diff options
author | Thomas Hintz <t@thintz.com> | 2020-02-05 18:04:50 -0800 |
---|---|---|
committer | Thomas Hintz <t@thintz.com> | 2020-02-05 18:04:50 -0800 |
commit | de7cf70319909161162cfe68a40265577450fbd0 (patch) | |
tree | ac29cd674c2f56c053c3c388332c8faaa3c28737 /src | |
download | farm-de7cf70319909161162cfe68a40265577450fbd0.tar.gz |
Initial public release.
Diffstat (limited to 'src')
142 files changed, 21075 insertions, 0 deletions
diff --git a/src/_vendor/sassy-lists/stylesheets/functions/_contain.scss b/src/_vendor/sassy-lists/stylesheets/functions/_contain.scss new file mode 100644 index 0000000..87d160b --- /dev/null +++ b/src/_vendor/sassy-lists/stylesheets/functions/_contain.scss @@ -0,0 +1,31 @@ +/// +/// Returns whether `$list` contains `$value`. +/// +/// @ignore Documentation: http://at-import.github.io/SassyLists/documentation/#function-sl-contain +/// +/// @param {List} $list - list to check +/// @param {*} $value - value to look for +/// +/// @example +/// sl-contain(a b c, a) +/// // true +/// +/// @example +/// sl-contain(a b c, z) +/// // false +/// +/// @return {Bool} +/// + +@function sl-contain($list, $value) { + @return not not index($list, $value); +} + +/// +/// @requires sl-contain +/// @alias sl-contain +/// + +@function sl-include($list, $value) { + @return sl-contain($list, $value); +} diff --git a/src/_vendor/sassy-lists/stylesheets/functions/_purge.scss b/src/_vendor/sassy-lists/stylesheets/functions/_purge.scss new file mode 100644 index 0000000..63102bf --- /dev/null +++ b/src/_vendor/sassy-lists/stylesheets/functions/_purge.scss @@ -0,0 +1,38 @@ +/// Removes all false and null values from `$list`. +/// +/// @ignore Documentation: http://at-import.github.io/SassyLists/documentation/#function-sl-purge +/// +/// @requires sl-is-true +/// @requires sl-to-list +/// +/// @param {List} $list - list to purge +/// +/// @example +/// sl-purge(null a false b) +/// // a b +/// +/// @return {List} +/// + +@function sl-purge($list) { + $_: sl-missing-dependencies('sl-is-true', 'sl-to-list'); + + $result: (); + + @each $item in $list { + @if sl-is-true($item) { + $result: append($result, $item, list-separator($list)); + } + } + + @return sl-to-list($result); +} + +/// +/// @requires sl-purge +/// @alias sl-purge +/// + +@function sl-clean($list) { + @return sl-purge($list); +} diff --git a/src/_vendor/sassy-lists/stylesheets/functions/_remove.scss b/src/_vendor/sassy-lists/stylesheets/functions/_remove.scss new file mode 100644 index 0000000..0282744 --- /dev/null +++ b/src/_vendor/sassy-lists/stylesheets/functions/_remove.scss @@ -0,0 +1,31 @@ +/// +/// Removes value(s) `$value` from `$list`. +/// +/// @ignore Documentation: http://at-import.github.io/SassyLists/documentation/#function-sl-remove +/// +/// @requires sl-replace +/// +/// @param {List} $list - list to update +/// @param {*} $value - value to remove +/// +/// @example +/// sl-remove(a b c, a) +/// // b c +/// +/// @return {List} +/// + +@function sl-remove($list, $value) { + $_: sl-missing-dependencies('sl-replace'); + + @return sl-replace($list, $value, null); +} + +/// +/// @requires sl-remove +/// @alias sl-remove +/// + +@function sl-without($list, $value) { + @return sl-remove($list, $value); +} diff --git a/src/_vendor/sassy-lists/stylesheets/functions/_replace.scss b/src/_vendor/sassy-lists/stylesheets/functions/_replace.scss new file mode 100644 index 0000000..8e70ad5 --- /dev/null +++ b/src/_vendor/sassy-lists/stylesheets/functions/_replace.scss @@ -0,0 +1,46 @@ +/// +/// Replaces `$old` by `$new` in `$list`. +/// +/// @ignore Documentation: http://at-import.github.io/SassyLists/documentation/#function-sl-replace +/// +/// @requires sl-is-true +/// @requires sl-purge +/// @requires sl-to-list +/// +/// @param {List} $list - list to update +/// @param {*} $old - value to replace +/// @param {*} $value - new value for $old +/// +/// @example +/// sl-replace(a b c, b, z) +/// // a z c +/// +/// @example +/// sl-replace(a b c, y, z) +/// // a b c +/// +/// @return {List} +/// + +@function sl-replace($list, $old, $value) { + $_: sl-missing-dependencies('sl-is-true', 'sl-purge', 'sl-to-list'); + + $running: true; + + @while $running { + $index: index($list, $old); + + @if not $index { + $running: false; + } + + @else { + $list: set-nth($list, $index, $value); + } + + } + + $list: if(sl-is-true($value), $list, sl-purge($list)); + + @return sl-to-list($list); +} diff --git a/src/_vendor/sassy-lists/stylesheets/functions/_to-list.scss b/src/_vendor/sassy-lists/stylesheets/functions/_to-list.scss new file mode 100644 index 0000000..eb8df21 --- /dev/null +++ b/src/_vendor/sassy-lists/stylesheets/functions/_to-list.scss @@ -0,0 +1,27 @@ +/// +/// Casts `$value` into a list. +/// +/// @ignore Documentation: http://at-import.github.io/SassyLists/documentation/#function-sl-to-list +/// +/// @param {*} $value - value to cast to list +/// @param {String} $separator [space] - separator to use +/// +/// @example +/// sl-to-list(a b c, comma) +/// // a, b, c +/// +/// @return {List} +/// + +@function sl-to-list($value, $separator: list-separator($value)) { + @return join((), $value, $separator); +} + +/// +/// @requires sl-to-list +/// @alias sl-to-list +/// + +@function sl-listify($value) { + @return sl-to-list($value); +} diff --git a/src/_vendor/sassy-lists/stylesheets/helpers/_missing-dependencies.scss b/src/_vendor/sassy-lists/stylesheets/helpers/_missing-dependencies.scss new file mode 100644 index 0000000..c4730b1 --- /dev/null +++ b/src/_vendor/sassy-lists/stylesheets/helpers/_missing-dependencies.scss @@ -0,0 +1,25 @@ +/// +/// Checks whether `$functions` exist in global scope. +/// +/// @access private +/// +/// @param {ArgList} $functions - list of functions to check for +/// +/// @return {Bool} Whether or not there are missing dependencies +/// + +@function sl-missing-dependencies($functions...) { + $missing-dependencies: (); + + @each $function in $functions { + @if not function-exists($function) { + $missing-dependencies: append($missing-dependencies, $function, comma); + } + } + + @if length($missing-dependencies) > 0 { + @error 'Unmet dependencies! The following functions are required: #{$missing-dependencies}.'; + } + + @return length($missing-dependencies) > 0; +} diff --git a/src/_vendor/sassy-lists/stylesheets/helpers/_true.scss b/src/_vendor/sassy-lists/stylesheets/helpers/_true.scss new file mode 100644 index 0000000..277652e --- /dev/null +++ b/src/_vendor/sassy-lists/stylesheets/helpers/_true.scss @@ -0,0 +1,13 @@ +/// +/// Returns truthiness of `$value`. +/// +/// @access private +/// +/// @param {*} $value - value to check +/// +/// @return {Bool} +/// + +@function sl-is-true($value) { + @return if($value == null, false, $value and $value != null and $value != '' and $value != ()); +} diff --git a/src/components/app/App.jsx b/src/components/app/App.jsx new file mode 100644 index 0000000..9fae531 --- /dev/null +++ b/src/components/app/App.jsx @@ -0,0 +1,86 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React, { Fragment } from 'react' +import { connect } from 'react-redux' + +import Board from '../farm/Board.jsx' +import MessagePanel from '../farm/MessagePanel.jsx' +import CreateOrJoin from '../create-or-join/CreateOrJoin.jsx' +import NewGame from '../new-game/NewGame.jsx' +import JoinGame from '../join-game/JoinGame.jsx' +import Welcome from '../welcome/Welcome.jsx' +import Tractor from '../tractor/Tractor.jsx' + +import { SCREENS, messagePanelId } from '../../constants.js' +import { play } from './actions.js' + +class Chrome extends React.Component { + render() { + return ( + <div className='flex-fullcenter'> + <div className='background-heading'><h1>Alpha Centauri Farming</h1></div> + {this.props.children} + <Tractor spikes={this.props.spikes} className={this.props.tractorClass} /> + </div> + ); + } +} + +class App extends React.Component { + render() { + let view; + switch (this.props.screen) { + case SCREENS.intro: + view = (<Chrome spikes={true} tractorClass='intro'><Welcome /></Chrome>); + break; + case SCREENS.start: + view = (<Chrome><CreateOrJoin /></Chrome>); + break; + case SCREENS.newGame: + view = (<Chrome> + <div className='view-container'> + <NewGame colors={['green', 'red', 'blue', 'yellow', 'black']} + button={'Start'} + title={'New Game'} + type={'new-game'} + showGameName={true} /> + </div> + </Chrome>); + break; + case SCREENS.joinGame: + view = (<Chrome><div className='view-container'><JoinGame /></div></Chrome>); + break; + case SCREENS.play: + view = (<Board />); + break; + } + return ( + <Fragment> + {view} + <div id={messagePanelId}><MessagePanel /></div> + </Fragment> + ); + } +} + +export default connect( + state => state.app, + null +)(App); + diff --git a/src/components/app/actionTypes.js b/src/components/app/actionTypes.js new file mode 100644 index 0000000..92b4d83 --- /dev/null +++ b/src/components/app/actionTypes.js @@ -0,0 +1,22 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +export const START = 'start'; +export const PLAY = 'play'; +export const SHOW_NEW_GAME = 'show-new-game'; +export const SHOW_JOIN_GAME = 'show-join-game'; diff --git a/src/components/app/actions.js b/src/components/app/actions.js new file mode 100644 index 0000000..797d285 --- /dev/null +++ b/src/components/app/actions.js @@ -0,0 +1,37 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import { START, PLAY, SHOW_NEW_GAME, SHOW_JOIN_GAME } from './actionTypes.js' + +export { start, play, showNewGame, showJoinGame } + +function start() { + return { type: START }; +} + +function play() { + return { type: PLAY }; +} + +function showNewGame() { + return { type: SHOW_NEW_GAME }; +} + +function showJoinGame() { + return { type: SHOW_JOIN_GAME }; +} diff --git a/src/components/app/reducers.js b/src/components/app/reducers.js new file mode 100644 index 0000000..647f3bd --- /dev/null +++ b/src/components/app/reducers.js @@ -0,0 +1,40 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import { PLAY, SHOW_NEW_GAME, SHOW_JOIN_GAME, START } from './actionTypes.js' +import { SCREENS } from '../../constants.js' + +const initialState = { + screen: SCREENS.intro +}; + +export default function(state = initialState, action) { + switch (action.type) { + case START: + return { ...state, screen: SCREENS.start }; + case PLAY: + return { ...state, screen: SCREENS.play }; + case SHOW_NEW_GAME: + return { ...state, screen: SCREENS.newGame }; + case SHOW_JOIN_GAME: + return { ...state, screen: SCREENS.joinGame }; + default: + return state; + } +} + diff --git a/src/components/create-or-join/CreateOrJoin.jsx b/src/components/create-or-join/CreateOrJoin.jsx new file mode 100644 index 0000000..cdc6b03 --- /dev/null +++ b/src/components/create-or-join/CreateOrJoin.jsx @@ -0,0 +1,45 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React, { Fragment } from 'react' +import { connect } from 'react-redux' + +import { GroupBox, Row, Col, Button } from '../widgets.jsx' +import { showNewGame, showJoinGame } from '../app/actions.js' + +class CreateOrJoin extends React.Component { + render() { + return ( + <Fragment> + <Button size='large' className='shadow' onClick={this.props.showNewGame}> + New Game + </Button> + <Button size='large' className='shadow' onClick={this.props.showJoinGame}> + Join Game + </Button> + </Fragment> + ); + } +} + +export default connect( + state => state, + { showNewGame, + showJoinGame + } +)(CreateOrJoin) diff --git a/src/components/farm/Board.jsx b/src/components/farm/Board.jsx new file mode 100644 index 0000000..963178b --- /dev/null +++ b/src/components/farm/Board.jsx @@ -0,0 +1,1446 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import CornImg from './../../../assets/img/corn.svg' +import FruitImg from './../../../assets/img/fruit.svg' +import CowImg from './../../../assets/img/cow.svg' +import HayImg from './../../../assets/img/hay.svg' +import WheatImg from './../../../assets/img/wheat.svg' +import TractorImg from './../../../assets/img/tractor-icon.svg' +import TractorFullImg from './../../../assets/img/tractor-with-spikes.svg' +import HarvesterImg from './../../../assets/img/harvester.svg' + +import React, { Fragment } from 'react' +import ReactDOM from 'react-dom' +import { connect } from 'react-redux' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faUser, faUsers, faTractor, faWindowRestore, + faDollarSign, faTimes, faAsterisk + } from '@fortawesome/free-solid-svg-icons' + +import { GroupBox, Row, Col, Button } from '../widgets.jsx' +import SpaceNode from './SpaceNode.jsx' +import Tractor from '../tractor/Tractor.jsx' + +import { GAME_STATES, ALERTS } from '../../constants.js' +import { itemCard, itemCardShort, fateCard, ridgeNames } from 'game.js' +import { setSelectedCard, setMessagePanelSpace, setMPDims, movePlayer, + nextUIAction, alert } from './actions.js' +import { buy, roll, endTurn, loan, trade, submitTradeAccept, + submitTradeDeny, submitTradeCancel, audit, + buyUncleBert } from './interface.js' + +function netWorth(player) { + return ((player.assets.hay + player.assets.grain) * 2000) + + (player.assets.fruit * 5000) + + (player.assets.cows * 500) + + ((player.assets.harvester + player.assets.tractor) * 10000) + + player.cash - player.debt; +} + +function assetsValue(player) { + return ((player.assets.hay + player.assets.grain) * 2000) + + (player.assets.fruit * 5000) + + (player.assets.cows * 500) + + ((player.assets.harvester + player.assets.tractor) * 10000); +} + +function getElementValue(id) { + return document.getElementById(id).value; +} + +function getString(id) { + return getElementValue(id); +} + +function getInt(id) { + return parseInt(getElementValue(id)); +} + +function getChecked(id) { + return document.getElementById(id).checked; +} + +function getOption(id) { + let options = document.getElementById(id).selectedOptions; + if (options.length > 0) { + return options[0].value; + } else { + return false; + } +} + +function submitTrade() { + trade({ hay: getInt('trade-hay'), grain: getInt('trade-grain'), + fruit: getInt('trade-fruit'), cows: getInt('trade-cows'), + harvester: getInt('trade-harvesters'), + tractor: getInt('trade-tractors'), + ridge1: getChecked('trade-ridge1'), + ridge2: getChecked('trade-ridge2'), + ridge3: getChecked('trade-ridge3'), + ridge4: getChecked('trade-ridge4'), + player: getOption('trade-player'), + money: getInt('trade-money'), cards: getString('trade-cards') }); +} + +function tradeString(player, invert) { + var r = ''; + let mult = invert ? -1 : 1; + for (var k in player.trade) { + if (k !== 'player' && k !== 'originator' && k !== 'cards') { + r += (player.trade[k] === true ? '' : player.trade[k] * mult) + ' ' + k + ', '; + } else if (k === 'cards') { + r += 'cards: ' + player.trade[k] + ', '; + } + } + return r.slice(0, -2); // remove last ", " +} + +class PlayerTradeProposed extends React.Component { + render () { + if (this.props.player.trade.player === undefined) { + return (<br />); + } else if (this.props.player.trade.originator === this.props.player.name) { + return (<p>You proposed a trade with <b>{this.props.player.trade.player}</b> for + {'\u00A0'}<b>{tradeString(this.props.player, false)}</b>.</p>); + } else { + return (<p><b>{this.props.player.trade.player}</b> proposed a trade for + {'\u00A0'}<b>{tradeString(this.props.player, true)}</b>.</p>); + } + } +} + +function tradeFormSubmit(e, player) { + e.preventDefault(); + if (player.trade.player === undefined) { + submitTrade(); + } else if (player.trade.originator === player.name) { + submitTradeCancel(); + } else { + submitTradeAccept(); + } + return false; +} + +class PlayerTradeButton extends React.Component { + tradeDeny = e => { + e.preventDefault(); + submitTradeDeny(); + } + + render() { + var text = ''; + var receiver = false; + if (this.props.player.trade.player === undefined) { + text = 'Propose'; + } else if (this.props.player.trade.originator === this.props.player.name) { + text = 'Cancel'; + } else { + text = 'Accept'; + receiver = true; + } + if (!receiver) { + return (<Button className='tiny' type='submit'>{text} Trade</Button>); + } else { + return (<Fragment> + <Button className='tiny' type='submit'>{text} Trade</Button> + <Button className='tiny' onClick={this.submitTradeDeny}> + Deny Trade + </Button> + </Fragment>); + } + } +} + +class ResourceUnit extends React.Component { + render () { + const hslString = 'hsl(' + this.props.h + ', ' + this.props.s; + return ( + <div className='resource-unit' + title={this.props.amount + ' ' + this.props.label} + style={{backgroundColor: hslString + '%, 85%)', + borderTop: '3px solid ' + hslString + '%, 55%)'}}> + {this.props.img ? (<img src={this.props.img} />) : (<Fragment />)} + {this.props.children} + </div> + ); + } +} + +class PlayerTradeResources extends React.Component { + render () { + let player = this.props.player; + return (<form onSubmit={e => tradeFormSubmit(e, player)}> +<div className='player-trade-resources'> + <ResourceUnit h='120' s='100' label='acres of Hay' amount={''}> + <input type='number' id='trade-hay' defaultValue='0' /></ResourceUnit> {' '} + <ResourceUnit h='41' s='100' label='acres of Grain' amount={''}> + <input type='number' id='trade-grain' defaultValue='0' /></ResourceUnit> {' '} + <ResourceUnit h='0' s='100' label='acres of Fruit' amount={''}> + <input type='number' id='trade-fruit' defaultValue='0' /></ResourceUnit> {' '} + <ResourceUnit h='0' s='59' label='head of Cows' amount={''}> + <input type='number' id='trade-cows' defaultValue='0' /></ResourceUnit> {' '} + <ResourceUnit h='240' s='100' label='Harvesters' amount={''}> + <input type='number' id='trade-harvesters' defaultValue='0' /></ResourceUnit> {' '} + <ResourceUnit h='240' s='100' label='Tractors' amount={''}> + <input type='number' id='trade-tractors' defaultValue='0' /></ResourceUnit> + <br /><br /> + <b>Ridges</b>: <b>{ridgeNames[0][0]}</b>: <input type='checkbox' id='trade-ridge1' />{'\u00A0'} + <b>R</b>: <input type='checkbox' id='trade-ridge2' />{'\u00A0'} + <b>C</b>: <input type='checkbox' id='trade-ridge3' />{'\u00A0'} + <b>T</b>: <input type='checkbox' id='trade-ridge4' />{'\u00A0'} + <br /><br /> + <Row> + <Col width='4 column-no-padding'> + <label htmlFor='trade-player'><b>Player</b>:</label> + <select id='trade-player'> + {this.props.otherPlayers.map(p => ( + <option key={p.player.name} value={p.player.name}>{p.player.name}</option> + ))} + </select> + </Col> + <Col width='4 column-no-padding'> + <label htmlFor='trade-money'><b>Money</b>: </label> + <input type='number' id='trade-money' defaultValue='0' /> + </Col> + <Col width='4 column-no-padding'> + <label htmlFor='trade-cards'><b>Cards</b>: </label> + <input type='text' id='trade-cards' /> + </Col> + </Row> + <PlayerTradeProposed player={this.props.player} /> + <PlayerTradeButton player={this.props.player} /> +</div> +</form>); + } +} + +class PlayerResources extends React.Component { + render () { + const player = this.props.player; + return ( + <div className='resource-unit-container'> + <ResourceUnit img={HayImg} h='120' s='100' label='acres of Hay' + amount={player.assets.hay}> + {player.assets.hay} + </ResourceUnit> {' '} + <ResourceUnit img={WheatImg} h='41' s='100' label='acres of Grain' + amount={player.assets.grain}> + {player.assets.grain} + </ResourceUnit> {' '} + <ResourceUnit img={FruitImg} h='0' s='100' label='acres of Fruit' + amount={player.assets.fruit}> + {player.assets.fruit} + </ResourceUnit> {' '} + <ResourceUnit img={CowImg} h='0' s='59' label='head of Cows' + amount={player.assets.cows}> + {player.assets.cows} + </ResourceUnit> {' '} + <ResourceUnit img={HarvesterImg} h='240' s='100' label='Harvesters' + amount={player.assets.harvester}> + {player.assets.harvester} + </ResourceUnit> {' '} + <ResourceUnit img={TractorImg} h='240' s='100' label='Tractors' + amount={player.assets.tractor}> + {player.assets.tractor} + </ResourceUnit> + </div> + ); + } +} + +// http://stackoverflow.com/questions/149055 +function formatMoney(n) { + return n.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, '$1,').slice(0, -2); } + +class MoneySummary extends React.Component { + render () { + return ( + <span><b>{this.props.title}</b>: ${formatMoney(this.props.value)}</span> + ); + } +} + +class PlayerSummary extends React.Component { + render () { + const player = this.props.player; + return ( + <GroupBox title={(<Fragment><div className={'player player-' + player.color}></div> {player.name} + </Fragment>)}> + <MoneySummary title='Cash' value={player.cash} /> {' '} + <MoneySummary title='Debt' value={player.debt} /> {' '} + <br /><br /> + <MoneySummary title='Assets' value={assetsValue(player)} /> {' '} + <MoneySummary title='Net worth' value={netWorth(player)} /> + <br /><br /> + <PlayerResources player={player} /> + <br /> + <PlayerTurnContainer player={player} + game={this.props.game} + ui={this.props.ui} + screen={this.props.screen} + showScreen={this.props.showScreen}/> + </GroupBox> + ); + } +} + +class PlayerTurnContainer extends React.Component { + clickRoll = () => { + roll(); + this.props.showScreen(SCREENS.action); + } + + render() { + let view; + const player = this.props.player, + worth = netWorth(player), + auditButton = (this.props.game.calledAudit === false && worth >= this.props.game.auditThreshold) ? ( + <Button onClick={audit}>Call Audit</Button>) : ''; + if (player.state === GAME_STATES.preTurn) { + view = ( + <Fragment> + <Button onClick={this.clickRoll}>Roll</Button>{' '}{auditButton} + </Fragment> + ); + } else if (player.state === GAME_STATES.midTurn) { + if (this.props.ui.action) { + if (this.props.screen === SCREENS.action) { + view = `It's your turn!`; + } else { + view = ( + <Button onClick={() => this.props.showScreen(SCREENS.action)}> + Complete your turn + </Button>); + } + } else { + if (player.cash < 0) { + view = ( + <Button onClick={() => this.props.showScreen(SCREENS.loans)}> + Raise Cash + </Button> + ); + } else { + view = ( + <Fragment> + <Button onClick={endTurn}> + End Turn + </Button>{' '}{auditButton} + </Fragment>); + } + } + } else if (this.props.game.state === 'finished') { + view = (<b>Game finished!</b>); + } else { + view = ( + <Button onClick={() => this.props.showScreen(SCREENS.action)}> + Watch {this.props.game.currentPlayer}'s turn + </Button>); + } + return ( + <Row> + <Col width='12'> + <div className='turn-container'> + {view} + </div> + </Col> + </Row> + ); + } +} + +// just the circle, for players circles in a space see PlayerIcon +class PlayerColorIcon extends React.Component { + render() { + return (<div className={'player player-' + this.props.color}></div>); + } +} + +function playerHasRidge(player, ridge) { + return player.ridges[ridge] !== 0 ? true : false; +} + +function makeRidgeLookup(player, otherPlayers) { + var ridges = { ridge1: playerHasRidge(player, 'ridge1') ? player.color : false, + ridge2: playerHasRidge(player, 'ridge2') ? player.color : false, + ridge3: playerHasRidge(player, 'ridge3') ? player.color : false, + ridge4: playerHasRidge(player, 'ridge4') ? player.color : false }; + for (let i = 0; i < otherPlayers.length; i++) { + let p = otherPlayers[i].player; + if (!ridges.ridge1) { + ridges.ridge1 = playerHasRidge(p, 'ridge1') ? p.color : false; } + if (!ridges.rattlensake) { + ridges.ridge2 = playerHasRidge(p, 'ridge2') ? p.color : false; } + if (!ridges.ridge3) { + ridges.ridge3 = playerHasRidge(p, 'ridge3') ? p.color : false; } + if (!ridges.ridge4) { + ridges.ridge4 = playerHasRidge(p, 'ridge4') ? p.color : false; } + } + return ridges; +} + +class FarmsContainer extends React.Component { + render() { + let ridges = makeRidgeLookup(this.props.player, this.props.otherPlayers); + return ( + <GroupBox title='Farms'> + <b>Ridges</b>: <b>{ridgeNames[0][0]}</b>: <PlayerColorIcon color={ridges.ridge1} />{'\u00A0'} + <b>{ridgeNames[1][0]}</b>: <PlayerColorIcon color={ridges.ridge2} />{'\u00A0'} + <b>{ridgeNames[2][0]}</b>: <PlayerColorIcon color={ridges.ridge3} />{'\u00A0'} + <b>{ridgeNames[3][0]}</b>: <PlayerColorIcon color={ridges.ridge4} />{'\u00A0'} + <br /><br /> + {this.props.otherPlayers + .map(p => ( + <div key={p.player.name}> + <PlayerColorIcon color={p.player.color} />{'\u00A0'} + <b>{p.player.name}</b> <PlayerResources player={p.player} /> + <br /> <br /> + </div>))} + </GroupBox> + ); + } +} + +class TradeContainer extends React.Component { + render() { + return ( + <GroupBox title='Trade'> + <PlayerTradeResources otherPlayers={this.props.game.otherPlayers} + player={this.props.player} /> + </GroupBox> + ); + } +} + +class CCBY extends React.Component { + render() { + return ( + <Fragment> + License Creative Commons <a href='https://creativecommons.org/licenses/by/3.0/us/legalcode'>CCBY</a> + </Fragment> + ); + } +} + +class Misc extends React.Component { + render() { + return ( + <div className='credits'> + <h1>Credits</h1> + <ul> + <li> + Game created by <a href='https://thomashintz.org'>Thomas Hintz</a>. + </li> + <li> + <a href='https://code.thintz.com/farm'>Game source</a> available under the <a href='https://www.gnu.org/licenses/gpl-3.0-standalone.html'>GPLv3</a>+ license. + </li> + <li> + <img src={TractorFullImg} /> <img src={TractorImg} /> Copyright Nick Roach with modifications by Thomas Hintz - License <a href='GPL http://www.gnu.org/copyleft/gpl.html'>GPL</a> + </li> + <li> + <img src={CornImg} /> <img src={FruitImg} /> Copyright <a href='https://madexmade.com/'>Made</a> - <CCBY /> + </li> + <li> + <img src={CowImg} /> Copyright <a href='https://thenounproject.com/rivercon/'>rivercon</a> - <CCBY /> + </li> + <li> + <img src={HayImg} /> Copyright <a href='https://thenounproject.com/maxicons/'>Maxicons</a> - <CCBY /> + </li> + <li> + <img src={WheatImg} /> Copyright <a href='https://thenounproject.com/amoghdesign/'>P Thanga Vignesh</a> - <CCBY /> + </li> + <li> + <img src={HarvesterImg} /> Copyright <a href='https://andrejskirma.com/'>Andrejs Kirma</a> - <CCBY /> + </li> + </ul> + </div> + ); + } +} + +class Messages extends React.Component { + render() { + let lastTurn = -1, + newTurn = false; + return ( + <div className='messages'> + {this.props.game.messages.map((m, i) => ( + <span key={i}>{m[0] === this.props.player.name ? (<b>{m[0]}</b>) : m[0]}{': ' + m[2]}<br /></span> + ))} + {this.props.game.messages.length > 0 ? (<Fragment><br /><br /></Fragment>) : (<Fragment />)} + {this.props.game.oldMessages.map((m, i) => { + newTurn = false; + if (m[1] !== lastTurn) { + lastTurn = m[1]; + newTurn = true; + } + return ( + <Fragment key={i}> + {newTurn ? (<Fragment><b>Turn {m[1]}</b><br /></Fragment>) : <Fragment />} + <span> + {m[0] + ': ' + m[2]} + <br /></span> + </Fragment> + )})} + </div> + ); + } +} + +class Loans extends React.Component { + constructor(props) { + super(props); + this.state = { repay: 0, takeOut: 0 }; + } + + handleInput = e => { + const target = e.target, + name = e.target.name; + let value = Math.max(0, parseInt(e.target.value)); + if (isNaN(value)) { value = 0; } + let repay = 0, + takeOut = 0; + if (name === 'repay') { + repay = value * 1000; + } else { + takeOut = value * 1000; + } + this.setState({ repay: Math.max(0, Math.floor( + Math.min(repay, this.props.player.debt, this.props.player.cash) / 1000)), + takeOut: Math.max(0, Math.floor( + Math.min(takeOut, 50000 - this.props.player.debt) / 1000)) }); + } + + handleSubmit = e => { + e.preventDefault(); + loan((this.state.repay + this.state.takeOut) * (this.state.repay > 0 ? -1 : 1)); + this.setState({ repay: 0, takeOut: 0 }); + } + + render () { + return ( + <GroupBox title={'Loans'}> + <MoneySummary title='Cash' value={this.props.player.cash} /> {' '} + <MoneySummary title='Debt' value={this.props.player.debt} />/$50,000 + <br /><br /> + <form onSubmit={this.handleSubmit}> + <Row collapse='true'> + <Col width='8'> + <div className='money'> + $: + <input onChange={this.handleInput} name='repay' type='number' + value={this.state.repay === 0 ? '' : this.state.repay}/> + {'\u00A0'}K / ${this.props.player.debt / 1000}K + </div> + </Col> + <Col width='4'> + <Button className='tiny' type='submit'>Repay</Button> + </Col> + </Row> + <Row collapse='true'> + <Col width='8'> + <div className='money'> + $: + <input onChange={this.handleInput} name='takeOut' type='number' + value={this.state.takeOut === 0 ? '' : this.state.takeOut} /> + {'\u00A0'}K / ${(50000 - this.props.player.debt) / 1000}K + </div> + </Col> + <Col width='4'> + <Button className='tiny' type='submit'>Take Out</Button> + </Col> + </Row> + </form> + </GroupBox> + ); + } +} + +function random(max, min = 1) { + return Math.floor(Math.random() * max) + min; +} + +function decay(totalMs, decayFactorPct, ticksToGo) { + return totalMs * Math.pow(1 - decayFactorPct, ticksToGo); +} + +class Die extends React.Component { + timerId = false; + lastRoll = 0; + ticks = 0; + maxTicks = Number.MAX_SAFE_INTEGER; + speed = { fast: 0.1, slow: 0.7 }; + interval = 100; + acc = 0; + trigger = 0; + decayFactorPct = 0.4; + showScreenTimerId = false; + + constructor(props) { + super(props); + this.state = { num: props.roll ? this.roll() : props.num, + finalFace: false } + this.trigger = 1000 * this.speed[props.speed ? props.speed : 'fast'] + if (props.ms) { + this.maxTicks = Math.floor(props.ms / this.trigger); + if (props.decay) { + this.trigger = decay(props.ms, this.decayFactorPct, this.maxTicks); + } + } + } + + roll() { + let roll = random(6); + while (roll === this.lastRoll) { + roll = random(6); + } + this.lastRoll = roll; + return roll; + } + + tick() { + this.acc += this.interval; + // update die face + if (this.ticks < this.maxTicks && this.acc >= this.trigger) { + this.ticks++; + this.acc = 0; + // update trigger for decay + if (this.props.decay) { + this.trigger = decay(this.props.ms, this.decayFactorPct, + this.maxTicks - this.ticks); + } + // we're finished, clear things, set final roll value + if (this.ticks >= this.maxTicks) { + clearInterval(this.timerId); + this.timerId = false; + this.setState({ num: this.props.num, finalFace: true }); + if (this.props.showScreen) { + this.showScreenTimerId = setInterval(() => { + this.props.showScreen(); + clearInterval(this.showScreenTimerId); + this.showScreenTimerId = false; + }, this.props.showScreenDelay); + } + } else { + this.setState({ num: this.roll() }); + } + } + } + + componentDidMount() { + if (this.props.roll) { + this.timerId = setInterval(() => this.tick(), this.interval); + } + } + + componentWillUnmount() { + if (this.timerId) { + clearInterval(this.timerId); + } + if (this.showScreenTimerId) { + clearInterval(this.showScreenTimerId); + } + } + + skip() { + clearInterval(this.timerId); + this.timerId = false; + this.setState({ num: this.props.num, finalFace: true }); + if (this.props.showScreen) { + this.showScreenTimerId = setInterval(() => { + this.props.showScreen(); + clearInterval(this.showScreenTimerId); + this.showScreenTimerId = false; + }, this.props.showScreenDelay); + } + } + + render() { + let face; + switch (this.state.num) { + case 1: + face = (<div className='die-face face-1'></div>); + break; + case 2: + face = (<Fragment> + <div className='die-face face-21'></div> + <div className='die-face face-22'></div> + </Fragment>); + break; + case 3: + face = (<Fragment> + <div className='die-face face-21'></div> + <div className='die-face face-1'></div> + <div className='die-face face-22'></div> + </Fragment>); + break; + case 4: + face = (<Fragment> + <div className='die-face face-21'></div> + <div className='die-face face-22'></div> + <div className='die-face face-52'></div> + <div className='die-face face-54'></div> + </Fragment>); + break; + case 5: + face = (<Fragment> + <div className='die-face face-21'></div> + <div className='die-face face-1'></div> + <div className='die-face face-22'></div> + <div className='die-face face-52'></div> + <div className='die-face face-54'></div> + </Fragment>); + break; + case 6: + face = (<Fragment> + <div className='die-face face-21'></div> + <div className='die-face face-63'></div> + <div className='die-face face-64'></div> + <div className='die-face face-22'></div> + <div className='die-face face-52'></div> + <div className='die-face face-54'></div> + </Fragment>); + break; + } + return ( + <Fragment> + <div className={'die' + (this.state.finalFace ? ' die-selected' : '')}> + {face} + </div> + {this.props.skip && !this.state.finalFace ? + (<div className='center'> + <Button onClick={() => this.skip()}>Skip</Button> + </div>) : + (<Fragment />)} + </Fragment> + ); + } +} + +class PreRolling extends React.Component { + render() { + return ( + <GroupBox title={this.props.name + ' is rolling!'}> + <Die roll={true} /> + </GroupBox> + ); + } +} + +class Rolling extends React.Component { + render() { + return ( + <GroupBox title={this.props.name + ' is rolling!'}> + <Die decay={true} num={this.props.num} ms={2000} roll={true} + showScreen={this.props.showScreen} + skip={this.props.skip} + showScreenDelay={this.props.showScreenDelay} /> + </GroupBox> + ); + } +} + +class Harvest extends React.Component { + cropsToImg = { hay: HayImg, + cherries: FruitImg, + wheat: WheatImg, + cows: CowImg, + fruit: FruitImg, + corn: CornImg } + + constructor(props) { + super(props); + this.state = { view: 'ready' } + } + + nextView = view => { + this.setState({ view }); + } + + cropToImg = crop => { + return this.cropsToImg[crop]; + } + + render() { + let view; + switch (this.state.view) { + case 'ready': + view = ( + <div className='clear-background'> + <div className={'harvest-card space-type-' + this.props.crop}> + <h1>{this.props.crop + ' harvest!'}</h1> + <div className='harvest-card-contents'> + <img src={this.cropToImg(this.props.crop)} /> + Get ready to harvest <b>{this.props.acres} + {this.props.crop === 'cows' ? ' head of cow' : ' acres'}</b>! + </div> + {this.props.player.name === this.props.game.currentPlayer ? ( + <Button onClick={() => this.nextView('roll')}> + Roll for harvest! + </Button> + ) : (<Fragment />)} + </div> + </div> + ); + break; + case 'roll': + view = (<Die decay={true} num={this.props.rolled} ms={2000} roll={true} + showScreen={() => this.nextView('income')} + skip={true} + showScreenDelay={2000} />); + break; + case 'income': + view = ( + <div> + <div className='game-card'> + <div className={'flex green'}> + <FontAwesomeIcon icon={faDollarSign} size='6x' /> + <div> + {this.props.player.name === this.props.game.currentPlayer ? 'You' : + this.props.game.currentPlayer} rolled a <b>{this.props.rolled}</b> and earned <b>${formatMoney(this.props.income)}</b>! + </div> + </div> + </div> + <div className='center spacer'> + <Button onClick={() => this.nextView('operating-expense')}>Draw Operating Expense</Button> + </div> + </div> + ); + break; + case 'operating-expense': + view = ( + <Fragment> + <div className='game-card'> + <GroupBox title='Operating Expense'> + <div className='card' + dangerouslySetInnerHTML={{__html: this.props.contents}} /> + </GroupBox> + </div> + <div className='center spacer'> + <Button onClick={() => this.nextView('expense-value')}>Continue</Button> + </div> + </Fragment> + ); + break; + case 'expense-value': + view = ( + <div> + <div className='game-card'> + <div className={'flex ' + (this.props.expenseValue < 0 ? 'red' : 'green')}> + <FontAwesomeIcon icon={faDollarSign} size='6x' /> + <div> + {this.props.player.name === this.props.game.currentPlayer ? 'You' : + this.props.game.currentPlayer} {this.props.expenseValue < 0 ? 'lost ' : 'gained '} + ${Math.abs(this.props.expenseValue)}! + </div> + </div> + </div> + <div className='center spacer'> + <Button onClick={this.props.nextAction}>Continue</Button> + </div> + </div> + ); + break; + } + return view; + } +} + +// props: currentPos, moveTo, color +// actions: movePlayer +class Moving extends React.Component { + timerId = false + endPos = 0 + color = '' + hurtBack = false + + constructor(props) { + super(props); + const to = this.props.ui.actionValue.to, + from = this.props.ui.actionValue.from; + const currentPlayer = this.props.player.name === this.props.game.currentPlayer ? + this.props.player : this.props.game.otherPlayers + .find(p => p.player.name === this.props.game.currentPlayer).player; + const currentPos = from; + this.state = { currentPos: currentPos < 0 ? currentPos + 49 : currentPos }; + this.endPos = to; + this.color = currentPlayer.color; + // only for hurt back do we go backwards + this.hurtBack = to === 2 && from === 11 ? true : false; + } + + componentDidMount() { + // TODO combine with tick + const delta = this.state.currentPos === 48 ? -48 : this.hurtBack ? -1 : 1; + this.props.movePlayer(this.state.currentPos + delta, + this.state.currentPos, + this.color); + this.setState(state => ({ currentPos: state.currentPos + delta})); + if (this.state.currentPos + delta !== this.endPos) { + this.timerId = setInterval(() => this.tick(), 500); + } + } + + componentWillUnmount() { + if (this.timerId) { + clearInterval(this.timerId); + this.timerId = false; + } + } + + tick() { + const delta = this.state.currentPos === 48 ? -48 : this.hurtBack ? -1 : 1; + this.props.movePlayer(this.state.currentPos + delta, + this.state.currentPos, + this.color); + this.setState(state => ({ currentPos: state.currentPos + delta})); + if (this.state.currentPos === this.endPos) { + if (this.timerId) { clearInterval(this.timerId); } + this.timerId = false; + } + } + + skip() { + clearInterval(this.timerId); + this.timerId = false; + this.props.movePlayer(this.endPos, this.state.currentPos, this.color); + this.setState({ currentPos: this.endPos }); + } + + render() { + let buttons; + if (this.props.player.name !== this.props.game.currentPlayer) { + buttons = (<Fragment />); + } else if (this.state.currentPos === this.endPos) { + buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>); + } else { + buttons = (<Button onClick={() => this.skip()}>Skip</Button>); + } + return ( + <Row> + <Col width={'12'}> + <div className='moving'> + <div className={'action clear-background'}> + <SpaceNode space={this.props.spaces[this.state.currentPos]} + height={'170px'} showtitle={true} /> + </div> + </div> + <br /> + <div className={'center'}> + {buttons} + </div> + </Col> + </Row> + ); + } +} + +class Action extends React.Component { + render() { + let view, buttons; + const currentPlayer = this.props.player.name === this.props.game.currentPlayer ? + this.props.player : this.props.game.otherPlayers + .find(p => p.player.name === this.props.game.currentPlayer).player; + switch (this.props.ui.action) { + case 'otb': + view = ( + <div className='game-card'> + <GroupBox title={itemCard}> + <div className='card' + dangerouslySetInnerHTML={{__html: this.props.ui.actionValue}} /> + </GroupBox> + </div> + ); + buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>); + break; + case 'farmers-fate': + view = ( + <div className='game-card'> + <GroupBox title={fateCard}> + <div className='card' + dangerouslySetInnerHTML={{__html: this.props.ui.actionValue}} /> + </GroupBox> + </div> + ); + buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>); + break; + case 'ff-uncle-bert': + view = ( + <GroupBox title={`Uncle Bert's inheritance`}> + <div className='center'> + <p> + {this.props.player.cash >= 10000 ? + `You have enough cash to take over Uncle Bert's farm!` : + `You must raise another $` + + formatMoney(10000 - this.props.player.cash) + + ` to be able to take over Uncle Berts farm!` + } + </p> + <p> + {this.props.player.cash >= 10000 ? + (<Button onClick={() => { buyUncleBert(); this.props.showNextAction(); }}> + Yes, take over for $10,000!</Button>) : + (<Fragment />)} + <Button onClick={() => this.props.showNextAction()}> + No, continue on + </Button> + </p> + </div> + </GroupBox> + ); + buttons = (<Fragment />); + break; + case 'money': + view = (<div className='game-card'> + <div className={'flex ' + (this.props.ui.actionValue < 0 ? 'red' : 'green')}> + <FontAwesomeIcon icon={faDollarSign} size='6x' /> + <div> + {this.props.player.name === this.props.game.currentPlayer ? 'You' : + this.props.game.currentPlayer} {this.props.ui.actionValue < 0 ? 'lost ' : 'gained '} + ${Math.abs(this.props.ui.actionValue)}! + </div> + </div> + </div>); + buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>); + break; + case 'info': + view = ( + <div> + <p>{this.props.ui.actionValue}</p> + </div> + ); + buttons = (<Button onClick={() => this.props.showNextAction()}>Continue</Button>); + break; + case 'harvest': + view = (<Harvest rolled={this.props.ui.actionValue.rolled} + player={this.props.player} + game={this.props.game} + income={this.props.ui.actionValue.income} + contents={this.props.ui.actionValue.operatingExpense} + expenseValue={this.props.ui.actionValue.operatingExpenseValue} + crop={this.props.ui.actionValue.crop} + acres={this.props.ui.actionValue.acres} + nextAction={() => this.props.showNextAction()} />); + buttons = (<Fragment />); + break; + case 'move': + view = (<Moving showNextAction={() => this.props.showNextAction()} + key={this.props.game.currentPlayer + '|' + this.props.ui.actionValue.from + '|' + this.props.ui.actionValue.to} + player={this.props.player} + game={this.props.game} + spaces={this.props.spaces} + movePlayer={this.props.movePlayer} + ui={this.props.ui}/>); + buttons = (<Fragment />); + break; + case 'goto': + view = (<Moving showNextAction={() => this.props.showNextAction()} + key={this.props.game.currentPlayer + '|' + this.props.ui.actionValue.from + '|' + this.props.ui.actionValue.to} + player={this.props.player} + game={this.props.game} + spaces={this.props.spaces} + movePlayer={this.props.movePlayer} + ui={this.props.ui} />); + buttons = (<Fragment />); + break; + case 'pre-rolling': + view = (<PreRolling name={this.props.game.currentPlayer} />); + buttons = (<Fragment />); + break; + case 'roll': + view = (<Rolling num={this.props.ui.actionValue.to - this.props.ui.actionValue.from} + name={this.props.game.currentPlayer} + showScreen={this.props.player.name === this.props.game.currentPlayer + ? () => this.props.showNextAction() + : false} + skip={this.props.player.name === this.props.game.currentPlayer} + showScreenDelay={2000} />); + buttons = (<Fragment />); + break; + default: + if (this.props.player.name === this.props.game.currentPlayer) { + view = (<PlayerSummary player={this.props.player} + ui={this.props.ui} + screen={this.props.screen} + showScreen={this.props.showScreen} + game={this.props.game} />); + } else { + view = (<Fragment>Waiting for {this.props.game.currentPlayer}</Fragment>); + } + } + return ( + <Row> + <Col width={'12'}> + <div className={'action'}> + {view} + </div> + <br /> + <div className={'center'}> + {this.props.otherPlayersTurn ? (<Fragment />) : buttons} + </div> + </Col> + </Row> + ); + } +} + +class Board extends React.Component { + render() { + const rh = this.props.height || '20px'; // height={h} + const renderSpace = (s, h, o) => + (<SpaceNode space={s} key={s.key} orientation={o} />); + + return ( +<Fragment> + <Row collapse='true' className='row-spaces'> + <Col width='1'>{renderSpace(this.props.spaces[0], rh, 'corner-tl')}</Col> + <Col width='10'> + <div className='flex-row'> + {this.props.spaces.slice(1, 14).map(s => renderSpace(s, rh, 'top'))} + </div> + </Col> + <Col width='1'>{renderSpace(this.props.spaces[14], rh, 'corner-tr')}</Col> + </Row> + <Row collapse='true' className='mid-row-container'> + <Col width='1'> + <div className='mid-col'> + {this.props.spaces.slice(38, 49).reverse() + .map(s => renderSpace(s, this.props.height || false, 'left'))} + </div> + </Col> + <Col width='10'> + <div className='mid-row'> + <div className='center-board'> + <Row> + <Col width='12 column-no-padding'> + {this.props.children} + </Col> + </Row> + <div className='board-heading'><h1>Alpha Centauri Farming</h1></div> + <Tractor /> + </div> + </div> + </Col> + <Col width='1'> + <div className='mid-col'> + {this.props.spaces.slice(15, 25) + .map(s => renderSpace(s, this.props.height ? (parseInt(this.props.height) * 1.11) + 'px' : false, 'right'))} + </div> + </Col> + </Row> + <Row collapse='true' className='row-spaces'> + <Col width='1'>{renderSpace(this.props.spaces[37], rh, 'corner-bl')}</Col> + <Col width='10'> + <div className='flex-row'> + {this.props.spaces.slice(26, 37).reverse() + .map(s => renderSpace(s, rh, 'bottom'))} + </div> + </Col> + <Col width='1'>{renderSpace(this.props.spaces[25], rh, 'corner-br')}</Col> + </Row> + </Fragment> + ); + } +} + +// handler, buttonText, children +class AlertOverlay extends React.Component { + constructor(props) { + super(props); + this.state = { visible: typeof this.props.visible !== 'undefined' ? + this.props.visible : true }; + } + + hide = () => { + this.setState({ visible: false }); + this.props.hideHandler(); + } + + buttonClick = () => { + this.hide(); + this.props.handler(); + } + + render() { + return ( + <div className={'alert-overlay' + (this.state.visible ? '' : ' hidden') }> + <div onClick={this.hide} className='alert-overlay-hide'> + <FontAwesomeIcon icon={faTimes} /> + </div> + <div className='alert-overlay-contents'> + {this.props.children} + <br /> + <Button onClick={this.buttonClick}>{this.props.buttonText}</Button> + <label><input type='checkbox' /> {`Don't show again`}</label> + <a href='#' onClick={this.hide}>close</a> + </div> + </div> + ); + } +} + +const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms', + cards: 'cards', trade: 'trade', loans: 'loans', + action: 'action' }; + +class BoardApp extends React.Component { + iconToScreen = { user: SCREENS.summary, 'window-restore': SCREENS.cards, + 'dollar-sign': SCREENS.loans, users: SCREENS.farms, + 'exchange-alt': SCREENS.trade, asterisk: SCREENS.misc, + tractor: SCREENS.action } + unsubscribeAlert = () => null + + constructor(props) { + super(props); + this.state = { + screen: SCREENS.summary + }; + } + + showScreen = screen => { + this.setState({ screen: screen }); + } + + componentDidMount() { + // const midColHeight = (window.innerHeight - + // (document.getElementsByClassName('flex-row')[0].offsetHeight * 2)) + 'px'; + // const midCols = document.getElementsByClassName('mid-col'); + // midCols[0].style.height = midColHeight; + // midCols[1].style.height = midColHeight; + + // const mpDims = this.props.mpDims; + // const minWidth = midCols[0].clientWidth; + // const minHeight = 78 + mpDims.padding; + // const maxHeight = + // midCols[0].clientHeight + minHeight - 225 - mpDims.padding; + // const maxWidth = window.innerWidth - minWidth - 160 - mpDims.padding; + // this.props.setMPDims(minWidth, minHeight, maxHeight, maxWidth); + + // const midRow = document.getElementsByClassName('mid-row')[0]; + // midRow.style.height = midColHeight; + // midRow.onmouseover = () => this.props.setMessagePanelSpace(null); + } + + iconClass = icon => { + return this.iconToScreen[icon] === this.state.screen ? 'is-active' : ' '; + } + + tabClass = screen => { + return 'tab' + (screen === this.state.screen ? ' show' : ''); + } + + iconOnClick = icon => { + return () => this.showScreen(this.iconToScreen[icon]); + } + + render() { + let alertOverlay; + if (!this.props.ui.alertHandled && this.state.screen !== SCREENS.action) { + switch (this.props.ui.alert) { + case ALERTS.beginTurn: + alertOverlay = ( + <AlertOverlay visible={true} + buttonText='Roll' + hideHandler={() => this.props.alert(false)} + handler={() => { + roll(); + this.showScreen(SCREENS.action); }}> + <h1>{`It's your turn!`}</h1> + </AlertOverlay> + ); + break; + case ALERTS.otherPlayersTurn: + alertOverlay = ( + <AlertOverlay visible={true} + buttonText={'Watch ' + this.props.game.currentPlayer + `'s turn`} + hideHandler={() => this.props.alert(false)} + handler={() => { + this.showScreen(SCREENS.action); }}> + <h1>It's {this.props.game.currentPlayer}'s turn</h1> + </AlertOverlay> + ); + break; + default: + alertOverlay = (<Fragment />); + } + } else { + alertOverlay = (<Fragment />); + } + if (this.props.ui.alert === ALERTS.raiseMoney) { + alertOverlay = ( + <AlertOverlay visible={true} + buttonText='Raise Money' + hideHandler={() => this.props.alert(false)} + handler={() => { + this.showScreen(SCREENS.loans); }}> + <Fragment> + <h1>Out of cash!</h1> + <p>You have less than $0 cash and must raise money to continue.</p> + </Fragment> + </AlertOverlay> + ); + } + // faExchangeAlt -> trade icon, hidden for now + return ( + <div className='game-container'> + {alertOverlay} + <Board spaces={this.props.spaces}> + <div className='center-board-container'> + <ul className='horizontal menu icons icons-top'> + {[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faAsterisk] + .map((icon, i) => + (<li key={i} className={this.iconClass(icon.iconName)}> + <div></div> + <a href='#' onClick={this.iconOnClick(icon.iconName)}> + <FontAwesomeIcon icon={icon} /></a></li>))} + </ul> + <ul className='vertical menu icons icons-top'> + {[faUser, faTractor, faWindowRestore, faDollarSign, faUsers, faAsterisk] + .map((icon, i) => + (<li key={i} className={this.iconClass(icon.iconName)}> + <a href='#' onClick={this.iconOnClick(icon.iconName)}> + <FontAwesomeIcon icon={icon} /></a></li>))} + </ul> + <div className='tab-container'> + <div className={this.tabClass(SCREENS.summary)}> + <PlayerSummary player={this.props.player} + ui={this.props.ui} + screen={this.state.screen} + game={this.props.game} showScreen={this.showScreen} /> + </div> + <div className={this.tabClass(SCREENS.action)}> + <Action + spaces={this.props.spaces} + player={this.props.player} + game={this.props.game} + movePlayer={this.props.movePlayer} + showNextAction={this.props.nextUIAction} + otherPlayersTurn={this.props.player.name !== this.props.game.currentPlayer} + screen={this.state.screen} + showScreen={screen => this.showScreen(screen)} + ui={this.props.ui} /> + </div> + <div className={this.tabClass(SCREENS.cards)}> + <Row> + <div className='cell medium-auto'> + <CardList ui={this.props.ui} /> + </div> + <div className='cell medium-auto'> + <Card ui={this.props.ui} /> + </div> + </Row> + </div> + <div className={this.tabClass(SCREENS.loans)}> + <Loans player={this.props.player} /> + </div> + <div className={this.tabClass(SCREENS.farms)}> + <FarmsContainer player={this.props.player} + otherPlayers={this.props.game.otherPlayers} /> + </div> + <div className={this.tabClass(SCREENS.trade)}> + <TradeContainer player={this.props.player} game={this.props.game} /> + </div> + <div className={this.tabClass(SCREENS.misc)}> + <Misc /> + </div> + </div> + </div> + </Board> + </div> + ); + } +} + +export default connect( + state => state.farm, + { setMessagePanelSpace, setMPDims, nextUIAction, movePlayer, alert } +)(BoardApp) + +class Card extends React.Component { + render () { + const card = this.props.ui.card; + let action = (null); + switch (card.type) { + case 'otb': action = ( + <div className='card-action'> + <form onSubmit={(e) => + { e.preventDefault(); + buy(card.id, parseInt(document.getElementById('cash-input').value)); + return false; }}> + <Row collapse='true'> + <Col width='2'> + <Button className='tiny' type='submit'>Buy</Button> + </Col> + <Col width='4' /> + <Col width='6'> + <div className='money'> + $: + <input id='cash-input' type='number' /> + {'\u00A0'},000 + </div> + </Col> + </Row> + </form> + </div>); break; + case 'no-card': action = + (<span></span>); break; + } + return ( + <div className='game-card'> + <GroupBox title={this.props.ui.card.title}> + <div className='card-id'>{card.id}</div> + <div className='card' + dangerouslySetInnerHTML={{__html: card.contents}} /> + {this.props.hideBuying ? (<Fragment />) : action} + </GroupBox> + </div> + ); + } +} + +// (<option key={i}>{typeToText(c.type)}: {cropToText(c.crop)}</option>)) + +class CardListComp extends React.Component { + render () { + const typeToText = type => { switch (type) { + case 'otb': return itemCardShort }}, + cropToText = crop => { switch (crop) { + case 'fruit': return '5 acres Fruit'; + case 'grain': return '10 acres Grain'}}; + const ui = this.props.ui, + cards = ui.cards, + cardOps = cards.map((c, i) => + (<li key={i} className={c == ui.card ? 'card-select-selected' : ''} + onClick={() => this.props.setSelectedCard(c)}> + {c.summary} + </li>)); + + return ( + <GroupBox title='Cards'> + <ul className='card-select'> + {cardOps} + </ul> + </GroupBox> + ); + } +} + +const CardList = connect( + null, + { setSelectedCard } +)(CardListComp) diff --git a/src/components/farm/MessagePanel.jsx b/src/components/farm/MessagePanel.jsx new file mode 100644 index 0000000..19fde7f --- /dev/null +++ b/src/components/farm/MessagePanel.jsx @@ -0,0 +1,50 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React from 'react' +import { connect } from 'react-redux' + +import SpaceNode from './SpaceNode.jsx' + +import { setMessagePanelSpace, mpMouse } from './actions.js' + +class MessagePanel extends React.Component { + render () { + if (this.props.space !== null) { + const panel = document.getElementById('message-panel'), + mpDims = this.props.mpDims; + panel.style.top = + (Math.min(Math.max(mpDims.mouseY, mpDims.minHeight + mpDims.padding), + mpDims.maxHeight)) + 'px'; + panel.style.left = + (Math.min(Math.max(mpDims.mouseX, mpDims.minWidth + mpDims.padding), + mpDims.maxWidth)) + 'px'; + return ( + <SpaceNode space={this.props.space} height='210px' + showtitle={true} orientation={''} /> + ); + } else { + return null; + } + } +} + +export default connect( + state => state.farm, + null +)(MessagePanel); diff --git a/src/components/farm/PlayerIcon.jsx b/src/components/farm/PlayerIcon.jsx new file mode 100644 index 0000000..751faf0 --- /dev/null +++ b/src/components/farm/PlayerIcon.jsx @@ -0,0 +1,30 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React from 'react' + +export default class PlayerIcon extends React.Component { + render() { + return ( + <center> + {this.props.colors + .map(c => (<div key={c} className={'player player-' + c}></div>))} + </center> + ); + } +} diff --git a/src/components/farm/SpaceNode.jsx b/src/components/farm/SpaceNode.jsx new file mode 100644 index 0000000..44a72f3 --- /dev/null +++ b/src/components/farm/SpaceNode.jsx @@ -0,0 +1,69 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React from 'react' +import { connect } from 'react-redux' + +import PlayerIcon from './PlayerIcon.jsx' + +import { setMessagePanelSpace, mpMouse } from './actions.js' + +class SpaceNode extends React.Component { + render() { + const space = this.props.space; + let title = ''; + if (this.props.showtitle) { + switch (this.props.space.type) { + case 'hay': title = 'Hay Cutting'; break; + case 'cherry': title = 'Cherry Harvest'; break; + case 'wheat': title = 'Wheat Harvest'; break; + case 'cows': title = 'Livestock Sales'; break; + case 'apple': title = 'Apple Harvest'; break; + case 'corn': title = 'Corn Harvest'; break; + case 'buy': title = 'Purchasing'; break; + } + } + return ( + <div className={'space space-type-' + this.props.space.type + + ' space-orientation-' + this.props.orientation} + onMouseOver={evt => { + const clientRects = evt.target.getClientRects()[0]; + this.props.setMessagePanelSpace(space); + this.props.mpMouse(clientRects.left, clientRects.top); + return false; } }> + <div style={this.props.height ? {height: this.props.height} : {}}> + <center>{this.props.space.month}</center> + { this.props.showtitle ? ( + <div className='space-title'> + {title} + </div>) + : (null)} + { this.props.space.players.length ? <PlayerIcon colors={this.props.space.players} /> : ''} + <div className='space-description'> + {this.props.space.description} + </div> + </div> + </div> + ); + } +} + +export default connect( + null, + { setMessagePanelSpace, mpMouse } +)(SpaceNode) diff --git a/src/components/farm/actionTypes.js b/src/components/farm/actionTypes.js new file mode 100644 index 0000000..8c4f164 --- /dev/null +++ b/src/components/farm/actionTypes.js @@ -0,0 +1,36 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +export const UPDATE_GAME = 'update-game'; +export const UPDATE_PLAYER = 'update-player'; +export const GAME_STATE = 'game-state'; +export const SET_SELECTED_CARD = 'set-selected-card'; +export const SET_CARDS = 'set-cards'; +export const SPACE_PUSH_PLAYER = 'space-push-player'; +export const SPACE_CLEAR_PLAYERS = 'space-clear-players'; +export const SET_OLD_MESSAGES = 'set-old-messages'; +export const MESSAGE_PANEL_SPACE = 'message-panel-space'; +export const MP_MOUSE = 'mp-mouse'; +export const SET_MP_DIMS = 'set-mp-dims'; +export const MOVE_PLAYER = 'move-player' +export const SET_NEXT_ACTION = 'set-next-action' +export const NEXT_UI_ACTION = 'next-ui-action' +export const NEXT_UI_ACTION_SILENT = 'next-ui-action-silent' +export const MARK_ACTION_CHANGE_HANDLED = 'mark-action-change-handled' +export const ALERT = 'alert' +export const ALERT_HANDLED = 'alert-handled' diff --git a/src/components/farm/actions.js b/src/components/farm/actions.js new file mode 100644 index 0000000..1921671 --- /dev/null +++ b/src/components/farm/actions.js @@ -0,0 +1,115 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import { UPDATE_GAME, UPDATE_PLAYER, GAME_STATE, SET_SELECTED_CARD, SET_CARDS, + SPACE_PUSH_PLAYER, SPACE_CLEAR_PLAYERS, SET_OLD_MESSAGES, MESSAGE_PANEL_SPACE, + MP_MOUSE, SET_MP_DIMS, MARK_ACTION_CHANGE_HANDLED, SET_NEXT_ACTION, + MOVE_PLAYER, NEXT_UI_ACTION, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED + } from './actionTypes.js' + +export { updateGame, updatePlayer, gameState, setSelectedCard, setCards, + spacePushPlayer, spaceClearPlayers, setOldMessages, setMessagePanelSpace, + mpMouse, setMPDims, movePlayer, setNextAction, nextUIAction, + markActionChangeHandled, nextUIActionSilent, alert, alertHandled } + +function updateGame(update) { + return { type: UPDATE_GAME, + update }; +} + +function updatePlayer(update) { + return { type: UPDATE_PLAYER, + update }; +} + +function gameState(state) { + return { type: GAME_STATE, + state }; +} + +function setSelectedCard(card) { + return { type: SET_SELECTED_CARD, + // TODO share with initialState ui.card + card: card ? card : { type: 'no-card', contents: '', total: 0 } } +} + +function setCards(cards) { + return { type: SET_CARDS, + cards }; +} + +function spacePushPlayer(id, player) { + return { type: SPACE_PUSH_PLAYER, + id, + player }; +} + +function spaceClearPlayers(id) { + return { type: SPACE_CLEAR_PLAYERS, + id }; +} + +function setOldMessages(messages) { + return { type: SET_OLD_MESSAGES, + messages }; +} + +function setMessagePanelSpace(space) { + return { type: MESSAGE_PANEL_SPACE, + space }; +} + +function mpMouse(mouseX, mouseY) { + return { type: MP_MOUSE, + mouseX, mouseY }; +} + +function setMPDims(minWidth, minHeight, maxWidth, maxHeight) { + return { type: SET_MP_DIMS, + minWidth, minHeight, maxWidth, maxHeight }; +} + +function movePlayer(newSpace, oldSpace, player) { + return { type: MOVE_PLAYER, + newSpace, oldSpace, player }; +} + +function nextUIAction() { + return { type: NEXT_UI_ACTION }; +} + +function nextUIActionSilent() { + return { type: NEXT_UI_ACTION_SILENT }; +} + +function setNextAction(action, value) { + return { type: SET_NEXT_ACTION, + action, value }; +} + +function markActionChangeHandled() { + return { type: MARK_ACTION_CHANGE_HANDLED }; +} + +function alert(value) { + return { type: ALERT, value }; +} + +function alertHandled() { + return { type: ALERT_HANDLED }; +} diff --git a/src/components/farm/interface.js b/src/components/farm/interface.js new file mode 100644 index 0000000..3737603 --- /dev/null +++ b/src/components/farm/interface.js @@ -0,0 +1,177 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import { GAME_STATES, ALERTS } from '../../constants.js' + +import { batch } from 'react-redux' +import * as websocket from '../../websocket.js' + +import { updateGame, updatePlayer, gameState, setSelectedCard, setCards, + movePlayer, setOldMessages, markActionChangeHandled, + mpMouse, rolled, setNextAction, nextUIAction, nextUIActionSilent, alert + } from './actions.js' + +export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept, + submitTradeDeny, submitTradeCancel, audit, handleMessage, + nextAction, buyUncleBert, actionsFinished } + +let store; + +let spacesWithPlayers = []; +let loop = 0; +function handleMessage(evt) { + const data = JSON.parse(evt.data), + type = data.event; + + if (data.event === 'error') { + console.log('error:' + data.exn); + return; + } + batch(() => { + if (data.player.state === GAME_STATES.preTurn && + data.game.otherPlayers.length > 0 && + store.getState().farm.player.state !== GAME_STATES.preTurn) { + store.dispatch(alert(ALERTS.beginTurn)); + } else if (data.game.otherPlayers.length > 0 && + data.game.currentPlayer !== store.getState().farm.game.currentPlayer) { + store.dispatch(alert(ALERTS.otherPlayersTurn)); + } + store.dispatch(updatePlayer(data.player)); + if (data.event === 'init') { + store.dispatch(movePlayer(data.player.space, 0, data.player.color)); + } + // new player(s) added to game, put them on the board + if (data.game.otherPlayers.length !== store.getState().farm.game.otherPlayers.length) { + const otherPlayers = store.getState().farm.game.otherPlayers; + const newPlayers = data.game.otherPlayers.filter( + x => !otherPlayers.find(y => y.player.name === x.player.name)); + for (const p of newPlayers) { + store.dispatch(movePlayer(p.player.space, 0, p.player.color)); + } + } + const oldMessages = store.getState().farm.game.messages.slice(0, 20); + store.dispatch(updateGame(data.game)); + store.dispatch(setOldMessages(oldMessages)); + if (data.player.cards.length > 0) { + store.dispatch(setSelectedCard(data.player.cards[0])); + } else { + store.dispatch(setSelectedCard()); + } + store.dispatch(setCards(data.player.cards)); + if (data.event === 'action') { + if (data.player.name !== data.game.currentPlayer && + data.action !== 'roll') { + store.dispatch(nextUIAction()); + } + store.dispatch(setNextAction(data.action, data.value)); + if (data.action === 'roll') { + store.dispatch(nextUIAction()); + } + } + if (data.player.state === GAME_STATES.midTurn && + data.player.cash < 0 && + !store.getState().farm.ui.nextAction) { + store.dispatch(alert(ALERTS.raiseMoney)); + } + }); +}; + +let sendCommand; + +function buy(id, cash) { + sendCommand({ type: 'buy', id: id, cash: cash }); +} + +function roll() { + store.dispatch(setNextAction('pre-rolling', false)); + store.dispatch(nextUIActionSilent()); + sendCommand({ type: 'roll' }); +} + +function endTurn() { + store.dispatch(gameState(GAME_STATES.turnEnded)); + sendCommand({ type: 'turn-ended' }); +} + +function loan(amount) { + sendCommand({ type: 'loan', amount: amount }); +} + +function trade(parameters) { + sendCommand({ type: 'trade', + parameters: parameters }); +} + +function submitTradeAccept() { + sendCommand({ type: 'trade-accept' }); +} + +function submitTradeDeny() { + sendCommand({ type: 'trade-deny' }); +} + +function submitTradeCancel() { + sendCommand({ type: 'trade-cancel' }); +} + +function audit() { + sendCommand({ type: 'audit' }); +} + +function nextAction() { + sendCommand({ type: 'next-action' }); +} + +function buyUncleBert() { + sendCommand({ type: 'buy-uncle-bert' }); +} + +function actionsFinished() { + sendCommand({ type: 'actions-finished' }); +} + +function initialize(st, sc) { + store = st; + sendCommand = sc; + + const unsubscribe = store.subscribe( + () => { + const state = store.getState(); + if (state.farm.player.name === state.farm.game.currentPlayer + && !state.farm.ui.actionChangeHandled) { + store.dispatch(markActionChangeHandled()); + nextAction(); + } + }); + + // mpDims.mouseX = e.clientX + // window.onmousemove = e => store. + // dispatch(mpMouse(e.clientX, store.getState().farm.mpDims.mouseY)); + // document.addEventListener('keydown', keydown); +} + +function keydown(e) { + switch (e.key) { + case 'r': + roll(); + break; + case 'e': + endTurn(); + break; + } +} diff --git a/src/components/farm/reducers.js b/src/components/farm/reducers.js new file mode 100644 index 0000000..2baf4e6 --- /dev/null +++ b/src/components/farm/reducers.js @@ -0,0 +1,182 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import { UPDATE_GAME, UPDATE_PLAYER, GAME_STATE, SET_SELECTED_CARD, SET_CARDS, + SPACE_PUSH_PLAYER, SPACE_CLEAR_PLAYERS, + SET_OLD_MESSAGES, MESSAGE_PANEL_SPACE, MP_MOUSE, + SET_MP_DIMS, MOVE_PLAYER, SET_NEXT_ACTION, NEXT_UI_ACTION, + MARK_ACTION_CHANGE_HANDLED, NEXT_UI_ACTION_SILENT, ALERT, ALERT_HANDLED + } from './actionTypes.js' +import { GAME_STATES } from '../../constants.js' +import { spaceContent, corners } from 'game.js' + +const spaces = + [[corners[0], 'buy'], + ['January', 'buy'], + ['January', 'buy'], + ['January', 'buy'], + ['January', 'buy'], + ['February','buy'], + ['February', 'buy'], + ['February', 'buy'], + ['February', 'buy'], + ['March', 'buy'], + ['March', 'buy'], + ['March', 'buy'], + ['March', 'buy'], + ['April', 'buy'], + [corners[1], 'buy'], + ['April', 'none'], + ['April', 'none'], + ['May', 'none'], + ['May', 'none'], + ['May', 'hay'], + ['May', 'hay'], + ['June', 'hay'], + ['June', 'hay'], + ['June', 'cherry'], + ['June', 'cherry'], + [corners[2], 'cherry'], + ['July', 'hay'], + ['July', 'hay'], + ['July', 'hay'], + ['July', 'wheat'], + ['August', 'wheat'], + ['August', 'wheat'], + ['August', 'wheat'], + ['August', 'wheat'], + ['September', 'hay'], + ['September', 'hay'], + ['September', 'cows'], + [corners[3], 'cows'], + ['September', 'cows'], + ['October', 'cows'], + ['October', 'hay'], + ['October', 'hay'], + ['October', 'apple'], + ['November', 'apple'], + ['November', 'apple'], + ['November', 'apple'], + ['November', 'corn'], + ['December', 'corn'], + ['December', 'corn']] + .map((s, i) => { + return { month: s[0], description: spaceContent[i], + type: s[1], key: i, players: [] }}); + +const initialState = { + player: { cash: 5000, + lastCash: 5000, + debt: 5000, + spaces, + state: GAME_STATES.turnEnded, + assets: { hay: 10, grain: 10, fruit: 0, cows: 0, harvester: 0, tractor: 0 }, + color: '', + name: '', + ridges: { ridge1: 0, ridge2: 0, ridge3: 0, ridge4: 0 }, + space: 0, + trade: {} + }, + game: { auditThreshold: 250000, + calledAudit: false, + currentPlayer: '', + messages: [], + otherPlayers: [], + state: GAME_STATES.preTurn, + turn: 0, + oldMessages: [] }, + ui: { card: { type: 'no-card', contents: '', total: 0 }, + cards: [], + action: false, + actionValue: null, + nextAction: false, + nextActionValue: null, + actionChangeHandled: true, + alert: false, + alertHandled: false }, + spaces: spaces, + space: null, + // message panel dimenions + mpDims: { mouseX: 0, mouseY: 0, + minWidth: 0, minHeight: 0, maxWidth: 0, maxHeight: 0, + padding: 8 }, + profile: false, + profileTurns: 500 +} + +export default function(state = initialState, action) { + switch (action.type) { + case UPDATE_GAME: + return { ...state, game: { ...state.game, ...action.update }}; + case UPDATE_PLAYER: + return { ...state, player: action.update }; + case GAME_STATE: + return { ...state, game: { ...state.game, state: action.state } }; + case SET_SELECTED_CARD: + return { ...state, ui: { ...state.ui, card: action.card }}; + case SET_CARDS: + return { ...state, ui: { ...state.ui, cards: action.cards }}; + case MOVE_PLAYER: + return { ...state, spaces: state.spaces + .map((item, index) => { + if (index === action.newSpace && + item.players.indexOf(action.player) === -1) { + return { ...item, players: [...item.players, action.player]}; + } else if (index === action.oldSpace) { + return { ...item, + players: item.players + .filter(x => x !== action.player) }; + } + return item; + }) + }; + case SET_OLD_MESSAGES: + return { ...state, oldMessages: action.messages }; + case MESSAGE_PANEL_SPACE: + return { ...state, space: action.space }; + case MP_MOUSE: + return { ...state, mpDims: { ...state.mpDims, + mouseX: action.mouseX, mouseY: action.mouseY }}; + case SET_MP_DIMS: + return { ...state, mpDims: { ...state.mpDims, + minWidth: action.minWidth, + minHeight: action.minHeight, + maxWidth: action.maxWidth, + maxHeight: action.maxHeight }}; + case SET_NEXT_ACTION: + return { ...state, ui: { ...state.ui, nextAction: action.action, + nextActionValue: action.value }}; + case NEXT_UI_ACTION: + return { ...state, ui: { ...state.ui, action: state.ui.nextAction, + actionValue: state.ui.nextActionValue, + actionChangeHandled: !state.ui.nextAction }}; + case NEXT_UI_ACTION_SILENT: // don't set actionChangeHandled + return { ...state, ui: { ...state.ui, action: state.ui.nextAction, + actionValue: state.ui.nextActionValue }}; + case MARK_ACTION_CHANGE_HANDLED: + return { ...state, ui: { ...state.ui, actionChangeHandled: true }}; + case ALERT: + return { ...state, ui: { ...state.ui, + alert: action.value, + alertHandled: action.value === false ? true : false }}; + case ALERT_HANDLED: + return { ...state, ui: { ...state.ui, alertHandled: true }}; + default: + return state; + } +} diff --git a/src/components/join-game/JoinGame.jsx b/src/components/join-game/JoinGame.jsx new file mode 100644 index 0000000..d9abfba --- /dev/null +++ b/src/components/join-game/JoinGame.jsx @@ -0,0 +1,97 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React, { Fragment } from 'react' +import { connect } from 'react-redux' + +import { GroupBox, Row, Col, Button } from '../widgets.jsx' + +import { startOrJoinGame } from '../start/actions.js' + +import NewGame from '../new-game/NewGame.jsx' + +const JoinGameScreens = { list: 'list', details: 'details' }; + +class JoinGame extends React.Component { + constructor(props) { + super(props); + this.state = { + screen: JoinGameScreens.list, + game: null + }; + } + + handleClickGame = game => { + this.setState({ screen: JoinGameScreens.details, + game: game + }); + } + + handleBack = e => { + this.setState({ screen: JoinGameScreens.list }); + } + + handleJoinAsExisting = e => { + this.props.startOrJoinGame({ type: 'join-as-existing', + playerName: e.target.text, + gameName: this.state.game.name }); + } + + render() { + return ( + <GroupBox title='Join Game'> + <Row> + <Col width='12'> + {this.state.screen === JoinGameScreens.list ? + (<ul> + {this.props.games + .map((g, i) => + (<li key={i}> + <a href='#' onClick={() => this.handleClickGame(g)}>{g.name}</a> + </li>))} + </ul>) + : (<Fragment> + <p><a href="#" onClick={this.handleBack}>back to games</a></p> + <h3><b>Game:</b> {this.state.game.name}</h3> + <h4>Join as existing player:</h4> + <ul> + {this.state.game.players.map((p, i) => + (<li key={i}> + <a href='#' onClick={this.handleJoinAsExisting}> + {p} + </a> + </li>))} + </ul> + <NewGame colors={this.state.game.colors} + button={'Join'} + showGameName={false} + gameName={this.state.game.name} + type={'join-game'} + title={'Join as New Player'} /> + </Fragment>)} + </Col> + </Row> + </GroupBox> + ); + } +} + +export default connect( + state => state.start.start, + { startOrJoinGame } +)(JoinGame) diff --git a/src/components/new-game/NewGame.jsx b/src/components/new-game/NewGame.jsx new file mode 100644 index 0000000..0dc72be --- /dev/null +++ b/src/components/new-game/NewGame.jsx @@ -0,0 +1,104 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React, { Fragment } from 'react' +import { connect } from 'react-redux' + +import { GroupBox, Row, Col, Button } from '../widgets.jsx' + +import { startOrJoinGame } from '../start/actions.js' + +class NewGame extends React.Component { + constructor(props) { + super(props); + this.state = { + playerName: '', + checkedColor: props.colors[0], + gameName: props.gameName || '' + }; + } + + handleInputChange = e => { + const target = e.target, + value = target.type === 'checkbox' ? target.name : target.value, + name = target.type === 'checkbox' ? 'checkedColor' : target.name; + + this.setState({ + [name]: value + }); + } + + handleSubmit = e => { + e.preventDefault(); + this.props.startOrJoinGame(Object.assign({ type: this.props.type }, this.state)); + } + + render() { + let playerNameInput; + return ( + <GroupBox title={this.props.title}> + <form onSubmit={this.handleSubmit}> + <Row> + <Col width='12'> + <label>Your Name + <input type='text' name='playerName' + value={this.state.playerName} + onChange={this.handleInputChange} /> + </label> + </Col> + </Row> + <Row> + <Col width='12'> + <label>Your Color</label> + {this.props.colors + .map(c => + (<label key={c} className={'player player-selectable player-' + c + (this.state.checkedColor === c ? ' player-selected' : '')}> + <input type='checkbox' + checked={this.state.checkedColor === c} + onChange={this.handleInputChange} + name={c} /> + </label>)) + } + <br /><br /> + </Col> + </Row> + {this.props.showGameName && ( + <Row> + <Col width='12'> + <label>Game Name + <input type='text' name='gameName' value={this.state.gameName} + onChange={this.handleInputChange} /> + </label> + </Col> + </Row> + )} + <Row> + <Col width='12'> + <Button type='submit'>{this.props.button} Game</Button> + </Col> + </Row> + </form> + </GroupBox> + ); + } +} + +export default connect( + null, + { startOrJoinGame } +)(NewGame) diff --git a/src/components/start/Start.jsx b/src/components/start/Start.jsx new file mode 100644 index 0000000..2966516 --- /dev/null +++ b/src/components/start/Start.jsx @@ -0,0 +1,94 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React, { Fragment } from 'react' +import { connect } from 'react-redux' + +import { GroupBox, Row, Col } from '../widgets.jsx' +import { startOrJoinGame } from './actions.js' + +const JoinGameScreens = { list: 'list', details: 'details' }; + +class JoinGameComp extends React.Component { + constructor(props) { + super(props); + this.state = { + screen: JoinGameScreens.list, + game: null + }; + } + + handleClickGame = game => { + this.setState({ screen: JoinGameScreens.details, + game: game + }); + } + + handleBack = e => { + this.setState({ screen: JoinGameScreens.list }); + } + + handleJoinAsExisting = e => { + this.props.startOrJoinGame({ type: 'join-as-existing', + playerName: e.target.text, + gameName: this.state.game.name }); + } + + render() { + return ( + <GroupBox title='Join Game'> + <Row> + <Col width='12'> + {this.state.screen === JoinGameScreens.list ? + (<ul> + {this.props.games + .map((g, i) => + (<li key={i}> + <a href='#' onClick={() => this.handleClickGame(g)}>{g.name}</a> + </li>))} + </ul>) + : (<Fragment> + <p><a href="#" onClick={this.handleBack}>back to games</a></p> + <h3><b>Game:</b> {this.state.game.name}</h3> + <h4>Join as existing player:</h4> + <ul> + {this.state.game.players.map((p, i) => + (<li key={i}> + <a href='#' onClick={this.handleJoinAsExisting}> + {p} + </a> + </li>))} + </ul> + <NewGame colors={this.state.game.colors} + button={'Join'} + showGameName={false} + gameName={this.state.game.name} + type={'join-game'} + title={'Join as New Player'} /> + </Fragment>)} + </Col> + </Row> + </GroupBox> + ); + } +} + +const JoinGame = connect( + null, + { startOrJoinGame } +)(JoinGameComp) diff --git a/src/components/start/actionTypes.js b/src/components/start/actionTypes.js new file mode 100644 index 0000000..4a7e716 --- /dev/null +++ b/src/components/start/actionTypes.js @@ -0,0 +1,20 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +export const SET_START_GAMES = 'set-start-games'; +export const START_OR_JOIN_GAME = 'start-or-join-game'; diff --git a/src/components/start/actions.js b/src/components/start/actions.js new file mode 100644 index 0000000..a2b64ac --- /dev/null +++ b/src/components/start/actions.js @@ -0,0 +1,31 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import { SET_START_GAMES, START_OR_JOIN_GAME } from './actionTypes.js' + +export { setStartGames, startOrJoinGame } + +function setStartGames(games) { + return { type: SET_START_GAMES, + games }; +} + +function startOrJoinGame(msg) { + return { type: START_OR_JOIN_GAME, + msg }; +} diff --git a/src/components/start/reducers.js b/src/components/start/reducers.js new file mode 100644 index 0000000..86c9fc9 --- /dev/null +++ b/src/components/start/reducers.js @@ -0,0 +1,37 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import { SET_START_GAMES, START_OR_JOIN_GAME } from './actionTypes.js' +import { SCREENS } from '../../constants.js' + +const initialState = { + start: { games: [] }, + msg: null +}; + +export default function(state = initialState, action) { + switch (action.type) { + case SET_START_GAMES: + return { ...state, start: { ...state.start, games: action.games }}; + case START_OR_JOIN_GAME: + return { ...state, msg: action.msg }; + default: + return state; + } +} + diff --git a/src/components/tractor/Tractor.jsx b/src/components/tractor/Tractor.jsx new file mode 100644 index 0000000..aa7c384 --- /dev/null +++ b/src/components/tractor/Tractor.jsx @@ -0,0 +1,40 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import TractorImg from './../../../assets/img/tractor-offset.svg' +import TractorAndSpikesImg from './../../../assets/img/tractor-offset-and-spikes.svg' +import TractorSpikesImg from './../../../assets/img/tractor-spikes-offset.svg' + +import React, { Fragment } from 'react' + +export default class Tractor extends React.Component { + render() { + return ( + <Fragment> + <div className={'tractor ' + (this.props.className ? this.props.className : '')}> + <img src={this.props.spikes ? TractorImg : TractorAndSpikesImg} /> + </div> + {this.props.spikes ? ( + <div className='tractor spikes'> + <img src={TractorSpikesImg} /> + </div> + ) : (<Fragment />)} + </Fragment> + ); + } +} diff --git a/src/components/welcome/Welcome.jsx b/src/components/welcome/Welcome.jsx new file mode 100644 index 0000000..c047a8a --- /dev/null +++ b/src/components/welcome/Welcome.jsx @@ -0,0 +1,46 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React, { Fragment } from 'react' +import { connect } from 'react-redux' + +import { GroupBox, Row, Col, Button } from '../widgets.jsx' +import { start } from '../app/actions.js' + + +class Welcome extends React.Component { + render() { + return ( + <Fragment> + <div className='intro-text'> + <div className='game-card'> + Your ancestors were farmers on one of the first transports to Alpha Centuari{`'`}s Proxima b. The growing season is short and harsh but the colonists depend on you for their food. Are you up to the challenge? + </div> + </div> + <Button size='large' className='shadow intro' onClick={this.props.start}> + Begin + </Button> + </Fragment> + ); + } +} + +export default connect( + state => state, + { start } +)(Welcome) diff --git a/src/components/widgets.jsx b/src/components/widgets.jsx new file mode 100644 index 0000000..2bf6007 --- /dev/null +++ b/src/components/widgets.jsx @@ -0,0 +1,69 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +import React, { Fragment } from 'react' + +export { GroupBox, Row, Col, Button } + +class GroupBox extends React.Component { + render() { + return ( + <div className='panel card'> + {this.props.title ? + (<div className='card-divider'> + {this.props.title} + </div>) : (<Fragment />)} + <div className={'card-section ' + this.props.className ? this.props.className : ''}> + {this.props.children} + </div> + </div> + ); + } +} + +class Row extends React.Component { + render() { + return (<div className={'grid-x full-width ' + + (this.props.collapse ? 'collapse' : '') + ' ' + + (this.props.className ? this.props.className : '')} > + {this.props.children}</div>); + } +} + +class Col extends React.Component { + render() { + return ( + <div className={'cell small-' + this.props.width}> + {this.props.children} + </div> + ); + } +} + +class Button extends React.Component { + render() { + return ( + <button className={'button ' + (this.props.size ? this.props.size : '') + + ' ' + (this.props.className ? this.props.className : '')} + type={this.props.type || 'button'} + onClick={this.props.onClick} > + {this.props.children} + </button> + ); + } +} diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..4cea1a6 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,34 @@ +// Copyright 2020 Thomas Hintz +// +// This file is part of the Alpha Centauri Farming project. +// +// The Alpha Centauri Farming project is free software: you can +// redistribute it and/or modify it under the terms of the GNU General +// Public License as published by the Free Software Foundation, either +// version 3 of the License, or (at your option) any later version. +// +// The Alpha Centauri Farming project is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Alpha Centauri Farming project. If not, see +// <https://www.gnu.org/licenses/>. + +export const SCREENS = { + intro: 'intro', + start: 'start', + play: 'play', + newGame: 'new-game', + joinGame: 'join-game' +}; + +export const GAME_STATES = { preTurn: 'pre-turn', + midTurn: 'mid-turn', + turnEnded: 'turn-ended' }; +export const rootId = 'initial-element'; +export const messagePanelId = 'message-panel'; +export const ALERTS = { beginTurn: 'begin-turn', + otherPlayersTurn: 'other-players-turn', + raiseMoney: 'raise-money' } diff --git a/src/foundation/_global.scss b/src/foundation/_global.scss new file mode 100644 index 0000000..3210c8c --- /dev/null +++ b/src/foundation/_global.scss @@ -0,0 +1,244 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +// sass-lint:disable no-color-literals, no-qualifying-elements + +//// +/// @group global +//// + +@import 'util/util'; + +/// Font size attribute applied to `<html>` and `<body>`. We use 100% by default so the value is inherited from the user's browser settings. +/// @type Number +$global-font-size: 100% !default; + +/// Global width of your site. Used by the grid to determine row width. +/// @type Number +$global-width: rem-calc(1200) !default; + +/// Default line height for all type. `$global-lineheight` is 24px while `$global-font-size` is 16px +/// @type Number +$global-lineheight: 1.5 !default; + +/// Colors used for buttons, callouts, links, etc. There must always be a color called `primary`. +/// @type Map +$foundation-palette: ( + primary: #1779ba, + secondary: #767676, + success: #3adb76, + warning: #ffae00, + alert: #cc4b37, +) !default; + +/// Color used for light gray UI items. +/// @type Color +$light-gray: #e6e6e6 !default; + +/// Color used for medium gray UI items. +/// @type Color +$medium-gray: #cacaca !default; + +/// Color used for dark gray UI items. +/// @type Color +$dark-gray: #8a8a8a !default; + +/// Color used for black ui items. +/// @type Color +$black: #0a0a0a !default; + +/// Color used for white ui items. +/// @type Color +$white: #fefefe !default; + +/// Background color of the body. +/// @type Color +$body-background: $white !default; + +/// Text color of the body. +/// @type Color +$body-font-color: $black !default; + +/// Font stack of the body. +/// @type List +$body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif !default; + +/// Set to `true` to enable antialiased type, using the `-webkit-font-smoothing` and `-moz-osx-font-smoothing` CSS properties. +/// @type Boolean +$body-antialiased: true !default; + +/// Global value used for margin on components. +/// @type Number +$global-margin: 1rem !default; + +/// Global value used for padding on components. +/// @type Number +$global-padding: 1rem !default; + +/// Global value used for positioning on components. +/// @type Number +$global-position: 1rem !default; + +/// Global font weight used for normal type. +/// @type Keyword | Number +$global-weight-normal: normal !default; + +/// Global font weight used for bold type. +/// @type Keyword | Number +$global-weight-bold: bold !default; + +/// Global value used for all elements that have a border radius. +/// @type Number +$global-radius: 0 !default; + +/// Global value used for all menu styles. Can be overwritten at individual menu component level. +/// @type Number +$global-menu-padding: 0.7rem 1rem !default; + +/// Global value used for all menu styles. Nested margin for submenu. +$global-menu-nested-margin: 1rem !default; + +/// Sets the text direction of the CSS. Can be either `ltr` or `rtl`. +/// @type Keyword +$global-text-direction: ltr !default; + +/// Enables flexbox for components that support it. +/// @type Boolean +$global-flexbox: true !default; + +/// Enabled responsive breakpoints for prototypes if applicable +/// @type Boolean +$global-prototype-breakpoints: false !default; + +/// Button cursor's value, `auto` by default +/// @type Keyword +$global-button-cursor: auto !default; + +@if not map-has-key($foundation-palette, primary) { + @error 'In $foundation-palette, you must have a color named "primary".'; +} + +// Internal variables used for text direction +$global-left: if($global-text-direction == rtl, right, left); +$global-right: if($global-text-direction == rtl, left, right); + +// Internal variable that contains the flex justifying options +$-zf-flex-justify: -zf-flex-justify($global-text-direction); + +/// Global tolerance for color pick contrast. +/// @type Number +$global-color-pick-contrast-tolerance: 0 !default; + +// Internal variables used for colors +@include add-foundation-colors; + +@mixin foundation-global-styles { + @include foundation-normalize; + + // These styles are applied to a <meta> tag, which is read by the Foundation JavaScript + .foundation-mq { + font-family: '#{-zf-bp-serialize($breakpoints)}'; + } + + html { + box-sizing: border-box; + font-size: $global-font-size; + } + + // Set box-sizing globally to handle padding and border widths + *, + *::before, + *::after { + box-sizing: inherit; + } + + // Default body styles + body { + margin: 0; + padding: 0; + + background: $body-background; + + font-family: $body-font-family; + font-weight: $global-weight-normal; + line-height: $global-lineheight; + color: $body-font-color; + + @if ($body-antialiased) { + -webkit-font-smoothing: antialiased; // sass-lint:disable-line no-vendor-prefixes + -moz-osx-font-smoothing: grayscale; // sass-lint:disable-line no-vendor-prefixes + } + } + + img { + // Get rid of gap under images by making them display: inline-block; by default + display: inline-block; + vertical-align: middle; + + // Grid defaults to get images and embeds to work properly + max-width: 100%; + height: auto; + -ms-interpolation-mode: bicubic; + } + + // Make sure textarea takes on height automatically + textarea { + height: auto; + min-height: 50px; + border-radius: $global-radius; + } + + // Make select elements are 100% width by default + select { + box-sizing: border-box; + width: 100%; + border-radius: $global-radius; + } + + // Styles Google Maps and MapQuest embeds properly + // sass-lint:disable-line no-ids + .map_canvas, + .mqa-display { + img, + embed, + object { + max-width: none !important; + } + } + + // Reset <button> styles created by most browsers + button { + @include disable-mouse-outline; + padding: 0; + appearance: none; + border: 0; + border-radius: $global-radius; + background: transparent; + line-height: 1; + cursor: $global-button-cursor; + } + + // Prevent text overflow on pre + pre { + overflow: auto; + } + + // Make reset inherit font-family instead of settings sans-serif + button, + input, + optgroup, + select, + textarea { + font-family: inherit; + } + + // Internal classes to show/hide elements in JavaScript + .is-visible { + display: block !important; + } + + .is-hidden { + display: none !important; + } +} diff --git a/src/foundation/_settings.scss b/src/foundation/_settings.scss new file mode 100644 index 0000000..8b59e9e --- /dev/null +++ b/src/foundation/_settings.scss @@ -0,0 +1,896 @@ +// Foundation for Sites Settings +// ----------------------------- +// +// Table of Contents: +// +// 1. Global +// 2. Breakpoints +// 3. The Grid +// 4. Base Typography +// 5. Typography Helpers +// 6. Abide +// 7. Accordion +// 8. Accordion Menu +// 9. Badge +// 10. Breadcrumbs +// 11. Button +// 12. Button Group +// 13. Callout +// 14. Card +// 15. Close Button +// 16. Drilldown +// 17. Dropdown +// 18. Dropdown Menu +// 19. Flexbox Utilities +// 20. Forms +// 21. Label +// 22. Media Object +// 23. Menu +// 24. Meter +// 25. Off-canvas +// 26. Orbit +// 27. Pagination +// 28. Progress Bar +// 29. Prototype Arrow +// 30. Prototype Border-Box +// 31. Prototype Border-None +// 32. Prototype Bordered +// 33. Prototype Display +// 34. Prototype Font-Styling +// 35. Prototype List-Style-Type +// 36. Prototype Overflow +// 37. Prototype Position +// 38. Prototype Rounded +// 39. Prototype Separator +// 40. Prototype Shadow +// 41. Prototype Sizing +// 42. Prototype Spacing +// 43. Prototype Text-Decoration +// 44. Prototype Text-Transformation +// 45. Prototype Text-Utilities +// 46. Responsive Embed +// 47. Reveal +// 48. Slider +// 49. Switch +// 50. Table +// 51. Tabs +// 52. Thumbnail +// 53. Title Bar +// 54. Tooltip +// 55. Top Bar +// 56. Xy Grid + +@import 'util/util'; + +// 1. Global +// --------- + +$global-font-size: 100%; +$global-width: rem-calc(1200); +$global-lineheight: 1.5; +$foundation-palette: ( + primary: #B34534, // #1779ba + secondary: #BF7B71, // #767676 + success: #3adb76, + warning: #ffae00, + alert: #cc4b37, +); +// other colors: #FF624A, #FFA496, #7F3125 +$light-gray: #e6e6e6; +$medium-gray: #cacaca; +$dark-gray: #8a8a8a; +$black: #0a0a0a; +$white: #fefefe; +$body-background: $white; +$body-font-color: $black; +$body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; +$body-antialiased: true; +$global-margin: 1rem; +$global-padding: 1rem; +$global-position: 1rem; +$global-weight-normal: normal; +$global-weight-bold: bold; +$global-radius: 0; +$global-menu-padding: 0.7rem 1rem; +$global-menu-nested-margin: 1rem; +$global-text-direction: ltr; +$global-flexbox: true; +$global-prototype-breakpoints: false; +$global-button-cursor: auto; +$global-color-pick-contrast-tolerance: 0; +$print-transparent-backgrounds: true; +$print-hrefs: true; + +@include add-foundation-colors; + +// 2. Breakpoints +// -------------- + +$breakpoints: ( + small: 0, + medium: 640px, + large: 1024px, + xlarge: 1200px, + xxlarge: 1440px, +); +$breakpoints-hidpi: ( + hidpi-1: 1, + hidpi-1-5: 1.5, + hidpi-2: 2, + retina: 2, + hidpi-3: 3 +); +$print-breakpoint: large; +$breakpoint-classes: (small medium large); + +// 3. The Grid +// ----------- + +$grid-row-width: $global-width; +$grid-column-count: 12; +$grid-column-gutter: ( + small: 20px, + medium: 30px, +); +$grid-column-align-edge: true; +$grid-column-alias: 'columns'; +$block-grid-max: 8; + +// 4. Base Typography +// ------------------ + +$header-font-family: $body-font-family; +$header-font-weight: $global-weight-normal; +$header-font-style: normal; +$font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace; +$header-color: inherit; +$header-lineheight: 1.4; +$header-margin-bottom: 0.5rem; +$header-styles: ( + small: ( + 'h1': ('font-size': 24), + 'h2': ('font-size': 20), + 'h3': ('font-size': 19), + 'h4': ('font-size': 18), + 'h5': ('font-size': 17), + 'h6': ('font-size': 16), + ), + medium: ( + 'h1': ('font-size': 48), + 'h2': ('font-size': 40), + 'h3': ('font-size': 31), + 'h4': ('font-size': 25), + 'h5': ('font-size': 20), + 'h6': ('font-size': 16), + ), +); +$header-text-rendering: optimizeLegibility; +$small-font-size: 80%; +$header-small-font-color: $medium-gray; +$paragraph-lineheight: 1.6; +$paragraph-margin-bottom: 1rem; +$paragraph-text-rendering: optimizeLegibility; +$enable-code-inline: true; +$anchor-color: $primary-color; +$anchor-color-hover: scale-color($anchor-color, $lightness: -14%); +$anchor-text-decoration: none; +$anchor-text-decoration-hover: none; +$hr-width: $global-width; +$hr-border: 1px solid $medium-gray; +$hr-margin: rem-calc(20) auto; +$list-lineheight: $paragraph-lineheight; +$list-margin-bottom: $paragraph-margin-bottom; +$list-style-type: disc; +$list-style-position: outside; +$list-side-margin: 1.25rem; +$list-nested-side-margin: 1.25rem; +$defnlist-margin-bottom: 1rem; +$defnlist-term-weight: $global-weight-bold; +$defnlist-term-margin-bottom: 0.3rem; +$blockquote-color: $dark-gray; +$blockquote-padding: rem-calc(9 20 0 19); +$blockquote-border: 1px solid $medium-gray; +$enable-cite-block: true; +$keystroke-font: $font-family-monospace; +$keystroke-color: $black; +$keystroke-background: $light-gray; +$keystroke-padding: rem-calc(2 4 0); +$keystroke-radius: $global-radius; +$abbr-underline: 1px dotted $black; + +// 5. Typography Helpers +// --------------------- + +$lead-font-size: $global-font-size * 1.25; +$lead-lineheight: 1.6; +$subheader-lineheight: 1.4; +$subheader-color: $dark-gray; +$subheader-font-weight: $global-weight-normal; +$subheader-margin-top: 0.2rem; +$subheader-margin-bottom: 0.5rem; +$stat-font-size: 2.5rem; +$cite-color: $dark-gray; +$cite-font-size: rem-calc(13); +$cite-pseudo-content: '\2014 \0020'; +$code-color: $black; +$code-font-family: $font-family-monospace; +$code-font-weight: $global-weight-normal; +$code-background: $light-gray; +$code-border: 1px solid $medium-gray; +$code-padding: rem-calc(2 5 1); +$code-block-padding: 1rem; +$code-block-margin-bottom: 1.5rem; + +// 6. Abide +// -------- + +$abide-inputs: true; +$abide-labels: true; +$input-background-invalid: get-color(alert); +$form-label-color-invalid: get-color(alert); +$input-error-color: get-color(alert); +$input-error-font-size: rem-calc(12); +$input-error-font-weight: $global-weight-bold; + +// 7. Accordion +// ------------ + +$accordion-background: $white; +$accordion-plusminus: true; +$accordion-plus-content: '\002B'; +$accordion-minus-content: '\2013'; +$accordion-title-font-size: rem-calc(12); +$accordion-item-color: $primary-color; +$accordion-item-background-hover: $light-gray; +$accordion-item-padding: 1.25rem 1rem; +$accordion-content-background: $white; +$accordion-content-border: 1px solid $light-gray; +$accordion-content-color: $body-font-color; +$accordion-content-padding: 1rem; + +// 8. Accordion Menu +// ----------------- + +$accordionmenu-padding: $global-menu-padding; +$accordionmenu-nested-margin: $global-menu-nested-margin; +$accordionmenu-submenu-padding: $accordionmenu-padding; +$accordionmenu-arrows: true; +$accordionmenu-arrow-color: $primary-color; +$accordionmenu-item-background: null; +$accordionmenu-border: null; +$accordionmenu-submenu-toggle-background: null; +$accordion-submenu-toggle-border: $accordionmenu-border; +$accordionmenu-submenu-toggle-width: 40px; +$accordionmenu-submenu-toggle-height: $accordionmenu-submenu-toggle-width; +$accordionmenu-arrow-size: 6px; + +// 9. Badge +// -------- + +$badge-background: $primary-color; +$badge-color: $white; +$badge-color-alt: $black; +$badge-palette: $foundation-palette; +$badge-padding: 0.3em; +$badge-minwidth: 2.1em; +$badge-font-size: 0.6rem; + +// 10. Breadcrumbs +// --------------- + +$breadcrumbs-margin: 0 0 $global-margin 0; +$breadcrumbs-item-font-size: rem-calc(11); +$breadcrumbs-item-color: $primary-color; +$breadcrumbs-item-color-current: $black; +$breadcrumbs-item-color-disabled: $medium-gray; +$breadcrumbs-item-margin: 0.75rem; +$breadcrumbs-item-uppercase: true; +$breadcrumbs-item-separator: true; +$breadcrumbs-item-separator-item: '/'; +$breadcrumbs-item-separator-item-rtl: '\\'; +$breadcrumbs-item-separator-color: $medium-gray; + +// 11. Button +// ---------- + +$button-font-family: inherit; +$button-font-weight: null; +$button-padding: 0.85em 1em; +$button-margin: 0 0 $global-margin 0; +$button-fill: solid; +$button-background: $primary-color; +$button-background-hover: scale-color($button-background, $lightness: -15%); +$button-color: $white; +$button-color-alt: $black; +$button-radius: $global-radius; +$button-border: 1px solid transparent; +$button-hollow-border-width: 1px; +$button-sizes: ( + tiny: 0.6rem, + small: 0.75rem, + default: 0.9rem, + large: 1.25rem, +); +$button-palette: $foundation-palette; +$button-opacity-disabled: 0.25; +$button-background-hover-lightness: -20%; +$button-hollow-hover-lightness: -50%; +$button-transition: background-color 0.25s ease-out, color 0.25s ease-out; +$button-responsive-expanded: false; + +// 12. Button Group +// ---------------- + +$buttongroup-margin: 1rem; +$buttongroup-spacing: 1px; +$buttongroup-child-selector: '.button'; +$buttongroup-expand-max: 6; +$buttongroup-radius-on-each: true; + +// 13. Callout +// ----------- + +$callout-background: $white; +$callout-background-fade: 85%; +$callout-border: 1px solid rgba($black, 0.25); +$callout-margin: 0 0 1rem 0; +$callout-sizes: ( + small: 0.5rem, + default: 1rem, + large: 3rem, +); +$callout-font-color: $body-font-color; +$callout-font-color-alt: $body-background; +$callout-radius: $global-radius; +$callout-link-tint: 30%; + +// 14. Card +// -------- + +$card-background: $white; +$card-font-color: $body-font-color; +$card-divider-background: $light-gray; +$card-border: 1px solid $light-gray; +$card-shadow: none; +$card-border-radius: $global-radius; +$card-padding: $global-padding; +$card-margin-bottom: $global-margin; + +// 15. Close Button +// ---------------- + +$closebutton-position: right top; +$closebutton-z-index: 10; +$closebutton-default-size: medium; +$closebutton-offset-horizontal: ( + small: 0.66rem, + medium: 1rem, +); +$closebutton-offset-vertical: ( + small: 0.33em, + medium: 0.5rem, +); +$closebutton-size: ( + small: 1.5em, + medium: 2em, +); +$closebutton-lineheight: 1; +$closebutton-color: $dark-gray; +$closebutton-color-hover: $black; + +// 16. Drilldown +// ------------- + +$drilldown-transition: transform 0.15s linear; +$drilldown-arrows: true; +$drilldown-padding: $global-menu-padding; +$drilldown-nested-margin: 0; +$drilldown-background: $white; +$drilldown-submenu-padding: $drilldown-padding; +$drilldown-submenu-background: $white; +$drilldown-arrow-color: $primary-color; +$drilldown-arrow-size: 6px; + +// 17. Dropdown +// ------------ + +$dropdown-padding: 1rem; +$dropdown-background: $body-background; +$dropdown-border: 1px solid $medium-gray; +$dropdown-font-size: 1rem; +$dropdown-width: 300px; +$dropdown-radius: $global-radius; +$dropdown-sizes: ( + tiny: 100px, + small: 200px, + large: 400px, +); + +// 18. Dropdown Menu +// ----------------- + +$dropdownmenu-arrows: true; +$dropdownmenu-arrow-color: $anchor-color; +$dropdownmenu-arrow-size: 6px; +$dropdownmenu-arrow-padding: 1.5rem; +$dropdownmenu-min-width: 200px; +$dropdownmenu-background: null; +$dropdownmenu-submenu-background: $white; +$dropdownmenu-padding: $global-menu-padding; +$dropdownmenu-nested-margin: 0; +$dropdownmenu-submenu-padding: $dropdownmenu-padding; +$dropdownmenu-border: 1px solid $medium-gray; +$dropdown-menu-item-color-active: get-color(primary); +$dropdown-menu-item-background-active: transparent; + +// 19. Flexbox Utilities +// --------------------- + +$flex-source-ordering-count: 6; +$flexbox-responsive-breakpoints: true; + +// 20. Forms +// --------- + +$fieldset-border: 1px solid $medium-gray; +$fieldset-padding: rem-calc(20); +$fieldset-margin: rem-calc(18 0); +$legend-padding: rem-calc(0 3); +$form-spacing: rem-calc(16); +$helptext-color: $black; +$helptext-font-size: rem-calc(13); +$helptext-font-style: italic; +$input-prefix-color: $black; +$input-prefix-background: $light-gray; +$input-prefix-border: 1px solid $medium-gray; +$input-prefix-padding: 1rem; +$form-label-color: $black; +$form-label-font-size: rem-calc(14); +$form-label-font-weight: $global-weight-normal; +$form-label-line-height: 1.8; +$select-background: $white; +$select-triangle-color: $dark-gray; +$select-radius: $global-radius; +$input-color: $black; +$input-placeholder-color: $medium-gray; +$input-font-family: inherit; +$input-font-size: rem-calc(16); +$input-font-weight: $global-weight-normal; +$input-line-height: $global-lineheight; +$input-background: $white; +$input-background-focus: $white; +$input-background-disabled: $light-gray; +$input-border: 1px solid $medium-gray; +$input-border-focus: 1px solid $dark-gray; +$input-padding: $form-spacing / 2; +$input-shadow: inset 0 1px 2px rgba($black, 0.1); +$input-shadow-focus: 0 0 5px $medium-gray; +$input-cursor-disabled: not-allowed; +$input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out; +$input-number-spinners: true; +$input-radius: $global-radius; +$form-button-radius: $global-radius; + +// 21. Label +// --------- + +$label-background: $primary-color; +$label-color: $white; +$label-color-alt: $black; +$label-palette: $foundation-palette; +$label-font-size: 0.8rem; +$label-padding: 0.33333rem 0.5rem; +$label-radius: $global-radius; + +// 22. Media Object +// ---------------- + +$mediaobject-margin-bottom: $global-margin; +$mediaobject-section-padding: $global-padding; +$mediaobject-image-width-stacked: 100%; + +// 23. Menu +// -------- + +$menu-margin: 0; +$menu-nested-margin: $global-menu-nested-margin; +$menu-items-padding: $global-menu-padding; +$menu-simple-margin: 1rem; +$menu-item-color-active: $white; +$menu-item-color-alt-active: $black; +$menu-item-background-active: get-color(primary); +$menu-icon-spacing: 0.25rem; +$menu-state-back-compat: true; +$menu-centered-back-compat: true; +$menu-icons-back-compat: true; + +// 24. Meter +// --------- + +$meter-height: 1rem; +$meter-radius: $global-radius; +$meter-background: $medium-gray; +$meter-fill-good: $success-color; +$meter-fill-medium: $warning-color; +$meter-fill-bad: $alert-color; + +// 25. Off-canvas +// -------------- + +$offcanvas-sizes: ( + small: 250px, +); +$offcanvas-vertical-sizes: ( + small: 250px, +); +$offcanvas-background: $light-gray; +$offcanvas-shadow: 0 0 10px rgba($black, 0.7); +$offcanvas-inner-shadow-size: 20px; +$offcanvas-inner-shadow-color: rgba($black, 0.25); +$offcanvas-overlay-zindex: 11; +$offcanvas-push-zindex: 12; +$offcanvas-overlap-zindex: 13; +$offcanvas-reveal-zindex: 12; +$offcanvas-transition-length: 0.5s; +$offcanvas-transition-timing: ease; +$offcanvas-fixed-reveal: true; +$offcanvas-exit-background: rgba($white, 0.25); +$maincontent-class: 'off-canvas-content'; + +// 26. Orbit +// --------- + +$orbit-bullet-background: $medium-gray; +$orbit-bullet-background-active: $dark-gray; +$orbit-bullet-diameter: 1.2rem; +$orbit-bullet-margin: 0.1rem; +$orbit-bullet-margin-top: 0.8rem; +$orbit-bullet-margin-bottom: 0.8rem; +$orbit-caption-background: rgba($black, 0.5); +$orbit-caption-padding: 1rem; +$orbit-control-background-hover: rgba($black, 0.5); +$orbit-control-padding: 1rem; +$orbit-control-zindex: 10; + +// 27. Pagination +// -------------- + +$pagination-font-size: rem-calc(14); +$pagination-margin-bottom: $global-margin; +$pagination-item-color: $black; +$pagination-item-padding: rem-calc(3 10); +$pagination-item-spacing: rem-calc(1); +$pagination-radius: $global-radius; +$pagination-item-background-hover: $light-gray; +$pagination-item-background-current: $primary-color; +$pagination-item-color-current: $white; +$pagination-item-color-disabled: $medium-gray; +$pagination-ellipsis-color: $black; +$pagination-mobile-items: false; +$pagination-mobile-current-item: false; +$pagination-arrows: true; +$pagination-arrow-previous: '\00AB'; +$pagination-arrow-next: '\00BB'; + +// 28. Progress Bar +// ---------------- + +$progress-height: 1rem; +$progress-background: $medium-gray; +$progress-margin-bottom: $global-margin; +$progress-meter-background: $primary-color; +$progress-radius: $global-radius; + +// 29. Prototype Arrow +// ------------------- + +$prototype-arrow-directions: ( + down, + up, + right, + left +); +$prototype-arrow-size: 0.4375rem; +$prototype-arrow-color: $black; + +// 30. Prototype Border-Box +// ------------------------ + +$prototype-border-box-breakpoints: $global-prototype-breakpoints; + +// 31. Prototype Border-None +// ------------------------- + +$prototype-border-none-breakpoints: $global-prototype-breakpoints; + +// 32. Prototype Bordered +// ---------------------- + +$prototype-bordered-breakpoints: $global-prototype-breakpoints; +$prototype-border-width: rem-calc(1); +$prototype-border-type: solid; +$prototype-border-color: $medium-gray; + +// 33. Prototype Display +// --------------------- + +$prototype-display-breakpoints: $global-prototype-breakpoints; +$prototype-display: ( + inline, + inline-block, + block, + table, + table-cell +); + +// 34. Prototype Font-Styling +// -------------------------- + +$prototype-font-breakpoints: $global-prototype-breakpoints; +$prototype-wide-letter-spacing: rem-calc(4); +$prototype-font-normal: $global-weight-normal; +$prototype-font-bold: $global-weight-bold; + +// 35. Prototype List-Style-Type +// ----------------------------- + +$prototype-list-breakpoints: $global-prototype-breakpoints; +$prototype-style-type-unordered: ( + disc, + circle, + square +); +$prototype-style-type-ordered: ( + decimal, + lower-alpha, + lower-latin, + lower-roman, + upper-alpha, + upper-latin, + upper-roman +); + +// 36. Prototype Overflow +// ---------------------- + +$prototype-overflow-breakpoints: $global-prototype-breakpoints; +$prototype-overflow: ( + visible, + hidden, + scroll +); + +// 37. Prototype Position +// ---------------------- + +$prototype-position-breakpoints: $global-prototype-breakpoints; +$prototype-position: ( + static, + relative, + absolute, + fixed +); +$prototype-position-z-index: 975; + +// 38. Prototype Rounded +// --------------------- + +$prototype-rounded-breakpoints: $global-prototype-breakpoints; +$prototype-border-radius: rem-calc(3); + +// 39. Prototype Separator +// ----------------------- + +$prototype-separator-breakpoints: $global-prototype-breakpoints; +$prototype-separator-align: center; +$prototype-separator-height: rem-calc(2); +$prototype-separator-width: 3rem; +$prototype-separator-background: $primary-color; +$prototype-separator-margin-top: $global-margin; + +// 40. Prototype Shadow +// -------------------- + +$prototype-shadow-breakpoints: $global-prototype-breakpoints; +$prototype-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), + 0 2px 10px 0 rgba(0,0,0,.12); + +// 41. Prototype Sizing +// -------------------- + +$prototype-sizing-breakpoints: $global-prototype-breakpoints; +$prototype-sizing: ( + width, + height +); +$prototype-sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100% +); + +// 42. Prototype Spacing +// --------------------- + +$prototype-spacing-breakpoints: $global-prototype-breakpoints; +$prototype-spacers-count: 3; + +// 43. Prototype Text-Decoration +// ----------------------------- + +$prototype-decoration-breakpoints: $global-prototype-breakpoints; +$prototype-text-decoration: ( + overline, + underline, + line-through, +); + +// 44. Prototype Text-Transformation +// --------------------------------- + +$prototype-transformation-breakpoints: $global-prototype-breakpoints; +$prototype-text-transformation: ( + lowercase, + uppercase, + capitalize +); + +// 45. Prototype Text-Utilities +// ---------------------------- + +$prototype-utilities-breakpoints: $global-prototype-breakpoints; +$prototype-text-overflow: ellipsis; + +// 46. Responsive Embed +// -------------------- + +$responsive-embed-margin-bottom: rem-calc(16); +$responsive-embed-ratios: ( + default: 4 by 3, + widescreen: 16 by 9, +); + +// 47. Reveal +// ---------- + +$reveal-background: $white; +$reveal-width: 600px; +$reveal-max-width: $global-width; +$reveal-padding: $global-padding; +$reveal-border: 1px solid $medium-gray; +$reveal-radius: $global-radius; +$reveal-zindex: 1005; +$reveal-overlay-background: rgba($black, 0.45); + +// 48. Slider +// ---------- + +$slider-width-vertical: 0.5rem; +$slider-transition: all 0.2s ease-in-out; +$slider-height: 0.5rem; +$slider-background: $light-gray; +$slider-fill-background: $medium-gray; +$slider-handle-height: 1.4rem; +$slider-handle-width: 1.4rem; +$slider-handle-background: $primary-color; +$slider-opacity-disabled: 0.25; +$slider-radius: $global-radius; + +// 49. Switch +// ---------- + +$switch-background: $medium-gray; +$switch-background-active: $primary-color; +$switch-height: 2rem; +$switch-height-tiny: 1.5rem; +$switch-height-small: 1.75rem; +$switch-height-large: 2.5rem; +$switch-radius: $global-radius; +$switch-margin: $global-margin; +$switch-paddle-background: $white; +$switch-paddle-offset: 0.25rem; +$switch-paddle-radius: $global-radius; +$switch-paddle-transition: all 0.25s ease-out; +$switch-opacity-disabled: .5; +$switch-cursor-disabled: not-allowed; + +// 50. Table +// --------- + +$table-background: $white; +$table-color-scale: 5%; +$table-border: 1px solid smart-scale($table-background, $table-color-scale); +$table-padding: rem-calc(8 10 10); +$table-hover-scale: 2%; +$table-row-hover: darken($table-background, $table-hover-scale); +$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale); +$table-is-striped: true; +$table-striped-background: smart-scale($table-background, $table-color-scale); +$table-stripe: even; +$table-head-background: smart-scale($table-background, $table-color-scale / 2); +$table-head-row-hover: darken($table-head-background, $table-hover-scale); +$table-foot-background: smart-scale($table-background, $table-color-scale); +$table-foot-row-hover: darken($table-foot-background, $table-hover-scale); +$table-head-font-color: $body-font-color; +$table-foot-font-color: $body-font-color; +$show-header-for-stacked: false; +$table-stack-breakpoint: medium; + +// 51. Tabs +// -------- + +$tab-margin: 0; +$tab-background: $white; +$tab-color: $primary-color; +$tab-background-active: $light-gray; +$tab-active-color: $primary-color; +$tab-item-font-size: rem-calc(12); +$tab-item-background-hover: $white; +$tab-item-padding: 1.25rem 1.5rem; +$tab-content-background: $white; +$tab-content-border: $light-gray; +$tab-content-color: $body-font-color; +$tab-content-padding: 1rem; + +// 52. Thumbnail +// ------------- + +$thumbnail-border: 4px solid $white; +$thumbnail-margin-bottom: $global-margin; +$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2); +$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); +$thumbnail-transition: box-shadow 200ms ease-out; +$thumbnail-radius: $global-radius; + +// 53. Title Bar +// ------------- + +$titlebar-background: $black; +$titlebar-color: $white; +$titlebar-padding: 0.5rem; +$titlebar-text-font-weight: bold; +$titlebar-icon-color: $white; +$titlebar-icon-color-hover: $medium-gray; +$titlebar-icon-spacing: 0.25rem; + +// 54. Tooltip +// ----------- + +$has-tip-cursor: help; +$has-tip-font-weight: $global-weight-bold; +$has-tip-border-bottom: dotted 1px $dark-gray; +$tooltip-background-color: $black; +$tooltip-color: $white; +$tooltip-padding: 0.75rem; +$tooltip-max-width: 10rem; +$tooltip-font-size: $small-font-size; +$tooltip-pip-width: 0.75rem; +$tooltip-pip-height: $tooltip-pip-width * 0.866; +$tooltip-radius: $global-radius; + +// 55. Top Bar +// ----------- + +$topbar-padding: 0.5rem; +$topbar-background: $light-gray; +$topbar-submenu-background: $topbar-background; +$topbar-title-spacing: 0.5rem 1rem 0.5rem 0; +$topbar-input-width: 200px; +$topbar-unstack-breakpoint: medium; + +// 56. Xy Grid +// ----------- + +$xy-grid: true; +$grid-container: $global-width; +$grid-columns: 12; +$grid-margin-gutters: ( + small: 20px, + medium: 30px +); +$grid-padding-gutters: $grid-margin-gutters; +$grid-container-padding: $grid-padding-gutters; +$grid-container-max: $global-width; +$xy-block-grid-max: 8; + diff --git a/src/foundation/components/_accordion-menu.scss b/src/foundation/components/_accordion-menu.scss new file mode 100644 index 0000000..1e05469 --- /dev/null +++ b/src/foundation/components/_accordion-menu.scss @@ -0,0 +1,174 @@ +//// +/// @group accordion-menu +//// + +/// Sets accordion menu padding. +/// @type Number +$accordionmenu-padding: $global-menu-padding !default; + +/// Sets accordion menu nested margin +/// @type Number +$accordionmenu-nested-margin: $global-menu-nested-margin !default; + +/// Sets accordion menu submenu padding. +/// @type Number +$accordionmenu-submenu-padding: $accordionmenu-padding !default; + +/// Sets if accordion menus have the default arrow styles. +/// @type Boolean +$accordionmenu-arrows: true !default; + +/// Sets accordion menu arrow color if arrow is used. +/// @type Color +$accordionmenu-arrow-color: $primary-color !default; + +/// Sets accordion menu item padding. +/// @type Color +$accordionmenu-item-background: null !default; + +/// Sets accordion menu item border. +/// @type Color +$accordionmenu-border: null !default; + +/// Sets accordion menu item padding. +/// @type Color +$accordionmenu-submenu-toggle-background: null !default; + +/// Sets accordion menu item padding. +/// @type List +$accordion-submenu-toggle-border: $accordionmenu-border !default; + +/// Sets accordion menu submenu toggle background width. +/// @type Number +$accordionmenu-submenu-toggle-width: 40px !default; + +/// Sets accordion menu submenu toggle background height. +/// @type Number +$accordionmenu-submenu-toggle-height: $accordionmenu-submenu-toggle-width !default; + +/// Sets accordion menu arrow size if arrow is used. +/// @type Length +$accordionmenu-arrow-size: 6px !default; + +@mixin zf-accordion-menu-left-right-arrows { + .is-accordion-submenu-parent:not(.has-submenu-toggle) > a { + position: relative; + + &::after { + @include css-triangle($accordionmenu-arrow-size, $accordionmenu-arrow-color, down); + position: absolute; + top: 50%; + margin-top: -1 * ($accordionmenu-arrow-size / 2); + #{$global-right}: 1rem; + } + } + + &.align-left .is-accordion-submenu-parent > a::after { + right: 1rem; + left: auto; + } + + &.align-right .is-accordion-submenu-parent > a::after { + right: auto; + left: 1rem; + } +} +@mixin foundation-accordion-menu { + + .accordion-menu { + @if $accordionmenu-border { + border-bottom: $accordionmenu-border; + } + + li { + @if $accordionmenu-border { + border-top: $accordionmenu-border; + border-right: $accordionmenu-border; + border-left: $accordionmenu-border; + } + width: 100%; + } + + a { + @if $accordionmenu-item-background { + background: $accordionmenu-item-background; + } + padding: $accordionmenu-padding; + } + + .is-accordion-submenu a { + padding: $accordionmenu-submenu-padding; + } + + .nested.is-accordion-submenu { + @include menu-nested($accordionmenu-nested-margin); + } + + &.align-#{$global-right} { + .nested.is-accordion-submenu { + @include menu-nested($accordionmenu-nested-margin, right); + } + } + + @if $accordionmenu-arrows { + @include zf-accordion-menu-left-right-arrows; + + .is-accordion-submenu-parent[aria-expanded='true'] > a::after { + transform: rotate(180deg); + transform-origin: 50% 50%; + } + } + } + + .is-accordion-submenu li { + @if $accordionmenu-border { + border-right: 0; + border-left: 0; + } + } + + .is-accordion-submenu-parent { + position: relative; + } + + .has-submenu-toggle > a { + margin-#{$global-right}: $accordionmenu-submenu-toggle-width; + } + + // Submenu toggle + .submenu-toggle { + position: absolute; + top: 0; + #{$global-right}: 0; + + width: $accordionmenu-submenu-toggle-width; + height: $accordionmenu-submenu-toggle-height; + + cursor: pointer; + + border-#{$global-left}: $accordion-submenu-toggle-border; + + @if $accordionmenu-submenu-toggle-background { + background: $accordionmenu-submenu-toggle-background; + } + + // Add the arrow to the toggle + &::after { + @include css-triangle(6px, $accordionmenu-arrow-color, down); + + top: 0; + bottom: 0; + margin: auto; + } + } + + // Rotate the arrow when menu is open + .submenu-toggle[aria-expanded='true']::after { + transform: scaleY(-1); + transform-origin: 50% 50%; + } + + .submenu-toggle-text { + @include element-invisible; + } +} diff --git a/src/foundation/components/_accordion.scss b/src/foundation/components/_accordion.scss new file mode 100644 index 0000000..b7c0c58 --- /dev/null +++ b/src/foundation/components/_accordion.scss @@ -0,0 +1,164 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group accordion +//// + +/// Default background color of an accordion group. +/// @type Color +$accordion-background: $white !default; + +/// If `true`, adds plus and minus icons to the side of each accordion title. +/// @type Boolean +$accordion-plusminus: true !default; + +/// Content for the plus icon when `$accordion-plusminus` is `true` +/// @type String +$accordion-plus-content: '\002B' !default; + +/// Content for the minus icon when `$accordion-plusminus` is `true` +/// @type String +$accordion-minus-content: '\2013' !default; + +/// Font size of accordion titles. +/// @type Number +$accordion-title-font-size: rem-calc(12) !default; + +/// Default text color for items in a Menu. +/// @type Color +$accordion-item-color: $primary-color !default; + +/// Default background color on hover for items in a Menu. +/// @type Color +$accordion-item-background-hover: $light-gray !default; + +/// Default padding of an accordion item. +/// @type Number | List +$accordion-item-padding: 1.25rem 1rem !default; + +/// Default background color of tab content. +/// @type Color +$accordion-content-background: $white !default; + +/// Default border color of tab content. +/// @type Color +$accordion-content-border: 1px solid $light-gray !default; + +/// Default text color of tab content. +/// @type Color +$accordion-content-color: $body-font-color !default; + +/// Default padding for tab content. +/// @type Number | List +$accordion-content-padding: 1rem !default; + +/// Adds styles for an accordion container. Apply this to the same element that gets `data-accordion`. +@mixin accordion-container ( + $background: $accordion-background +) { + margin-#{$global-left}: 0; + background: $background; + list-style-type: none; + + &[disabled] { + .accordion-title { + cursor: not-allowed; + } + } +} + +/// Adds styles for the accordion item. Apply this to the list item within an accordion ul. +@mixin accordion-item { + &:first-child > :first-child { + border-radius: $global-radius $global-radius 0 0; + } + + &:last-child > :last-child { + border-radius: 0 0 $global-radius $global-radius; + } +} + +/// Adds styles for the title of an accordion item. Apply this to the link within an accordion item. +@mixin accordion-title ( + $padding: $accordion-item-padding, + $font-size: $accordion-title-font-size, + $color: $accordion-item-color, + $border: $accordion-content-border, + $background-hover: $accordion-item-background-hover +) { + position: relative; + display: block; + padding: $padding; + + border: $border; + border-bottom: 0; + + font-size: $font-size; + line-height: 1; + color: $color; + + :last-child:not(.is-active) > & { + border-bottom: $border; + border-radius: 0 0 $global-radius $global-radius; + } + + &:hover, + &:focus { + background-color: $background-hover; + } + + @if $accordion-plusminus { + &::before { + position: absolute; + top: 50%; + #{$global-right}: 1rem; + margin-top: -0.5rem; + content: $accordion-plus-content; + } + + .is-active > &::before { + content: $accordion-minus-content; + } + } +} + +/// Adds styles for accordion content. Apply this to the content pane below an accordion item's title. +@mixin accordion-content ( + $padding: $accordion-content-padding, + $border: $accordion-content-border, + $background: $accordion-content-background, + $color: $accordion-content-color +) { + display: none; + padding: $padding; + + border: $border; + border-bottom: 0; + background-color: $background; + + color: $color; + + :last-child > &:last-child { + border-bottom: $border; + } +} + +@mixin foundation-accordion { + .accordion { + @include accordion-container; + } + + .accordion-item { + @include accordion-item; + } + + .accordion-title { + @include accordion-title; + } + + .accordion-content { + @include accordion-content; + } +} diff --git a/src/foundation/components/_badge.scss b/src/foundation/components/_badge.scss new file mode 100644 index 0000000..3d5b6ba --- /dev/null +++ b/src/foundation/components/_badge.scss @@ -0,0 +1,63 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group badge +//// + +/// Default background color for badges. +/// @type Color +$badge-background: $primary-color !default; + +/// Default text color for badges. +/// @type Color +$badge-color: $white !default; + +/// Alternate text color for badges. +/// @type Color +$badge-color-alt: $black !default; + +/// Coloring classes. A map of classes to output in your CSS, like `.secondary`, `.success`, and so on. +/// @type Map +$badge-palette: $foundation-palette !default; + +/// Default padding inside badges. +/// @type Number +$badge-padding: 0.3em !default; + +/// Minimum width of a badge. +/// @type Number +$badge-minwidth: 2.1em !default; + +/// Default font size for badges. +/// @type Number +$badge-font-size: 0.6rem !default; + +/// Generates the base styles for a badge. +@mixin badge { + display: inline-block; + min-width: $badge-minwidth; + padding: $badge-padding; + + border-radius: 50%; + + font-size: $badge-font-size; + text-align: center; +} + +@mixin foundation-badge { + .badge { + @include badge; + + background: $badge-background; + color: $badge-color; + + @each $name, $color in $badge-palette { + &.#{$name} { + background: $color; + color: color-pick-contrast($color, ($badge-color, $badge-color-alt)); + } + } + } +} diff --git a/src/foundation/components/_breadcrumbs.scss b/src/foundation/components/_breadcrumbs.scss new file mode 100644 index 0000000..e48cc0b --- /dev/null +++ b/src/foundation/components/_breadcrumbs.scss @@ -0,0 +1,119 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group breadcrumbs +//// + +/// Margin around a breadcrumbs container. +/// @type Number +$breadcrumbs-margin: 0 0 $global-margin 0 !default; + +/// Font size of breadcrumb links. +/// @type Number +$breadcrumbs-item-font-size: rem-calc(11) !default; + +/// Color of breadcrumb links. +/// @type Color +$breadcrumbs-item-color: $primary-color !default; + +/// Color of the active breadcrumb link. +/// @type Color +$breadcrumbs-item-color-current: $black !default; + +/// Opacity of disabled breadcrumb links. +/// @type Number +$breadcrumbs-item-color-disabled: $medium-gray !default; + +/// Margin between breadcrumb items. +/// @type Number +$breadcrumbs-item-margin: 0.75rem !default; + +/// If `true`, makes breadcrumb links uppercase. +/// @type Boolean +$breadcrumbs-item-uppercase: true !default; + +/// If `true`, adds a seperator between breadcrumb links. +/// @type Boolean +$breadcrumbs-item-separator: true !default; + +// If it exists $breadcrumbs-item-slash is used to build $breadcrumbs-item-separator. See the documentation. +@if variable-exists(breadcrumbs-item-slash) { + $breadcrumbs-item-separator: $breadcrumbs-item-slash; +} + +/// Used character for the breadcrumb separator. +/// @type Content +$breadcrumbs-item-separator-item: '/' !default; + +/// Used character for the breadcrumb separator in rtl mode. +/// @type Content +$breadcrumbs-item-separator-item-rtl: '\\' !default; + +/// Color of breadcrumb item. +/// @type Color +$breadcrumbs-item-separator-color: $medium-gray !default; + +// If it exists $breadcrumbs-item-slash-color is used to build $breadcrumbs-item-separator-color. See the documentation. +@if variable-exists(breadcrumbs-item-slash-color) { + $breadcrumbs-item-separator-color: $breadcrumbs-item-slash-color; +} + +/// Adds styles for a breadcrumbs container, along with the styles for the `<li>` and `<a>` elements inside of it. +@mixin breadcrumbs-container { + @include clearfix; + margin: $breadcrumbs-margin; + list-style: none; + + // Item wrapper + li { + float: #{$global-left}; + + font-size: $breadcrumbs-item-font-size; + color: $breadcrumbs-item-color-current; + cursor: default; + + @if $breadcrumbs-item-uppercase { + text-transform: uppercase; + } + + @if $breadcrumbs-item-separator { + // Need to escape the backslash + $separator: if($global-text-direction == 'ltr', $breadcrumbs-item-separator-item, $breadcrumbs-item-separator-item-rtl); + + &:not(:last-child) { + &::after { + position: relative; + margin: 0 $breadcrumbs-item-margin; + opacity: 1; + content: $separator; + color: $breadcrumbs-item-separator-color; + } + } + } + @else { + margin-#{$global-right}: $breadcrumbs-item-margin; + } + } + + // Page links + a { + color: $breadcrumbs-item-color; + + &:hover { + text-decoration: underline; + } + } +} + +@mixin foundation-breadcrumbs { + .breadcrumbs { + @include breadcrumbs-container; + + .disabled { + color: $breadcrumbs-item-color-disabled; + cursor: not-allowed; + } + } +} diff --git a/src/foundation/components/_button-group.scss b/src/foundation/components/_button-group.scss new file mode 100644 index 0000000..59f432f --- /dev/null +++ b/src/foundation/components/_button-group.scss @@ -0,0 +1,299 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group button-group +//// + +/// Margin for button groups. +/// @type Number +$buttongroup-margin: 1rem !default; + +/// Margin between buttons in a button group. +/// @type Number +$buttongroup-spacing: 1px !default; + +/// Selector for the buttons inside a button group. +/// @type String +$buttongroup-child-selector: '.button' !default; + +/// Maximum number of buttons that can be in an even-width button group. (Only needed when $global-flexbox: false;) +/// @type Number +$buttongroup-expand-max: 6 !default; + +/// Determines if $button-radius is applied to each button or the button group as a whole. Use $global-radius in _settings.scss to change radius. +/// @type Boolean +$buttongroup-radius-on-each: true !default; + +/// Add styles for a button group container. +/// @param {String} $child-selector [$buttongroup-child-selector] - Selector for the buttons inside a button group. +/// @param {Number} $spacing [$buttongroup-spacing] - Spacing between buttons in a button group. +@mixin button-group( + $child-selector: $buttongroup-child-selector, + $spacing: $buttongroup-spacing +) { + @include clearfix; + margin-bottom: $buttongroup-margin; + + @if $global-flexbox { + display: flex; + flex-wrap: nowrap; + align-items: stretch; + } + @else { + font-size: 0; + } + + #{$child-selector} { + margin: 0; + margin-#{$global-right}: $spacing; + margin-bottom: $spacing; + font-size: map-get($button-sizes, default); + + @if $global-flexbox { + flex: 0 0 auto; + } + + &:last-child { + margin-#{$global-right}: 0; + } + + @if not $buttongroup-radius-on-each { + border-radius: 0; + + &:first-child { + border-top-#{$global-left}-radius: $button-radius; + border-bottom-#{$global-left}-radius: $button-radius; + } + + &:last-child { + border-top-#{$global-right}-radius: $button-radius; + border-bottom-#{$global-right}-radius: $button-radius; + } + } + } +} + +/// Make buttons bonded without gap between them. Borders between buttons are merged +/// @param {String} $selector [$buttongroup-child-selector] - Selector for the buttons inside a button group. +@mixin button-group-no-gaps( + $selector: $buttongroup-child-selector, + $border-width: $button-hollow-border-width +) { + #{$selector} { + margin-#{$global-right}: rem-calc(-$border-width); + + + #{$selector} { + border-#{$global-left}-color: transparent; + } + } +} + +/// Creates a full-width button group, making each button equal width. +/// @param {String} $selector [$buttongroup-child-selector] - Selector for the buttons inside a button group. +/// @param {Number} $spacing [$buttongroup-spacing] - Spacing between buttons in a button group. +@mixin button-group-expand( + $selector: $buttongroup-child-selector, + $spacing: $buttongroup-spacing, + $count: null +) { + @if not $global-flexbox { + margin-#{$global-right}: -$spacing; + + &::before, + &::after { + display: none; + } + } + + #{$selector} { + @if $global-flexbox { + flex: 1 1 0px; // sass-lint:disable-line zero-unit + } + @else { + // One child + &:first-child { + &:last-child { + width: calc(100% - #{$spacing}); + } + } + + // Two or more childreen + @for $i from 2 through $buttongroup-expand-max { + &:first-child:nth-last-child(#{$i}) { + &, &:first-child:nth-last-child(#{$i}) ~ #{$selector} { + display: inline-block; + width: calc(#{percentage(1 / $i)} - #{$spacing}); + margin-#{$global-right}: $spacing; + + &:last-child { + margin-#{$global-right}: $spacing * -$buttongroup-expand-max; + } + } + } + } + } + } +} + +/// Stacks the buttons in a button group. +/// @param {String} $selector [$buttongroup-child-selector] - Selector for the buttons inside the button group. +@mixin button-group-stack( + $selector: $buttongroup-child-selector +) { + @if $global-flexbox { + flex-wrap: wrap; + } + + #{$selector} { + @if $global-flexbox { + flex: 0 0 100%; + } + @else { + width: 100%; + } + + &:last-child { + margin-bottom: 0; + } + + + @if not $buttongroup-radius-on-each { + border-radius: 0; + + &:first-child { + border-top-#{$global-left}-radius: $global-radius; + border-top-#{$global-right}-radius: $global-radius; + } + + &:last-child { + margin-bottom: 0; + border-bottom-#{$global-left}-radius: $global-radius; + border-bottom-#{$global-right}-radius: $global-radius; + } + } + + } +} + +/// Un-stacks the buttons in a button group. +/// @param {String} $selector [$buttongroup-child-selector] - Selector for the buttons inside the button group. +@mixin button-group-unstack( + $selector: $buttongroup-child-selector +) { + #{$selector} { + @if $global-flexbox { + flex: 0 0 auto; + } + @else { + width: auto; + } + margin-bottom: 0; + + @if not $buttongroup-radius-on-each { + &:first-child { + border-top-#{$global-left}-radius: $global-radius; + border-top-#{$global-right}-radius: 0; + border-bottom-#{$global-left}-radius: $global-radius; + } + + &:last-child { + border-top-#{$global-right}-radius: $global-radius; + border-bottom-#{$global-right}-radius: $global-radius; + border-bottom-#{$global-left}-radius: 0; + } + } + + } +} + +@mixin foundation-button-group { + .button-group { + @include button-group; + + // Sizes + @each $size, $value in map-remove($button-sizes, default) { + &.#{$size} #{$buttongroup-child-selector} { + font-size: $value; + } + } + + // Even-width Group + &.expanded { + @include button-group-expand; + } + + // Solid, hollow & clear styles + @each $filling in (solid hollow clear) { + $base-selector: if($button-fill == $filling, null, '.#{$filling}'); + + &#{$base-selector} { + // Do not generate button base styles for the default filling + @if($button-fill != $filling) { + #{$buttongroup-child-selector} { + @include button-fill($filling); + @include button-fill-style($filling); + } + } + + @each $name, $color in $button-palette { + $individual-selector: if($button-fill == $filling, null, ' #{$buttongroup-child-selector}.#{$name}'); + + &.#{$name} #{$buttongroup-child-selector}, #{$individual-selector} { + @include button-fill-style($filling, $color, auto, auto); + } + } + } + + } + + &.no-gaps { + @include button-group-no-gaps; + } + + &.stacked, + &.stacked-for-small, + &.stacked-for-medium { + @include button-group-stack; + + &.expanded { + @include button-group-expand; + } + } + + &.stacked-for-small { + @include breakpoint(medium) { + @include button-group-unstack; + } + } + + &.stacked-for-medium { + @include breakpoint(large) { + @include button-group-unstack; + } + } + + &.stacked-for-small.expanded { + @include breakpoint(small only) { + display: block; + + #{$buttongroup-child-selector} { + display: block; + margin-#{$global-right}: 0; + } + } + } + + &.stacked-for-medium.expanded { + @include breakpoint(medium down) { + display: block; + + #{$buttongroup-child-selector} { + display: block; + margin-#{$global-right}: 0; + } + } + } + } +} diff --git a/src/foundation/components/_button.scss b/src/foundation/components/_button.scss new file mode 100644 index 0000000..e79aab1 --- /dev/null +++ b/src/foundation/components/_button.scss @@ -0,0 +1,428 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group button +//// + +/// Font family for button elements. +/// @type Font +$button-font-family: inherit !default; + +/// Font weight for button elements. +/// Ignored if null (default) +/// @type Font-Weight +$button-font-weight: null !default; + +/// Padding inside buttons. +/// @type List +$button-padding: 0.85em 1em !default; + +/// Margin around buttons. +/// @type List +$button-margin: 0 0 $global-margin 0 !default; + +/// Default fill for buttons. Can either be `solid` or `hollow`. +/// @type Keyword +$button-fill: solid !default; + +/// Default background color for buttons. +/// @type Color +$button-background: $primary-color !default; + +/// Background color on hover for buttons. +/// @type Color +$button-background-hover: scale-color($button-background, $lightness: -15%) !default; + +/// Font color for buttons. +/// @type List +$button-color: $white !default; + +/// Alternative font color for buttons. +/// @type List +$button-color-alt: $black !default; + +/// Border radius for buttons, defaulted to global-radius. +/// @type Number +$button-radius: $global-radius !default; + +/// Border for buttons, transparent by default +/// @type List +$button-border: 1px solid transparent !default; + +/// Border width for hollow outline buttons +/// @type Number +$button-hollow-border-width: 1px !default; + +/// Sizes for buttons. +/// @type Map +$button-sizes: ( + tiny: 0.6rem, + small: 0.75rem, + default: 0.9rem, + large: 1.25rem, +) !default; + +/// Coloring classes. A map of classes to output in your CSS, like `.secondary`, `.success`, and so on. +/// @type Map +$button-palette: $foundation-palette !default; + +/// opacity for a disabled button. +/// @type List +$button-opacity-disabled: 0.25 !default; + +/// Background color lightness on hover for buttons. +/// @type Number +$button-background-hover-lightness: -20% !default; + +/// Color lightness on hover for hollow buttons. +/// @type Number +$button-hollow-hover-lightness: -50% !default; + +// Internal: flip from margin-right to margin-left for defaults +@if $global-text-direction == 'rtl' { + $button-margin: 0 0 $global-margin $global-margin !default; +} + +/// transitions for buttons. +/// @type List +$button-transition: background-color 0.25s ease-out, color 0.25s ease-out !default; + +/// Additional responsive classes for .expanded +/// @type Boolean +$button-responsive-expanded: false !default; + +// TODO: Document button-base() mixin +@mixin button-base { + @include disable-mouse-outline; + display: inline-block; + vertical-align: middle; + margin: $button-margin; + + @if (type-of($button-padding) == 'map') { + @each $size, $padding in $button-padding { + @include breakpoint($size) { + padding: $padding; + } + } + } + @else { + padding: $button-padding; + } + + border: $button-border; + border-radius: $button-radius; + transition: $button-transition; + font-family: $button-font-family; + font-size: map-get($button-sizes, default); + font-weight: $button-font-weight; + -webkit-appearance: none; // sass-lint:disable-line no-vendor-prefixes + line-height: 1; + text-align: center; + cursor: pointer; +} + +/// Expands a button to make it full-width. +/// @param {Boolean} $expand [true] - Set to `true` to enable the expand behavior. Set to `false` to reverse this behavior. +@mixin button-expand($expand: true) { + @if $expand { + display: block; + width: 100%; + margin-right: 0; + margin-left: 0; + } + @else { + display: inline-block; + width: auto; + margin: $button-margin; + } +} + +/// Sets the base styles of a hollow or clear button filling according to `$fill`. +/// See mixin `button-fill-style` for the filling styles. +/// @param {Keyword} $fill [$button-fill] - Type of filling between `hollow` and `clear`. `solid` has no effects. +@mixin button-fill( + $fill: $button-fill +) { + @if $fill == hollow { + @include button-hollow; + } + @else if $fill == clear { + @include button-clear; + } +} + +/// Sets the visual styles of a solid/hollow/clear button filling according to `$fill`. +/// See mixins `button-style`, `button-hollow-style` and `button-clear-style` for effects of visual styling parameters. +/// @param {Keyword} $fill [$button-fill] - Type of filling between `hollow` and `clear`. +/// @param {Color} $background [$button-background] - - +/// @param {Color} $background-hover [$button-background-hover] - - +/// @param {Color} $color [$button-color] - - +@mixin button-fill-style( + $fill: $button-fill, + $background: $button-background, + $background-hover: $button-background-hover, + $color: $button-color +) { + @if $fill == solid { + @include button-style($background, $background-hover, $color); + } + @else if $fill == hollow { + @include button-hollow-style($background); + } + @else if $fill == clear { + @include button-clear-style($background); + } +} + +/// Sets the visual style of a button. +/// @param {Color} $background [$button-background] - Background color of the button. +/// @param {Color} $background-hover [$button-background-hover] - Background color of the button on hover. Set to `auto` to have the mixin automatically generate a hover color. +/// @param {Color} $color [$button-color] - Text color of the button. Set to `auto` to automatically generate a color based on the background color. +@mixin button-style( + $background: $button-background, + $background-hover: $button-background-hover, + $color: $button-color, + $background-hover-lightness: $button-background-hover-lightness +) { + @if $color == auto { + $color: color-pick-contrast($background, ($button-color, $button-color-alt)); + } + + @if $background-hover == auto { + $background-hover: scale-color($background, $lightness: $background-hover-lightness); + } + + // Default and disabled states + &, + &.disabled, &[disabled], + &.disabled:hover, &[disabled]:hover, + &.disabled:focus, &[disabled]:focus { + background-color: $background; + color: $color; + } + + &:hover, &:focus { + background-color: $background-hover; + color: $color; + } +} + +/// Sets the base styles of a hollow button. +/// See mixin `button-hollow-style` for the filling styles. +@mixin button-hollow { + &, &.disabled, &[disabled] { + &, &:hover, &:focus { + background-color: transparent; + } + } +} + +/// Sets the visual style of a hollow button. +/// @param {Color} $color [$button-background] - Text and border color of the button. +/// @param {Color} $hover-lightness [$button-hollow-hover-lightness] - Color lightness on hover. +/// @param {Color} $border-width [$button-hollow-border-width] - Border width of the button. +@mixin button-hollow-style( + $color: $button-background, + $hover-lightness: $button-hollow-hover-lightness, + $border-width: $button-hollow-border-width +) { + $color-hover: scale-color($color, $lightness: $hover-lightness); + + // Default and disabled states + &, + &.disabled, &[disabled], + &.disabled:hover, &[disabled]:hover, + &.disabled:focus, &[disabled]:focus { + border: $border-width solid $color; + color: $color; + } + + &:hover, &:focus { + border-color: $color-hover; + color: $color-hover; + } +} + +/// Sets the base styles of a clear button. +/// See mixin `button-clear-style` for the filling styles. +@mixin button-clear { + &, &.disabled, &[disabled] { + &, &:hover, &:focus { + border-color: transparent; + background-color: transparent; + } + } +} + +/// Sets the visual style of a clear button. +/// @param {Color} $color [$button-background] - Text color of the button. +/// @param {Color} $hover-lightness [$button-hollow-hover-lightness] - Color lightness on hover. +@mixin button-clear-style( + $color: $button-background, + $hover-lightness: $button-hollow-hover-lightness +) { + $color-hover: scale-color($color, $lightness: $hover-lightness); + + // Default and disabled states + &, + &.disabled, &[disabled], + &.disabled:hover, &[disabled]:hover, + &.disabled:focus, &[disabled]:focus { + color: $color; + } + + &:hover, &:focus { + color: $color-hover; + } +} + +/// Adds disabled styles to a button by fading the element and reseting the cursor. +/// @param {Number} $opacity [$button-opacity-disabled] - Opacity of the disabled button. +@mixin button-disabled( + $opacity: $button-opacity-disabled +) { + opacity: $button-opacity-disabled; + cursor: not-allowed; +} + +/// Adds a dropdown arrow to a button. +/// @param {Number} $size [0.4em] - Size of the arrow. We recommend using an `em` value so the triangle scales when used inside different sizes of buttons. +/// @param {Color} $color [white] - Color of the arrow. +/// @param {Number} $offset [$button-padding] - Distance between the arrow and the text of the button. Defaults to whatever the right padding of a button is. +@mixin button-dropdown( + $size: 0.4em, + $color: $white, + $offset: get-side($button-padding, right) +) { + &::after { + @include css-triangle($size, $color, down); + position: relative; + top: 0.4em; // Aligns the arrow with the text of the button + + display: inline-block; + float: #{$global-right}; + margin-#{$global-left}: $offset; + } +} + +/// Adds all styles for a button. For more granular control over styles, use the individual button mixins. +/// @param {Boolean} $expand [false] - Set to `true` to make the button full-width. +/// @param {Color} $background [$button-background] - Background color of the button. +/// @param {Color} $background-hover [$button-background-hover] - Background color of the button on hover. Set to `auto` to have the mixin automatically generate a hover color. +/// @param {Color} $color [$button-color] - Text color of the button. Set to `auto` to automatically generate a color based on the background color. +/// @param {Keyword} $style [solid] - Set to `hollow` to create a hollow button. The color defined in `$background` will be used as the primary color of the button. +@mixin button( + $expand: false, + $background: $button-background, + $background-hover: $button-background-hover, + $color: $button-color, + $style: $button-fill +) { + @include button-base; + @include button-fill($style); + @include button-fill-style($style, $background, $background-hover, $color); + + @if $expand { + @include button-expand; + } +} + +@mixin foundation-button { + .button { + @include button($style: none); + + // Sizes + @each $size, $value in map-remove($button-sizes, default) { + &.#{$size} { + font-size: $value; + } + } + + &.expanded { @include button-expand; } + + @if $button-responsive-expanded { + @each $size in $breakpoint-classes { + @include breakpoint(#{$size} only) { + &.#{$size}-only-expanded { + @include button-expand; + } + } + @if $size != $-zf-zero-breakpoint { + @include breakpoint(#{$size} down) { + &.#{$size}-down-expanded { + @include button-expand; + } + } + + @include breakpoint(#{$size}) { + &.#{$size}-expanded { + @include button-expand; + } + } + } + } + } + + // Solid, hollow & clear styles + @each $filling in (solid hollow clear) { + $selector: if($button-fill == $filling, null, '.#{$filling}'); + + &#{$selector} { + @include button-fill($filling); + @include button-fill-style($filling); + + @each $name, $color in $button-palette { + &.#{$name} { + @include button-fill-style($filling, $color, auto, auto); + } + } + } + } + + // Disabled state + &.disabled, &[disabled] { + @include button-disabled; + } + + // Dropdown arrow + &.dropdown { + @include button-dropdown; + + @if $button-fill == hollow { + &::after { + border-top-color: $button-background; + } + } + + &.hollow, &.clear { + &::after { + border-top-color: $button-background; + } + + @each $name, $color in $button-palette { + &.#{$name} { + &::after { + border-top-color: $color; + } + } + } + } + } + + // Button with dropdown arrow only + &.arrow-only::after { + top: -0.1em; + float: none; + margin-#{$global-left}: 0; + } + } + + a.button { // sass-lint:disable-line no-qualifying-elements + &:hover, + &:focus { + text-decoration: none; + } + } +} diff --git a/src/foundation/components/_callout.scss b/src/foundation/components/_callout.scss new file mode 100644 index 0000000..979a5ad --- /dev/null +++ b/src/foundation/components/_callout.scss @@ -0,0 +1,108 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group callout +//// + +/// Default background color. +/// @type Color +$callout-background: $white !default; + +/// Default fade value for callout backgrounds. +/// @type Number +$callout-background-fade: 85% !default; + +/// Default border style for callouts. +/// @type List +$callout-border: 1px solid rgba($black, 0.25) !default; + +/// Default bottom margin for callouts. +/// @type Number +$callout-margin: 0 0 1rem 0 !default; + +/// Sizes for Callout paddings. +/// @type Map +$callout-sizes: ( + small: 0.5rem, + default: 1rem, + large: 3rem, +) !default; + +/// Default font color for callouts. +/// @type Color +$callout-font-color: $body-font-color !default; + +/// Default font color for callouts, if the callout has a dark background. +/// @type Color +$callout-font-color-alt: $body-background !default; + +/// Default border radius for callouts. +/// @type Color +$callout-radius: $global-radius !default; + +/// Amount to tint links used within colored panels. Set to `false` to disable this feature. +/// @type Number | Boolean +$callout-link-tint: 30% !default; + +/// Adds basic styles for a callout, including padding and margin. +@mixin callout-base() { + position: relative; + margin: $callout-margin; + padding: map-get($callout-sizes, default); + + border: $callout-border; + border-radius: $callout-radius; + + // Respect the padding, fool. + > :first-child { + margin-top: 0; + } + + > :last-child { + margin-bottom: 0; + } +} + +/// Generate quick styles for a callout using a single color as a baseline. +/// @param {Color} $color [$callout-background] - Color to use. +@mixin callout-style($color: $callout-background) { + $background: scale-color($color, $lightness: $callout-background-fade); + + background-color: $background; + color: color-pick-contrast($background, ($callout-font-color, $callout-font-color-alt)); +} + +@mixin callout-size($padding) { + padding-top: $padding; + padding-right: $padding; + padding-bottom: $padding; + padding-left: $padding; +} + + +/// Adds styles for a callout. +/// @param {Color} $color [$callout-background] - Color to use. +@mixin callout($color: $callout-background) { + @include callout-base; + @include callout-style($color); +} + +@mixin foundation-callout { + .callout { + @include callout; + + @each $name, $color in $foundation-palette { + &.#{$name} { + @include callout-style($color); + } + } + + @each $size, $padding in map-remove($callout-sizes, default) { + &.#{$size} { + @include callout-size($padding); + } + } + } +} diff --git a/src/foundation/components/_card.scss b/src/foundation/components/_card.scss new file mode 100644 index 0000000..a5bc78c --- /dev/null +++ b/src/foundation/components/_card.scss @@ -0,0 +1,129 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group card +//// + +/// Default background color. +/// @type Color +$card-background: $white !default; + +/// Default font color for cards. +/// @type Color +$card-font-color: $body-font-color !default; + +/// Default background. +/// @type Color +$card-divider-background: $light-gray !default; + +/// Default border style. +/// @type List +$card-border: 1px solid $light-gray !default; + +/// Default card shadow. +/// @type List +$card-shadow: none !default; + +/// Default border radius. +/// @type List +$card-border-radius: $global-radius !default; + +/// Default padding. +/// @type Number +$card-padding: $global-padding !default; + +/// Default bottom margin. +/// @type number +$card-margin-bottom: $global-margin !default; + +/// Adds styles for a card container. +/// @param {Color} $background - Background color of the card. +/// @param {Color} $color - font color of the card. +/// @param {Number} $margin - Bottom margin of the card. +/// @param {List} $border - Border around the card. +/// @param {List} $radius - border radius of the card. +/// @param {List} $shadow - box shadow of the card. +@mixin card-container( + $background: $card-background, + $color: $card-font-color, + $margin: $card-margin-bottom, + $border: $card-border, + $radius: $card-border-radius, + $shadow: $card-shadow +) { + @if $global-flexbox { + display: flex; + flex-direction: column; + flex-grow: 1; + } + + margin-bottom: $margin; + + border: $border; + border-radius: $radius; + + background: $background; + box-shadow: $shadow; + + overflow: hidden; + color: $color; + + & > :last-child { + margin-bottom: 0; + } +} + +/// Adds styles for a card divider. +@mixin card-divider( + $background: $card-divider-background, + $padding: $card-padding +) { + @if $global-flexbox { + display: flex; + flex: 0 1 auto; + } + + padding: $padding; + background: $background; + + & > :last-child { + margin-bottom: 0; + } +} + +/// Adds styles for a card section. +@mixin card-section( + $padding: $card-padding +) { + @if $global-flexbox { + flex: 1 0 auto; + } + + padding: $padding; + + & > :last-child { + margin-bottom: 0; + } +} + +@mixin foundation-card { + .card { + @include card-container; + } + + .card-divider { + @include card-divider; + } + + .card-section { + @include card-section; + } + + // For IE 11 - Flexbug + // https://github.com/philipwalton/flexbugs/issues/75 + .card-image { + min-height: 1px; + } +} diff --git a/src/foundation/components/_close-button.scss b/src/foundation/components/_close-button.scss new file mode 100644 index 0000000..abb2c79 --- /dev/null +++ b/src/foundation/components/_close-button.scss @@ -0,0 +1,127 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group close-button +//// + +/// Default position of the close button. The first value should be `right` or `left`, and the second value should be `top` or `bottom`. +/// @type List +$closebutton-position: right top !default; + +/// Default z-index for a close button. +/// @type Number +$closebutton-z-index: 10 !default; + +/// Button size to use as default +/// @type String +/// @see $closebutton-size +/// @see $closebutton-offset-horizontal +/// @see $closebutton-offset-vertical +$closebutton-default-size: medium !default; + +/// Right (or left) offset(s) for a close button. +/// @type Number|Map +$closebutton-offset-horizontal: ( + small: 0.66rem, + medium: 1rem, +) !default; + +/// Top (or bottom) offset(s) for a close button. +/// @type Number|Map +$closebutton-offset-vertical: ( + small: 0.33em, + medium: 0.5rem, +) !default; + +/// Size(s) of the close button. Used to generate sizing modifiers. +/// @type Number|Map +$closebutton-size: ( + small: 1.5em, + medium: 2em, +) !default; + +/// The line-height of the close button. It affects the spacing of the element. +/// @type Number +$closebutton-lineheight: 1 !default; + +/// Default color of the close button. +/// @type Color +$closebutton-color: $dark-gray !default; + +/// Default color of the close button when being hovered on. +/// @type Color +$closebutton-color-hover: $black !default; + + +/// Get the size and position for a close button. If the input value is a number, the number is returned. If the input value is a config map and the map has the key `$size`, the value is returned. +/// +/// @param {Number|Map} $value - A number or map that represents the size or position value(s) of the close button. +/// @param {Keyword} $size - The size of the close button to use. +/// +/// @return {Number} The given number or the value found in the map. +@function -zf-get-size-val($value, $size) { + // Check if the value is a number + @if type-of($value) == 'number' { + // If it is, just return the number + @return $value; + } + + // Check if the size name exists in the value map + @else if map-has-key($value, $size) { + // If it does, return the value + @return map-get($value, $size); + } +} + +/// Sets the size and position of a close button. +/// @param {Keyword} $size [medium] - The size to use. Set to `small` to create a small close button. The 'medium' values defined in `$closebutton-*` variables will be used as the default size and position of the close button. +@mixin close-button-size($size) { + $x: nth($closebutton-position, 1); + $y: nth($closebutton-position, 2); + + #{$x}: -zf-get-size-val($closebutton-offset-horizontal, $size); + #{$y}: -zf-get-size-val($closebutton-offset-vertical, $size); + font-size: -zf-get-size-val($closebutton-size, $size); + line-height: -zf-get-size-val($closebutton-lineheight, $size); +} + +/// Adds styles for a close button, using the styles in the settings variables. +@mixin close-button { + $x: nth($closebutton-position, 1); + $y: nth($closebutton-position, 2); + + @include disable-mouse-outline; + position: absolute; + z-index: $closebutton-z-index; + color: $closebutton-color; + cursor: pointer; + + &:hover, + &:focus { + color: $closebutton-color-hover; + } +} + +@mixin foundation-close-button { + .close-button { + @include close-button; + + // Generate a placeholder and a class for each size + @each $name, $size in $closebutton-size { + @at-root { + %zf-close-button--#{$name} { + @include close-button-size($name); + } + } + + &.#{$name} { + @extend %zf-close-button--#{$name}; + } + } + + // Use by default the placeholder of the default size + @extend %zf-close-button--#{$closebutton-default-size}; + } +} diff --git a/src/foundation/components/_drilldown.scss b/src/foundation/components/_drilldown.scss new file mode 100644 index 0000000..81d3993 --- /dev/null +++ b/src/foundation/components/_drilldown.scss @@ -0,0 +1,140 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group drilldown +//// + +/// Transition property to use for animating menus. +/// @type Transition +$drilldown-transition: transform 0.15s linear !default; + +/// Adds arrows to drilldown items with submenus, as well as the back button. +/// @type Boolean +$drilldown-arrows: true !default; + +/// Sets drilldown menu item padding. +/// @type Number +$drilldown-padding: $global-menu-padding !default; + +/// Sets drilldown menu nested margin +/// @type Number +$drilldown-nested-margin: 0 !default; + +/// Background color for drilldown top level items. +/// @type Color +$drilldown-background: $white !default; + +/// Sets drilldown menu item padding in the submenu. +/// @type Number +$drilldown-submenu-padding: $drilldown-padding !default; + +/// Background color for drilldown submenus. +/// @type Color +$drilldown-submenu-background: $white !default; + +/// Sets drilldown arrow color if arrow is used. +/// @type Color +$drilldown-arrow-color: $primary-color !default; + +/// Sets drilldown arrow size if arrow is used. +/// @type Length +$drilldown-arrow-size: 6px !default; + +@mixin zf-drilldown-left-right-arrows { + .is-drilldown-submenu-parent > a { + position: relative; + + &::after { + @include css-triangle($drilldown-arrow-size, $drilldown-arrow-color, $global-right); + position: absolute; + top: 50%; + margin-top: -1 * $drilldown-arrow-size; + #{$global-right}: 1rem; + } + } + + &.align-left .is-drilldown-submenu-parent > a::after { + @include css-triangle($dropdownmenu-arrow-size, $dropdownmenu-arrow-color, right); + right: 1rem; + left: auto; + } + + &.align-right .is-drilldown-submenu-parent > a::after { + @include css-triangle($dropdownmenu-arrow-size, $dropdownmenu-arrow-color, left); + right: auto; + left: 1rem; + } + +} + +@mixin foundation-drilldown-menu { + // Applied to the Menu container + .is-drilldown { + position: relative; + overflow: hidden; + + li { + display: block; + } + + &.animate-height { + transition: height 0.5s; + } + } + + // The top level <ul> + .drilldown { + a { + padding: $drilldown-padding; + background: $drilldown-background; + } + + // Applied to submenu <ul>s + .is-drilldown-submenu { + position: absolute; + top: 0; + #{$global-left}: 100%; + z-index: -1; + + width: 100%; + background: $drilldown-submenu-background; + transition: $drilldown-transition; + + &.is-active { + z-index: 1; + display: block; + transform: translateX(if($global-text-direction == ltr, -100%, 100%)); + } + + &.is-closing { + transform: translateX(if($global-text-direction == ltr, 100%, -100%)); + } + + // Submenu item padding + a { + padding: $drilldown-submenu-padding; + } + } + + .nested.is-drilldown-submenu { + @include menu-nested($drilldown-nested-margin); + } + + .drilldown-submenu-cover-previous { + min-height: 100%; + } + + @if $drilldown-arrows { + @include zf-drilldown-left-right-arrows; + + .js-drilldown-back > a::before { + @include css-triangle($drilldown-arrow-size, $drilldown-arrow-color, $global-left); + display: inline-block; + vertical-align: middle; + margin-#{$global-right}: 0.75rem; // Creates space between the arrow and the text + } + } + } +} diff --git a/src/foundation/components/_dropdown-menu.scss b/src/foundation/components/_dropdown-menu.scss new file mode 100644 index 0000000..d2d3480 --- /dev/null +++ b/src/foundation/components/_dropdown-menu.scss @@ -0,0 +1,279 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group dropdown-menu +//// + +/// Enables arrows for items with dropdown menus. +/// @type Boolean +$dropdownmenu-arrows: true !default; + +/// Sets dropdown menu arrow color if arrow is used. +/// @type Color +$dropdownmenu-arrow-color: $anchor-color !default; + +/// Sets dropdown menu arrow size if arrow is used. +/// @type Length +$dropdownmenu-arrow-size: 6px !default; + +/// Sets dropdown menu arrow padding for aligning the arrow correctly. +/// @type Length +$dropdownmenu-arrow-padding: 1.5rem !default; + +/// Minimum width of dropdown sub-menus. +/// @type Length +$dropdownmenu-min-width: 200px !default; + +/// Background color for top level items. +/// @type Color +$dropdownmenu-background: null !default; + +/// Background color for dropdowns. +/// @type Color +$dropdownmenu-submenu-background: $white !default; + +/// Padding for top level items. +/// @type Number +$dropdownmenu-padding: $global-menu-padding !default; + +/// Sets dropdown menu nested margin +/// @type Number +$dropdownmenu-nested-margin: 0 !default; + +/// Padding for sub-menu items. +/// @type Number +$dropdownmenu-submenu-padding: $dropdownmenu-padding !default; + +/// Border for dropdown sub-menus. +/// @type List +$dropdownmenu-border: 1px solid $medium-gray !default; + +// Border width for dropdown sub-menus. +// Used to adjust top margin of a sub-menu if a border is used. +// @type Length +$dropdownmenu-border-width: nth($dropdownmenu-border, 1); + +/// Text color of an active dropdown menu item. Explicit override for menu defaults +/// @type Color +$dropdown-menu-item-color-active: get-color(primary) !default; + +/// Background color of an active dropdown menu item. Explicit override for menu defaults +/// @type Color +$dropdown-menu-item-background-active: transparent !default; + +@mixin zf-dropdown-left-right-arrows { + > a::after { + #{$global-right}: 14px; + } + + &.opens-left > a::after { + @include css-triangle($dropdownmenu-arrow-size, $dropdownmenu-arrow-color, left); + right: auto; + left: 5px; + } + + &.opens-right > a::after { + @include css-triangle($dropdownmenu-arrow-size, $dropdownmenu-arrow-color, right); + } +} + +@mixin dropdown-menu-direction($dir: horizontal) { + @if $dir == horizontal { + > li.opens-left { // sass-lint:disable-line no-qualifying-elements + > .is-dropdown-submenu { + top: 100%; + right: 0; + left: auto; + } + } + + > li.opens-right { // sass-lint:disable-line no-qualifying-elements + > .is-dropdown-submenu { + top: 100%; + right: auto; + left: 0; + } + } + + @if $dropdownmenu-arrows { + > li.is-dropdown-submenu-parent > a { // sass-lint:disable-line no-qualifying-elements + position: relative; + padding-#{$global-right}: $dropdownmenu-arrow-padding; + } + + > li.is-dropdown-submenu-parent > a::after { // sass-lint:disable-line no-qualifying-elements + @include css-triangle($dropdownmenu-arrow-size, $dropdownmenu-arrow-color, down); + #{$global-right}: 5px; + #{$global-left}: auto; + margin-top: -1 * ($dropdownmenu-arrow-size / 2); + } + } + } + @else if $dir == vertical { + > li { + .is-dropdown-submenu { + top: 0; + } + + &.opens-left { + > .is-dropdown-submenu { + top: 0; + right: 100%; + left: auto; + } + } + + &.opens-right { + > .is-dropdown-submenu { + right: auto; + left: 100%; + } + } + + @if $dropdownmenu-arrows { + @include zf-dropdown-left-right-arrows; + } + } + } + @else { + @warn 'The direction used for dropdown-menu-direction() must be horizontal or vertical.'; + } +} + +@mixin foundation-dropdown-menu { + .dropdown.menu { + @include dropdown-menu-direction(horizontal); + + a { + @include disable-mouse-outline; + } + + // Top-level item + > li > a { + background: $dropdownmenu-background; + padding: $dropdownmenu-padding; + } + + // Top-level item active state + > li.is-active > a { + background: $dropdown-menu-item-background-active; + color: $dropdown-menu-item-color-active; + } + + .no-js & ul { + display: none; + } + + .nested.is-dropdown-submenu { + @include menu-nested($dropdownmenu-nested-margin); + } + + &.vertical { + @include dropdown-menu-direction(vertical); + } + + @each $size in $breakpoint-classes { + @if $size != $-zf-zero-breakpoint { + @include breakpoint($size) { + &.#{$size}-horizontal { + @include dropdown-menu-direction(horizontal); + } + + &.#{$size}-vertical { + @include dropdown-menu-direction(vertical); + } + } + } + } + + &.align-right { + .is-dropdown-submenu.first-sub { + top: 100%; + right: 0; + left: auto; + } + } + } + + .is-dropdown-menu.vertical { + width: 100px; + + &.align-right { + float: right; + } + } + + .is-dropdown-submenu-parent { + position: relative; + + a::after { + position: absolute; + top: 50%; + #{$global-right}: 5px; + #{$global-left}: auto; + margin-top: -1 * $dropdownmenu-arrow-size; + } + + &.opens-inner > .is-dropdown-submenu { + + top: 100%; + @if $global-text-direction == 'rtl' { + right: auto; + } + @else { + left: auto; + } + } + + &.opens-left > .is-dropdown-submenu { + right: 100%; + left: auto; + } + + &.opens-right > .is-dropdown-submenu { + right: auto; + left: 100%; + } + } + + .is-dropdown-submenu { + position: absolute; + top: 0; + #{$global-left}: 100%; + z-index: 1; + + display: none; + min-width: $dropdownmenu-min-width; + + border: $dropdownmenu-border; + background: $dropdownmenu-submenu-background; + + .dropdown & a { + padding: $dropdownmenu-submenu-padding; + } + + .is-dropdown-submenu-parent { + @if $dropdownmenu-arrows { + @include zf-dropdown-left-right-arrows; + } + } + + @if (type-of($dropdownmenu-border-width) == 'number') { + .is-dropdown-submenu { + margin-top: (-$dropdownmenu-border-width); + } + } + + > li { + width: 100%; + } + + // [TODO] Cut back specificity + //&:not(.js-dropdown-nohover) > .is-dropdown-submenu-parent:hover > &, // why is this line needed? Opening is handled by JS and this causes some ugly flickering when the sub is re-positioned automatically... + &.js-dropdown-active { + display: block; + } + } +} diff --git a/src/foundation/components/_dropdown.scss b/src/foundation/components/_dropdown.scss new file mode 100644 index 0000000..53afead --- /dev/null +++ b/src/foundation/components/_dropdown.scss @@ -0,0 +1,82 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group dropdown +//// + +/// Padding for dropdown panes. +/// @type List +$dropdown-padding: 1rem !default; + +/// Background for dropdown panes. +/// @type Color +$dropdown-background: $body-background !default; + +/// Border for dropdown panes. +/// @type List +$dropdown-border: 1px solid $medium-gray !default; + +/// Font size for dropdown panes. +/// @type List +$dropdown-font-size: 1rem !default; + +/// Width for dropdown panes. +/// @type Number +$dropdown-width: 300px !default; + +/// Border radius dropdown panes. +/// @type Number +$dropdown-radius: $global-radius !default; + +/// Sizes for dropdown panes. Each size is a CSS class you can apply. +/// @type Map +$dropdown-sizes: ( + tiny: 100px, + small: 200px, + large: 400px, +) !default; + +/// Applies styles for a basic dropdown. +@mixin dropdown-container { + position: absolute; + z-index: 10; + + display: none; + + width: $dropdown-width; + padding: $dropdown-padding; + + visibility: hidden; + border: $dropdown-border; + border-radius: $dropdown-radius; + background-color: $dropdown-background; + + font-size: $dropdown-font-size; + + + // Allow an intermittent state to do positioning before making visible. + &.is-opening { + display: block; + } + + &.is-open { + display: block; + visibility: visible; + } +} + +@mixin foundation-dropdown { + .dropdown-pane { + @include dropdown-container; + } + + @each $name, $size in $dropdown-sizes { + .dropdown-pane { + &.#{$name} { + width: $size; + } + } + } +} diff --git a/src/foundation/components/_flex-video.scss b/src/foundation/components/_flex-video.scss new file mode 100644 index 0000000..bf85a6c --- /dev/null +++ b/src/foundation/components/_flex-video.scss @@ -0,0 +1 @@ +@import 'responsive-embed'; diff --git a/src/foundation/components/_flex.scss b/src/foundation/components/_flex.scss new file mode 100644 index 0000000..eed2eba --- /dev/null +++ b/src/foundation/components/_flex.scss @@ -0,0 +1,119 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group Flexbox Utilities +//// +// +/// Default value for the count of source ordering` +/// @type Number +$flex-source-ordering-count: 6 !default; + +/// Quickly disable/enable Responsive breakpoints for Vanilla Flex Helpers. +/// @type Boolean +$flexbox-responsive-breakpoints: true !default; + +@mixin flex-helpers { + .flex-container { + @include flex; + } + + .flex-child-auto { + flex: 1 1 auto; + } + + .flex-child-grow { + flex: 1 0 auto; + } + + .flex-child-shrink { + flex: 0 1 auto; + } + + @each $dir, $prop in $-zf-flex-direction { + .flex-dir-#{$dir} { + @include flex-direction($prop); + } + } + + @if ($flexbox-responsive-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-flex-container { + @include flex; + } + + .#{$size}-flex-child-auto { + flex: 1 1 auto; + } + + .#{$size}-flex-child-grow { + flex: 1 0 auto; + } + + .#{$size}-flex-child-shrink { + flex: 0 1 auto; + } + + @each $dir, $prop in $-zf-flex-direction { + .#{$size}-flex-dir-#{$dir} { + @include flex-direction($prop); + } + } + } + } + } + } +} + +@mixin foundation-flex-classes { + // Horizontal alignment using justify-content + @each $hdir, $prop in $-zf-flex-justify { + .align-#{$hdir} { + @include flex-align($x: $hdir); + } + } + + // Horizontal alignment Specifically for Vertical Menu + @each $hdir, $prop in map-remove($-zf-flex-justify, 'justify', 'spaced') { + .align-#{$hdir} { + &.vertical { + &.menu > li > a { + @include flex-align($x: $hdir); + } + } + } + } + + // Vertical alignment using align-items and align-self + @each $vdir, $prop in $-zf-flex-align { + .align-#{$vdir} { + @include flex-align($y: $vdir); + } + + .align-self-#{$vdir} { + @include flex-align-self($y: $vdir); + } + } + + // Central alignment of content + .align-center-middle { + @include flex-align($x: center, $y: middle); + align-content: center; + } + + // Source ordering + @include -zf-each-breakpoint { + @for $i from 1 through $flex-source-ordering-count { + .#{$-zf-size}-order-#{$i} { + @include flex-order($i); + } + } + } + + // Vanilla Flexbox Helpers + @include flex-helpers; +} diff --git a/src/foundation/components/_float.scss b/src/foundation/components/_float.scss new file mode 100644 index 0000000..3f81f3d --- /dev/null +++ b/src/foundation/components/_float.scss @@ -0,0 +1,27 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group float +//// + +@mixin foundation-float-classes { + .float-left { + float: left !important; + } + + .float-right { + float: right !important; + } + + .float-center { + display: block; + margin-right: auto; + margin-left: auto; + } + + .clearfix { + @include clearfix; + } +} diff --git a/src/foundation/components/_label.scss b/src/foundation/components/_label.scss new file mode 100644 index 0000000..cf7cf7d --- /dev/null +++ b/src/foundation/components/_label.scss @@ -0,0 +1,64 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group label +//// + +/// Default background color for labels. +/// @type Color +$label-background: $primary-color !default; + +/// Default text color for labels. +/// @type Color +$label-color: $white !default; + +/// Alternate text color for labels. +/// @type Color +$label-color-alt: $black !default; + +/// Coloring classes. A map of classes to output in your CSS, like `.secondary`, `.success`, and so on. +/// @type Map +$label-palette: $foundation-palette !default; + +/// Default font size for labels. +/// @type Number +$label-font-size: 0.8rem !default; + +/// Default padding inside labels. +/// @type Number +$label-padding: 0.33333rem 0.5rem !default; + +/// Default radius of labels. +/// @type Number +$label-radius: $global-radius !default; + +/// Generates base styles for a label. +@mixin label { + display: inline-block; + padding: $label-padding; + + border-radius: $label-radius; + + font-size: $label-font-size; + line-height: 1; + white-space: nowrap; + cursor: default; +} + +@mixin foundation-label { + .label { + @include label; + + background: $label-background; + color: $label-color; + + @each $name, $color in $label-palette { + &.#{$name} { + background: $color; + color: color-pick-contrast($color, ($label-color, $label-color-alt)); + } + } + } +} diff --git a/src/foundation/components/_media-object.scss b/src/foundation/components/_media-object.scss new file mode 100644 index 0000000..0ae8c51 --- /dev/null +++ b/src/foundation/components/_media-object.scss @@ -0,0 +1,114 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group media-object +//// + +/// Bottom margin of a media object. +/// @type Number +$mediaobject-margin-bottom: $global-margin !default; + +/// Left and right padding on sections within a media object. +/// @type Number +$mediaobject-section-padding: $global-padding !default; + +/// Width of images within a media object, when the object is stacked vertically. Set to 'auto' to use the image's natural width. +/// @type Number +$mediaobject-image-width-stacked: 100% !default; + +/// Adds styles for a media object container. +@mixin media-object-container { + display: if($global-flexbox, flex, block); + margin-bottom: $mediaobject-margin-bottom; + + @if $global-flexbox { + flex-wrap: nowrap; + } + + img { + max-width: none; + } + + @if $global-flexbox { + &.stack-for-#{$-zf-zero-breakpoint} { + @include breakpoint($-zf-zero-breakpoint only) { + flex-wrap: wrap; + } + } + } +} + +/// Adds styles for sections within a media object. +/// @param {Number} $padding [$mediaobject-section-padding] - Padding between sections. +@mixin media-object-section($padding: $mediaobject-section-padding) { + @if $global-flexbox { + flex: 0 1 auto; + } + @else { + display: table-cell; + vertical-align: top; + } + + &:first-child { + padding-#{$global-right}: $padding; + } + + &:last-child:not(:nth-child(2)) { + padding-#{$global-left}: $padding; + } + + > :last-child { + margin-bottom: 0; + } + + .stack-for-#{$-zf-zero-breakpoint} & { + @include breakpoint($-zf-zero-breakpoint only) { + @include media-object-stack; + } + } + + @if $global-flexbox { + &.main-section { + flex: 1 1 0px; // sass-lint:disable-line zero-unit + } + } + @else { + &.middle { + vertical-align: middle; + } + + &.bottom { + vertical-align: bottom; + } + } +} + +/// Adds styles to stack sections of a media object. Apply this to the section elements, not the container. +@mixin media-object-stack { + padding: 0; + padding-bottom: $mediaobject-section-padding; + + @if $global-flexbox { + flex-basis: 100%; + max-width: 100%; + } + @else { + display: block; + } + + img { + width: $mediaobject-image-width-stacked; + } +} + +@mixin foundation-media-object { + .media-object { + @include media-object-container; + } + + .media-object-section { + @include media-object-section; + } +} diff --git a/src/foundation/components/_menu-icon.scss b/src/foundation/components/_menu-icon.scss new file mode 100644 index 0000000..b0df173 --- /dev/null +++ b/src/foundation/components/_menu-icon.scss @@ -0,0 +1,9 @@ +@mixin foundation-menu-icon { + .menu-icon { + @include hamburger($color: $titlebar-icon-color, $color-hover: $titlebar-icon-color-hover); + } + + .menu-icon.dark { + @include hamburger; + } +} diff --git a/src/foundation/components/_menu.scss b/src/foundation/components/_menu.scss new file mode 100644 index 0000000..0cbb9d0 --- /dev/null +++ b/src/foundation/components/_menu.scss @@ -0,0 +1,495 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group menu +//// + +/// Margin of a menu. +/// @type Number +$menu-margin: 0 !default; + +/// Left-hand margin of a nested menu. +/// @type Number +$menu-nested-margin: $global-menu-nested-margin !default; + +/// Padding for items in a pill menu. +/// @type Number +$menu-items-padding: $global-menu-padding !default; + +/// margin for items in a simple menu. +/// @type Number +$menu-simple-margin: 1rem !default; + +/// Text color of an active menu item. +/// @type Color +$menu-item-color-active: $white !default; + +/// Alternative text color of an active menu item.. +/// @type Color +$menu-item-color-alt-active: $black !default; + +/// Background color of an active menu item. +/// @type Color +$menu-item-background-active: get-color(primary) !default; + +/// Spacing between an icon and text in a menu item. +/// @type Number +$menu-icon-spacing: 0.25rem !default; + +/// Backward compatibility for menu state. If true, this duplicate `active` with `is-active`. +/// But please note that `active` will be removed in upcoming versions. +/// @type Boolean +$menu-state-back-compat: true !default; + +/// Backward compatibility for menu centered. If true, this duplicate `.menu-centered > .menu` with `.menu.align-center`. +/// But please note that `menu-centered` will be removed in upcoming versions. +/// @type Boolean +$menu-centered-back-compat: true !default; + +/// Backward compatibility for using `icon-*` classes without `.icons` classes +/// But please note that this backward compatibility will be removed in upcoming versions. +/// @type Boolean +$menu-icons-back-compat: true !default; + +/// Creates the base styles for a Menu. +@mixin menu-base { + padding: 0; + margin: 0; + list-style: none; + position: relative; + + @if $global-flexbox { + display: flex; + flex-wrap: wrap; + } + + li { + @include disable-mouse-outline; + } + + a, + .button { + line-height: 1; + text-decoration: none; + display: block; + padding: $menu-items-padding; + } + + // Reset styles of inner elements + input, + select, + a, + button { + margin-bottom: 0; + } + + input { + display: inline-block; + } +} + +/// Expands the items of a Menu, so each item is the same width. +@mixin menu-expand { + @if $global-flexbox { + li { + flex: 1 1 0px; // sass-lint:disable-line zero-unit + } + } + @else { + display: table; + width: 100%; + + > li { + display: table-cell; + vertical-align: middle; + } + } +} + +/// Align menu items. +@mixin menu-align($alignment) { + @if $alignment == left { + @if $global-flexbox { + justify-content: flex-start; + } + @else { + text-align: $global-left; + } + } + @else if $alignment == right { + @if $global-flexbox { + li { + display: flex; + justify-content: flex-end; + + .submenu li { + justify-content: flex-start; + } + } + + &.vertical li { + display: block; + text-align: $global-right; + + .submenu li { + text-align: $global-right; + } + } + } + @else { + text-align: $global-right; + + .submenu li { + text-align: $global-left; + } + + &.vertical { + .submenu li { + text-align: $global-right; + } + } + } + } + @else if $alignment == center { + @if $global-flexbox { + li { + display: flex; + justify-content: center; + + .submenu li { + justify-content: flex-start; + } + } + } + @else { + text-align: center; + + .submenu li { + text-align: $global-left; + } + } + } +} + +/// Sets the direction of a Menu. +/// @param {Keyword} $dir [horizontal] - Direction of the Menu. Can be `horizontal` or `vertical`. +@mixin menu-direction($dir: horizontal) { + @if $dir == horizontal { + @if $global-flexbox { + flex-wrap: wrap; + flex-direction: row; + } + @else { + li { + display: inline-block; + } + } + } + @else if $dir == vertical { + @if $global-flexbox { + flex-wrap: nowrap; + flex-direction: column; + } + @else { + li { + display: block; + } + } + } + @else { + @warn 'The direction used for menu-direction() must be horizontal or vertical.'; + } +} + +/// Creates a simple Menu, which has no padding or hover state. +/// @param {Keyword} $dir [$global-left] - Direction of the menu. This effects the side of the `<li>` that receives the margin. +/// @param {Number} $margin [$menu-simple-margin] - The margin to apply to each `<li>`. +@mixin menu-simple($dir: $global-left, $margin: $menu-simple-margin) { + @if $global-flexbox { + align-items: center; + } + + li + li { + margin-#{$dir}: $margin; + } + + a { + padding: 0; + } +} + +/// Adds styles for a nested Menu, by adding `margin-left` to the menu. +/// @param {Keyword|Number} $margin [$menu-nested-margin] - Length of the margin. +/// @param {Keyword} $nested-alignment [left] - Alignment of the nested class +@mixin menu-nested( + $margin: $menu-nested-margin, + $nested-alignment: left +) { + @if $nested-alignment == right { + margin-#{$global-right}: $margin; + margin-#{$global-left}: 0; + } + @else { + margin-#{$global-right}: 0; + margin-#{$global-left}: $margin; + } + +} + +/// Adds basic styles for icons in menus. +@mixin menu-icons() { + @if $global-flexbox { + a { + display: flex; + } + } + @else { + img, + i, + svg { + vertical-align: middle; + + + span { + vertical-align: middle; + } + } + } +} + +/// Adds position classes for icons within a menu. +@mixin menu-icon-position($position: left, $spacing: $menu-icon-spacing) { + @if $position == left { + li a { + @if $global-flexbox { + flex-flow: row nowrap; + } + + img, + i, + svg { + margin-#{$global-right}: $spacing; + + @if not $global-flexbox { + display: inline-block; + } + } + } + } + @else if $position == right { + li a { + @if $global-flexbox { + flex-flow: row nowrap; + } + + img, + i, + svg { + margin-#{$global-left}: $spacing; + + @if not $global-flexbox { + display: inline-block; + } + } + } + } + @else if $position == top { + li a { + @if $global-flexbox { + flex-flow: column nowrap; + } + @else { + text-align: center; + } + + img, + i, + svg { + @if not $global-flexbox { + display: block; + margin: 0 auto $spacing; + } + @else { + align-self: stretch; + margin-bottom: $spacing; + text-align: center; + } + } + } + } + @else if $position == bottom { + li a { + @if $global-flexbox { + flex-flow: column nowrap; + } + @else { + text-align: center; + } + + img, + i, + svg { + @if not $global-flexbox { + display: block; + margin: $spacing auto 0; + } + @else { + align-self: stretch; + margin-bottom: $spacing; + text-align: center; + } + } + } + } +} + +@mixin menu-text { + padding: $global-menu-padding; + + font-weight: bold; + line-height: 1; + color: inherit; +} + +@mixin menu-state-active { + background: $menu-item-background-active; + color: color-pick-contrast($menu-item-background-active, ($menu-item-color-active, $menu-item-color-alt-active)); +} + +@mixin foundation-menu { + .menu { + @include menu-base; + + // Default orientation: horizontal + &, &.horizontal { + @include menu-direction(horizontal); + } + + // Vertical orientation modifier + &.vertical { + @include menu-direction(vertical); + } + + // Even-width modifier for horizontal orientation + &.expanded { + @include menu-expand; + } + + // Simple + &.simple { + @include menu-simple; + } + + // Breakpoint specific versions + @include -zf-each-breakpoint($small: false) { + &.#{$-zf-size}-horizontal { + @include menu-direction(horizontal); + } + + &.#{$-zf-size}-vertical { + @include menu-direction(vertical); + } + + &.#{$-zf-size}-expanded { + @include menu-expand; + } + + &.#{$-zf-size}-simple { + @include menu-expand; + } + } + + // Nesting + &.nested { + @include menu-nested; + } + + // Icon Base Styles + &.icons { + @include menu-icons; + } + + // Backward Compatibility for active state + @if $menu-icons-back-compat { + &.icon-top, + &.icon-right, + &.icon-bottom, + &.icon-left { + @include menu-icons; + } + } + + // Icon Left + &.icon-left { + @include menu-icon-position(left); + } + + // Icon Right + &.icon-right { + @include menu-icon-position(right); + } + + // Icon Top + &.icon-top { + @include menu-icon-position(top); + } + + // Icon Bottom + &.icon-bottom { + @include menu-icon-position(bottom); + } + + // Active state + .is-active > a { + @include menu-state-active; + } + + // Backward Compatibility for active state + @if $menu-state-back-compat { + .active > a { + @include menu-state-active; + } + } + + // Align left + &.align-#{$global-left} { + @include menu-align(left); + } + + // Align right + &.align-#{$global-right} { + @include menu-align(right); + + .nested { + @include menu-nested($nested-alignment: right); + } + } + + // Align center + &.align-center { + @include menu-align(center); + } + + .menu-text { + @include menu-text; + } + } + + @if $menu-centered-back-compat { + .menu-centered { + > .menu { + @if $global-flexbox { + justify-content: center; + } + + @include menu-align(center); + } + } + } + + // Prevent FOUC when using the Responsive Menu plugin + .no-js [data-responsive-menu] ul { + display: none; + } +} diff --git a/src/foundation/components/_off-canvas.scss b/src/foundation/components/_off-canvas.scss new file mode 100644 index 0000000..29eef9d --- /dev/null +++ b/src/foundation/components/_off-canvas.scss @@ -0,0 +1,511 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group off-canvas +//// + +/// Width map of a left/right off-canvas panel. +/// @type Map +$offcanvas-sizes: ( + small: 250px, +) !default; + +/// Height map of a top/bottom off-canvas panel. +/// @type Map +$offcanvas-vertical-sizes: ( + small: 250px, +) !default; + +/// Background color of an off-canvas panel. +/// @type Color +$offcanvas-background: $light-gray !default; + +/// Box shadow for the off-canvas overlap panel. +/// @type Shadow +$offcanvas-shadow: 0 0 10px rgba($black, 0.7) !default; + +/// Inner box shadow size for the off-canvas push panel. +/// @type Number +$offcanvas-inner-shadow-size: 20px !default; + +/// Inner box shadow color for the off-canvas push panel. +/// @type Color +$offcanvas-inner-shadow-color: rgba($black, 0.25) !default; + +/// Z-index of an off-canvas content overlay. +/// @type Number +$offcanvas-overlay-zindex: 11 !default; + +/// Z-index of an off-canvas panel with the `push` transition. +/// @type Number +$offcanvas-push-zindex: 12 !default; + +/// Z-index of an off-canvas panel with the `overlap` transition. +/// @type Number +$offcanvas-overlap-zindex: 13 !default; + +/// Z-index of an off-canvas panel using the `reveal-for-*` classes or mixin. +/// @type Number +$offcanvas-reveal-zindex: 12 !default; + +/// Length of the animation on an off-canvas panel. +/// @type Number +$offcanvas-transition-length: 0.5s !default; + +/// Timing function of the animation on an off-canvas panel. +/// @type Keyword +$offcanvas-transition-timing: ease !default; + +/// If `true`, a revealed off-canvas will be fixed-position, and scroll with the screen. +/// @type Bool +$offcanvas-fixed-reveal: true !default; + +/// Background color for the overlay that appears when an off-canvas panel is open. +/// @type Color +$offcanvas-exit-background: rgba($white, 0.25) !default; + +/// CSS class used for the main content area. The off-canvas mixins use this to target the page content. +$maincontent-class: 'off-canvas-content' !default; + +/// Adds baseline styles for off-canvas. This CSS is required to make the other pieces work. +@mixin off-canvas-basics { + + /// Transform deprecated size settings into map & show warning + @if variable-exists(offcanvas-size) { + $offcanvas-sizes: (small: $offcanvas-size, medium: $offcanvas-size) !global; + @warn '$offcanvas-size is deprecated and not used anymore! Please update your settings and use the map $offcanvas-sizes instead'; + } + @if variable-exists(offcanvas-vertical-size) { + $offcanvas-vertical-sizes: (small: $offcanvas-vertical-size, medium: $offcanvas-vertical-size) !global; + @warn '$offcanvas-vertical-size is deprecated and not used anymore! Please update your settings and use the map $offcanvas-vertical-sizes instead'; + } + + // Checks the z-indexes and increase them due to backwards compatibility. + // This is necessary because the overlay's z-index is new since v6.4 and may be identical to the user custom settings of the push z-index. + @if $offcanvas-push-zindex <= $offcanvas-overlay-zindex { $offcanvas-push-zindex: $offcanvas-overlay-zindex + 1 !global; } + @if $offcanvas-overlap-zindex <= $offcanvas-push-zindex { $offcanvas-overlap-zindex: $offcanvas-push-zindex + 1 !global; } + @if $offcanvas-reveal-zindex <= $offcanvas-overlay-zindex { $offcanvas-reveal-zindex: $offcanvas-overlay-zindex + 1 !global; } + + // Hides overflow on body when an off-canvas panel is open. + .is-off-canvas-open { + overflow: hidden; + } + + // Off-canvas overlay (generated by JavaScript) + .js-off-canvas-overlay { + position: absolute; + top: 0; + left: 0; + z-index: $offcanvas-overlay-zindex; + + width: 100%; + height: 100%; + + transition: opacity $offcanvas-transition-length $offcanvas-transition-timing, visibility $offcanvas-transition-length $offcanvas-transition-timing; + + background: $offcanvas-exit-background; + + opacity: 0; + visibility: hidden; + + overflow: hidden; + + &.is-visible { + opacity: 1; + visibility: visible; + } + + &.is-closable { + cursor: pointer; + } + + &.is-overlay-absolute { + position: absolute; + } + + &.is-overlay-fixed { + position: fixed; + } + } +} + +// Adds basic styles for an off-canvas wrapper. +@mixin off-canvas-wrapper() { + position: relative; + overflow: hidden; +} + +/// Adds basic styles for an off-canvas panel. +@mixin off-canvas-base( + $background: $offcanvas-background, + $transition: $offcanvas-transition-length $offcanvas-transition-timing, + $fixed: true +) { + @include disable-mouse-outline; + + @if $fixed == true { + position: fixed; + } + @else { + position: absolute; + } + + // Set the off-canvas z-index. + z-index: $offcanvas-push-zindex; + + // Increase CSS specificity + &.is-transition-push { + z-index: $offcanvas-push-zindex; + } + + transition: transform $transition; + backface-visibility: hidden; + + background: $background; + + // Hide inactive off-canvas within the content that have the same position + &.is-closed { + visibility: hidden; + } + + // Overlap only styles. + &.is-transition-overlap { + z-index: $offcanvas-overlap-zindex; + + &.is-open { + box-shadow: $offcanvas-shadow; + } + } + + // Sets transform to 0 to show an off-canvas panel. + &.is-open { + transform: translate(0, 0); + } +} + +/// Adds styles to position an off-canvas panel to the left/right/top/bottom. +@mixin off-canvas-position( + $position: left, + $orientation: horizontal, + $sizes: if($orientation == horizontal, $offcanvas-sizes, $offcanvas-vertical-sizes) +) { + @if $position == left { + top: 0; + left: 0; + height: 100%; + overflow-y: auto; + + @each $name, $size in $sizes { + @include breakpoint($name) { + width: $size; + transform: translateX(-$size); + } + } + + // Sets the position for nested off-canvas element + @at-root .#{$maincontent-class} .off-canvas.position-#{$position} { + + @each $name, $size in $sizes { + @include breakpoint($name) { + transform: translateX(-$size); + } + } + &.is-transition-overlap.is-open { + transform: translate(0, 0); + } + } + + // Sets the open position for the content + @at-root .#{$maincontent-class}.is-open-#{$position} { + &.has-transition-push { + @each $name, $size in $sizes { + @include breakpoint($name) { + transform: translateX($size); + } + } + } + } + } + @else if $position == right { + top: 0; + right: 0; + height: 100%; + overflow-y: auto; + + @each $name, $size in $sizes { + @include breakpoint($name) { + width: $size; + transform: translateX($size); + } + } + + // Sets the position for nested off-canvas element + @at-root .#{$maincontent-class} .off-canvas.position-#{$position} { + + @each $name, $size in $sizes { + @include breakpoint($name) { + transform: translateX($size); + } + } + &.is-transition-overlap.is-open { + transform: translate(0, 0); + } + } + + // Sets the open position for the content + @at-root .#{$maincontent-class}.is-open-#{$position} { + &.has-transition-push { + @each $name, $size in $sizes { + @include breakpoint($name) { + transform: translateX(-$size); + } + } + } + } + } + @else if $position == top { + top: 0; + left: 0; + width: 100%; + overflow-x: auto; + + @each $name, $size in $sizes { + @include breakpoint($name) { + height: $size; + transform: translateY(-$size); + } + } + + // Sets the position for nested off-canvas element + @at-root .#{$maincontent-class} .off-canvas.position-#{$position} { + @each $name, $size in $sizes { + @include breakpoint($name) { + transform: translateY(-$size); + } + } + &.is-transition-overlap.is-open { + transform: translate(0, 0); + } + } + + // Sets the open position for the content + @at-root .#{$maincontent-class}.is-open-#{$position} { + &.has-transition-push { + @each $name, $size in $sizes { + @include breakpoint($name) { + transform: translateY($size); + } + } + } + } + } + @else if $position == bottom { + bottom: 0; + left: 0; + width: 100%; + overflow-x: auto; + + @each $name, $size in $sizes { + @include breakpoint($name) { + height: $size; + transform: translateY($size); + } + } + + // Sets the position for nested off-canvas element + @at-root .#{$maincontent-class} .off-canvas.position-#{$position} { + @each $name, $size in $sizes { + @include breakpoint($name) { + transform: translateY($size); + } + } + &.is-transition-overlap.is-open { + transform: translate(0, 0); + } + } + + // Sets the open position for the content + @at-root .#{$maincontent-class}.is-open-#{$position} { + &.has-transition-push { + @each $name, $size in $sizes { + @include breakpoint($name) { + transform: translateY(-$size); + } + } + } + } + } + + // If $offcanvas-inner-shadow-size is set, add inner box-shadow. + // This mimics the off-canvas panel having a lower z-index, without having to have one. + @if $offcanvas-inner-shadow-size { + &.is-transition-push { + @if $position == left { + @include inner-side-shadow(right, $offcanvas-inner-shadow-size, $offcanvas-inner-shadow-color); + } + @else if $position == right { + @include inner-side-shadow(left, $offcanvas-inner-shadow-size, $offcanvas-inner-shadow-color); + } + @else if $position == top { + @include inner-side-shadow(bottom, $offcanvas-inner-shadow-size, $offcanvas-inner-shadow-color); + } + @else if $position == bottom { + @include inner-side-shadow(top, $offcanvas-inner-shadow-size, $offcanvas-inner-shadow-color); + } + } + } + +} + +/// Sets the styles for the content container. +@mixin off-canvas-content() { + transform: none; + backface-visibility: hidden; + + // Bind to has-transition-X class to prevent transition for transform:none + &.has-transition-overlap, + &.has-transition-push { + transition: transform $offcanvas-transition-length $offcanvas-transition-timing; + } + + // Transform scope until the element is closed (makes sure transitionend gets triggered) + &.has-transition-push { + transform: translate(0, 0); + } + + // Consider element & content, nested in another content + .off-canvas.is-open { + transform: translate(0, 0); + } +} + +/// Adds styles that reveal an off-canvas panel. +@mixin off-canvas-reveal( +$position: left, +$zindex: $offcanvas-reveal-zindex, +$content: $maincontent-class, +$breakpoint: small +) { + transform: none; + z-index: $zindex; + transition: none; + visibility: visible; + + @if not $offcanvas-fixed-reveal { + position: absolute; + } + + .close-button { + display: none; + } + + // Consider revealed element is nested in content + .#{$maincontent-class} & { + transform: none; + } + + @at-root .#{$content}.has-reveal-#{$position} { + margin-#{$position}: -zf-get-bp-val($offcanvas-sizes, $breakpoint); + } + + // backwards compatibility (prior to v6.4) + & ~ .#{$content} { + margin-#{$position}: -zf-get-bp-val($offcanvas-sizes, $breakpoint); + } +} + +/// Overrides the off-canvas styles +@mixin in-canvas() { + visibility: visible; + height: auto; + position: static; + background: none; + width: auto; + overflow: visible; + transition: none; + + // Increase CSS specificity + &.position-left, + &.position-right, + &.position-top, + &.position-bottom { + box-shadow: none; + transform: none; + } + + .close-button { + display: none; + } +} + +@mixin foundation-off-canvas { + @include off-canvas-basics; + + // Off-canvas wrapper + .off-canvas-wrapper { + @include off-canvas-wrapper; + } + + // Off-canvas container + .off-canvas { + @include off-canvas-base; + + // Force position absolute for nested off-canvas because fixed doesn't work for push transition within the transform scope. + @at-root .#{$maincontent-class} & { + // NOTE: since overlap transition is currently forced if nested, there's no need to force position absolute until nested push transition is supported. + // position: absolute; + } + } + + // Off-canvas container with absolute position + .off-canvas-absolute { + @include off-canvas-base($fixed: false); + } + + // Off-canvas position classes + .position-left { @include off-canvas-position(left, horizontal); } + .position-right { @include off-canvas-position(right, horizontal); } + .position-top { @include off-canvas-position(top, vertical); } + .position-bottom { @include off-canvas-position(bottom, vertical); } + + .off-canvas-content { + @include off-canvas-content; + } + + // Reveal off-canvas panel on larger screens + @each $name, $value in $breakpoint-classes { + @if $name != $-zf-zero-breakpoint { + @include breakpoint($name) { + .position-left.reveal-for-#{$name} { + @include off-canvas-reveal(left, $offcanvas-reveal-zindex, $maincontent-class, $name); + } + + .position-right.reveal-for-#{$name} { + @include off-canvas-reveal(right, $offcanvas-reveal-zindex, $maincontent-class, $name); + } + + .position-top.reveal-for-#{$name} { + @include off-canvas-reveal(top, $offcanvas-reveal-zindex, $maincontent-class, $name); + } + + .position-bottom.reveal-for-#{$name} { + @include off-canvas-reveal(bottom, $offcanvas-reveal-zindex, $maincontent-class, $name); + } + } + } + } + + // Move in-canvas for larger screens + @each $name, $value in $breakpoint-classes { + @if $name != $-zf-zero-breakpoint { + @include breakpoint($name) { + .off-canvas.in-canvas-for-#{$name} { + @include in-canvas; + } + } + } + } +} + diff --git a/src/foundation/components/_orbit.scss b/src/foundation/components/_orbit.scss new file mode 100644 index 0000000..988c291 --- /dev/null +++ b/src/foundation/components/_orbit.scss @@ -0,0 +1,197 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group orbit +//// + +/// Default color for Orbit's bullets. +/// @type Color +$orbit-bullet-background: $medium-gray !default; + +/// Default active color for Orbit's bullets. +/// @type Color +$orbit-bullet-background-active: $dark-gray !default; + +/// Default diameter for Orbit's bullets. +/// @type Number +$orbit-bullet-diameter: 1.2rem !default; + +/// Default margin between Orbit's bullets. +/// @type Number +$orbit-bullet-margin: 0.1rem !default; + +/// Default distance from slide region for Orbit's bullets. +/// @type Number +$orbit-bullet-margin-top: 0.8rem !default; + +/// Default bottom margin from Orbit's bullets to whatever content may lurk below it. +/// @type Number +$orbit-bullet-margin-bottom: 0.8rem !default; + +/// Default background color for Orbit's caption. +/// @type Color +$orbit-caption-background: rgba($black, 0.5) !default; + +/// Default padding for Orbit's caption. +/// @type Number +$orbit-caption-padding: 1rem !default; + +/// Default background color for Orbit's controls when hovered. +/// @type Color +$orbit-control-background-hover: rgba($black, 0.5) !default; + +/// Default padding for Orbit's controls. +/// @type Number +$orbit-control-padding: 1rem !default; + +/// Default z-index for Orbit's controls. +/// @type Number +$orbit-control-zindex: 10 !default; + +/// Adds styles for the outer Orbit wrapper. These styles are used on the `.orbit` class. +@mixin orbit-wrapper { + position: relative; +} + +/// Adds styles for the inner Orbit slide container. These styles are used on the `.orbit-container` class. +@mixin orbit-container { + position: relative; + height: 0; // Prevent FOUC by not showing until JS sets height + margin: 0; + list-style: none; + overflow: hidden; +} + +/// Adds styles for the individual slides of an Orbit slider. These styles are used on the `.orbit-slide` class. +@mixin orbit-slide { + width: 100%; + position: absolute; + + &.no-motionui { + &.is-active { + top: 0; + left: 0; + } + } +} + +@mixin orbit-figure { + margin: 0; +} + +/// Adds styles for a slide containing an image. These styles are used on the `.orbit-image` class. +@mixin orbit-image { + width: 100%; + max-width: 100%; + margin: 0; +} + +/// Adds styles for an orbit slide caption. These styles are used on the `.orbit-caption` class. +@mixin orbit-caption { + position: absolute; + bottom: 0; + width: 100%; + margin-bottom: 0; + padding: $orbit-caption-padding; + + background-color: $orbit-caption-background; + color: color-pick-contrast($orbit-caption-background); +} + +/// Adds base styles for the next/previous buttons in an Orbit slider. These styles are shared between the `.orbit-next` and `.orbit-previous` classes in the default CSS. +@mixin orbit-control { + @include disable-mouse-outline; + @include vertical-center; + z-index: $orbit-control-zindex; + padding: $orbit-control-padding; + color: $white; + + &:hover, + &:active, + &:focus { + background-color: $orbit-control-background-hover; + } +} + +/// Adds styles for the Orbit previous button. These styles are used on the `.orbit-previous` class. +@mixin orbit-previous { + #{$global-left}: 0; +} + +/// Adds styles for the Orbit next button. These styles are used on the `.orbit-next` class. +@mixin orbit-next { + #{$global-left}: auto; + #{$global-right}: 0; +} + +/// Adds styles for a container of Orbit bullets. /// Adds styles for the Orbit previous button. These styles are used on the `.orbit-bullets` class. +@mixin orbit-bullets { + @include disable-mouse-outline; + position: relative; + margin-top: $orbit-bullet-margin-top; + margin-bottom: $orbit-bullet-margin-bottom; + text-align: center; + + button { + width: $orbit-bullet-diameter; + height: $orbit-bullet-diameter; + margin: $orbit-bullet-margin; + + border-radius: 50%; + background-color: $orbit-bullet-background; + + &:hover { + background-color: $orbit-bullet-background-active; + } + + &.is-active { + background-color: $orbit-bullet-background-active; + } + } +} + +@mixin foundation-orbit { + .orbit { + @include orbit-wrapper; + } + + .orbit-container { + @include orbit-container; + } + + .orbit-slide { + @include orbit-slide; + } + + .orbit-figure { + @include orbit-figure; + } + + .orbit-image { + @include orbit-image; + } + + .orbit-caption { + @include orbit-caption; + } + + %orbit-control { + @include orbit-control; + } + + .orbit-previous { + @extend %orbit-control; + @include orbit-previous; + } + + .orbit-next { + @extend %orbit-control; + @include orbit-next; + } + + .orbit-bullets { + @include orbit-bullets; + } +} diff --git a/src/foundation/components/_pagination.scss b/src/foundation/components/_pagination.scss new file mode 100644 index 0000000..b4095d4 --- /dev/null +++ b/src/foundation/components/_pagination.scss @@ -0,0 +1,201 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group pagination +//// + +/// Font size of pagination items. +/// @type Number +$pagination-font-size: rem-calc(14) !default; + +/// Default bottom margin of the pagination object. +/// @type Number +$pagination-margin-bottom: $global-margin !default; + +/// Text color of pagination items. +/// @type Color +$pagination-item-color: $black !default; + +/// Padding inside of pagination items. +/// @type Number +$pagination-item-padding: rem-calc(3 10) !default; + +/// Right margin to separate pagination items. +/// @type Number +$pagination-item-spacing: rem-calc(1) !default; + +/// Default radius for pagination items. +/// @type Number +$pagination-radius: $global-radius !default; + +/// Background color of pagination items on hover. +/// @type Color +$pagination-item-background-hover: $light-gray !default; + +/// Background color of pagination item for the current page. +/// @type Color +$pagination-item-background-current: $primary-color !default; + +/// Text color of the pagination item for the current page. +/// @type Color +$pagination-item-color-current: $white !default; + +/// Text color of a disabled pagination item. +/// @type Color +$pagination-item-color-disabled: $medium-gray !default; + +/// Color of the ellipsis in a pagination menu. +/// @type Color +$pagination-ellipsis-color: $black !default; + +/// If `false`, don't display page number links on mobile, only next/previous links +/// and optionally current page number. +/// @type Boolean +$pagination-mobile-items: false !default; + +/// If `true`, display the current page number on mobile even if `$pagination-mobile-items` is set to `false`. +/// This parameter will only override the visibility setting of the current item for `$pagination-mobile-items: false;`, +/// it will not affect the current page number visibility when `$pagination-mobile-items` is set to `true`. +/// @type Boolean +$pagination-mobile-current-item: false !default; + +/// If `true`, arrows are added to the next and previous links of pagination. +/// @type Boolean +$pagination-arrows: true !default; + +/// Content for the previous arrow when `$pagination-arrows` is `true` +/// @type String +$pagination-arrow-previous: '\00AB' !default; + +/// Content for the next arrow when `$pagination-arrows` is `true` +/// @type String +$pagination-arrow-next: '\00BB' !default; + +/// Adds styles for a pagination container. Apply this to a `<ul>`. +@mixin pagination-container ( + $margin-bottom: $pagination-margin-bottom, + $font-size: $pagination-font-size, + $spacing: $pagination-item-spacing, + $radius: $pagination-radius, + $color: $pagination-item-color, + $padding: $pagination-item-padding, + $background-hover: $pagination-item-background-hover +) { + @include clearfix; + margin-#{$global-left}: 0; + margin-bottom: $margin-bottom; + + // List item + li { + margin-#{$global-right}: $spacing; + border-radius: $radius; + font-size: $font-size; + + @if $pagination-mobile-items { + display: inline-block; + } + @else { + display: none; + + &:last-child, + &:first-child { + display: inline-block; + } + + @if $pagination-mobile-current-item { + &.current { + display: inline-block; + } + } + + @include breakpoint(medium) { + display: inline-block; + } + } + } + + // Page links + a, + button { + display: block; + padding: $padding; + border-radius: $radius; + color: $color; + + &:hover { + background: $background-hover; + } + } +} + +/// Adds styles for the current pagination item. Apply this to an `<a>`. +@mixin pagination-item-current ( + $padding: $pagination-item-padding, + $background-current: $pagination-item-background-current, + $color-current: $pagination-item-color-current +) { + padding: $padding; + background: $background-current; + color: $color-current; + cursor: default; +} + +/// Adds styles for a disabled pagination item. Apply this to an `<a>`. +@mixin pagination-item-disabled ( + $padding: $pagination-item-padding, + $color: $pagination-item-color-disabled +) { + padding: $padding; + color: $color; + cursor: not-allowed; + + &:hover { + background: transparent; + } +} + +/// Adds styles for an ellipsis for use in a pagination list. +@mixin pagination-ellipsis ( + $padding: $pagination-item-padding, + $color: $pagination-ellipsis-color +) { + padding: $padding; + content: '\2026'; + color: $color; +} + +@mixin foundation-pagination { + .pagination { + @include pagination-container; + + .current { + @include pagination-item-current; + } + + .disabled { + @include pagination-item-disabled; + } + + .ellipsis::after { + @include pagination-ellipsis; + } + } + + @if $pagination-arrows { + .pagination-previous a::before, + .pagination-previous.disabled::before { + display: inline-block; + margin-#{$global-right}: 0.5rem; + content: $pagination-arrow-previous; + } + + .pagination-next a::after, + .pagination-next.disabled::after { + display: inline-block; + margin-#{$global-left}: 0.5rem; + content: $pagination-arrow-next; + } + } +} diff --git a/src/foundation/components/_progress-bar.scss b/src/foundation/components/_progress-bar.scss new file mode 100644 index 0000000..7de5f87 --- /dev/null +++ b/src/foundation/components/_progress-bar.scss @@ -0,0 +1,63 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +/// Adds styles for a progress bar container. +@mixin progress-container { + height: $progress-height; + margin-bottom: $progress-margin-bottom; + border-radius: $progress-radius; + background-color: $progress-background; +} + +/// Adds styles for the inner meter of a progress bar. +@mixin progress-meter { + position: relative; + display: block; + width: 0%; + height: 100%; + background-color: $progress-meter-background; + + @if has-value($progress-radius) { + border-radius: $global-radius; + } +} + +/// Adds styles for text in the progress meter. +@mixin progress-meter-text { + @include absolute-center; + margin: 0; + font-size: 0.75rem; + font-weight: bold; + color: $white; + white-space: nowrap; + + @if has-value($progress-radius) { + border-radius: $progress-radius; + } +} + +@mixin foundation-progress-bar { + // Progress bar + .progress { + @include progress-container; + + @each $name, $color in $foundation-palette { + &.#{$name} { + .progress-meter { + background-color: $color; + } + } + } + } + + // Inner meter + .progress-meter { + @include progress-meter; + } + + // Inner meter text + .progress-meter-text { + @include progress-meter-text; + } +} diff --git a/src/foundation/components/_responsive-embed.scss b/src/foundation/components/_responsive-embed.scss new file mode 100644 index 0000000..8bcd899 --- /dev/null +++ b/src/foundation/components/_responsive-embed.scss @@ -0,0 +1,57 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group responsive-embed +//// + +/// Margin below a responsive embed container. +/// @type Number +$responsive-embed-margin-bottom: rem-calc(16) !default; + +/// Aspect ratios used to determine padding-bottom of responsive embed containers. +/// @type Map +$responsive-embed-ratios: ( + default: 4 by 3, + widescreen: 16 by 9, +) !default; + +/// Creates a responsive embed container. +/// @param {String|List} $ratio [default] - Ratio of the container. Can be a key from the `$responsive-embed-ratios` map or a list formatted as `x by y`. +@mixin responsive-embed($ratio: default) { + @if type-of($ratio) == 'string' { + $ratio: map-get($responsive-embed-ratios, $ratio); + } + position: relative; + height: 0; + margin-bottom: $responsive-embed-margin-bottom; + padding-bottom: ratio-to-percentage($ratio); + overflow: hidden; + + iframe, + object, + embed, + video { + position: absolute; + top: 0; + #{$global-left}: 0; + width: 100%; + height: 100%; + } +} + +@mixin foundation-responsive-embed { + .responsive-embed, + .flex-video { + @include responsive-embed($ratio: default); + + $ratios: map-remove($responsive-embed-ratios,default); + + @each $name, $ratio in $ratios { + &.#{$name} { + padding-bottom: ratio-to-percentage($ratio); + } + } + } +} diff --git a/src/foundation/components/_reveal.scss b/src/foundation/components/_reveal.scss new file mode 100644 index 0000000..8099490 --- /dev/null +++ b/src/foundation/components/_reveal.scss @@ -0,0 +1,185 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group reveal +//// + +/// Default background color of a modal. +/// @type Color +$reveal-background: $white !default; + +/// Default width of a modal, with no class applied. +/// @type Number +$reveal-width: 600px !default; + +/// Default maximum width of a modal. +/// @type Number +$reveal-max-width: $global-width !default; + +/// Default padding inside a modal. +/// @type Number +$reveal-padding: $global-padding !default; + +/// Default border around a modal. +/// @type Number +$reveal-border: 1px solid $medium-gray !default; + +/// Default radius for modal. +/// @type Number +$reveal-radius: $global-radius !default; + +/// z-index for modals. The overlay uses this value, while the modal itself uses this value plus one. +/// @type Number +$reveal-zindex: 1005 !default; + +/// Background color of modal overlays. +/// @type Color +$reveal-overlay-background: rgba($black, 0.45) !default; + + +// Placeholder selector for medium-and-up modals +// Prevents duplicate CSS when defining multiple Reveal sizes +// This should be in the same breakpoint then `@mixin reveal-modal-width` +@include breakpoint(medium) { + %reveal-centered { + right: auto; + left: auto; + margin: 0 auto; + } +} + + +/// Adds styles for a modal overlay. +/// @param {Color} $background [$reveal-overlay-background] - Background color of the overlay. +@mixin reveal-overlay($background: $reveal-overlay-background) { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $reveal-zindex; + + display: none; + background-color: $background; + overflow-y: auto; +} + +/// Adds base styles for a modal. +@mixin reveal-modal-base { + @include disable-mouse-outline; + z-index: $reveal-zindex + 1; + // Workaround android browser z-index bug + backface-visibility: hidden; + + display: none; + padding: $reveal-padding; + + border: $reveal-border; + border-radius: $reveal-radius; + background-color: $reveal-background; + + @include breakpoint(medium) { + min-height: 0; + } + + // Make sure rows don't have a min-width on them + .column { + min-width: 0; + } + + // Strip margins from the last item in the modal + > :last-child { + margin-bottom: 0; + } +} + +/// Adjusts the width of a modal. +/// @param {Number} $width - Width of the modal. Generally a percentage. +/// @param {Number} $max-width [$reveal-max-width] - Maximum width of the modal. +@mixin reveal-modal-width( + $width: $reveal-width, + $max-width: $reveal-max-width +) { + // Extends must be made outside of breakpoints for compatibility with newer Sass versions (libsass v3.5) + @extend %reveal-centered; + @include breakpoint(medium) { + width: $width; + max-width: $max-width; + } +} + +/// Creates a full-screen modal, which stretches the full width and height of the window. +@mixin reveal-modal-fullscreen { + top: 0; + right: 0; + bottom: 0; + left: 0; + + width: 100%; + max-width: none; + height: 100%; + min-height: 100%; + margin-left: 0; + + border: 0; + border-radius: 0; +} + +@mixin foundation-reveal { + + /// Disables the scroll when Reveal is shown to prevent the background from shifting + html.is-reveal-open { + position: fixed; + width: 100%; + overflow-y: hidden; + + &.zf-has-scroll { + overflow-y: scroll; + } + + body { // sass-lint:disable-line no-qualifying-elements + overflow-y: hidden; + } + } + + // Overlay + .reveal-overlay { + @include reveal-overlay; + } + + // Modal container + .reveal { + @include reveal-modal-base; + @include reveal-modal-width($reveal-width); + position: relative; + top: 100px; + margin-right: auto; + margin-left: auto; + overflow-y: auto; + + // Remove padding + &.collapse { + padding: 0; + } + + // Sizing classes + &.tiny { @include reveal-modal-width(30%); } + &.small { @include reveal-modal-width(50%); } + &.large { @include reveal-modal-width(90%); } + + // Full-screen mode + &.full { + @include reveal-modal-fullscreen; + } + + @include breakpoint($-zf-zero-breakpoint only) { + @include reveal-modal-fullscreen; + } + + &.without-overlay { + position: fixed; + } + } +} diff --git a/src/foundation/components/_slider.scss b/src/foundation/components/_slider.scss new file mode 100755 index 0000000..c32b228 --- /dev/null +++ b/src/foundation/components/_slider.scss @@ -0,0 +1,137 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +// [TODO] Check how plugin confirms disabled or vertical status +// [TODO] Check if transition: all; is necessary + +//// +/// @group slider +//// + +/// Default slider width of a vertical slider. (Doesn't apply to the native slider.) +/// @type Number +$slider-width-vertical: 0.5rem !default; + +/// Transition properties to apply to the slider handle and fill. (Doesn't apply to the native slider.) +/// @type Transition +$slider-transition: all 0.2s ease-in-out !default; + +/// Adds the general styles for sliders. +@mixin slider-container { + position: relative; + height: $slider-height; + margin-top: 1.25rem; + margin-bottom: 2.25rem; + + background-color: $slider-background; + cursor: pointer; + user-select: none; + touch-action: none; +} + +/// Adds the general styles for active fill for sliders. +@mixin slider-fill { + position: absolute; + top: 0; + left: 0; + + display: inline-block; + max-width: 100%; + height: $slider-height; + + background-color: $slider-fill-background; + transition: $slider-transition; + + &.is-dragging { + transition: all 0s linear; + } +} + +/// Adds the general styles for the slider handles. +@mixin slider-handle { + @include disable-mouse-outline; + @include vertical-center; + left: 0; + z-index: 1; + + display: inline-block; + width: $slider-handle-width; + height: $slider-handle-height; + + border-radius: $slider-radius; + background-color: $slider-handle-background; + transition: $slider-transition; + touch-action: manipulation; + + &:hover { + background-color: scale-color($slider-handle-background, $lightness: -15%); + } + + &.is-dragging { + transition: all 0s linear; + } +} + +@mixin slider-disabled { + opacity: $slider-opacity-disabled; + cursor: not-allowed; +} + +@mixin slider-vertical { + display: inline-block; + width: $slider-width-vertical; + height: 12.5rem; + margin: 0 1.25rem; + transform: scale(1, -1); + + .slider-fill { + top: 0; + width: $slider-width-vertical; + max-height: 100%; + } + + .slider-handle { + position: absolute; + top: 0; + left: 50%; + width: $slider-handle-height; + height: $slider-handle-width; + transform: translateX(-50%); + } +} + +@mixin foundation-slider { + // Container + .slider { + @include slider-container; + } + + // Fill area + .slider-fill { + @include slider-fill; + } + + // Draggable handle + .slider-handle { + @include slider-handle; + } + + // Disabled state + .slider.disabled, + .slider[disabled] { + @include slider-disabled; + } + + // Vertical slider + .slider.vertical { + @include slider-vertical; + } + + // RTL support + @if $global-text-direction == rtl { + .slider:not(.vertical) { + transform: scale(-1, 1); + } + } +} diff --git a/src/foundation/components/_sticky.scss b/src/foundation/components/_sticky.scss new file mode 100644 index 0000000..378e0e2 --- /dev/null +++ b/src/foundation/components/_sticky.scss @@ -0,0 +1,39 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +@mixin foundation-sticky { + .sticky-container { + position: relative; + } + + .sticky { + position: relative; + z-index: 0; + transform: translate3d(0, 0, 0); + } + + .sticky.is-stuck { + position: fixed; + z-index: 5; + width: 100%; + + &.is-at-top { + top: 0; + } + + &.is-at-bottom { + bottom: 0; + } + } + + .sticky.is-anchored { + position: relative; + right: auto; + left: auto; + + &.is-at-bottom { + bottom: 0; + } + } +} diff --git a/src/foundation/components/_switch.scss b/src/foundation/components/_switch.scss new file mode 100644 index 0000000..b1056da --- /dev/null +++ b/src/foundation/components/_switch.scss @@ -0,0 +1,261 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group switch +//// + +/// Background color of a switch. +/// @type Color +$switch-background: $medium-gray !default; + +/// Background active color of a switch. +/// @type Color +$switch-background-active: $primary-color !default; + +/// Height of a switch, with no class applied. +/// @type Number +$switch-height: 2rem !default; + +/// Height of a switch with .tiny class. +/// @type Number +$switch-height-tiny: 1.5rem !default; + +/// Height of a switch with .small class. +/// @type Number +$switch-height-small: 1.75rem !default; + +/// Height of a switch with .large class. +/// @type Number +$switch-height-large: 2.5rem !default; + +/// Border radius of the switch +/// @type Number +$switch-radius: $global-radius !default; + +/// border around a modal. +/// @type Number +$switch-margin: $global-margin !default; + +/// Background color for the switch container and paddle. +/// @type Color +$switch-paddle-background: $white !default; + +/// Spacing between a switch paddle and the edge of the body. +/// @type Number +$switch-paddle-offset: 0.25rem !default; + +/// border radius of the switch paddle +/// @type Number +$switch-paddle-radius: $global-radius !default; + +/// switch transition. +/// @type Number +$switch-paddle-transition: all 0.25s ease-out !default; + +/// Opacity of a disabled switch. +/// @type Number +$switch-opacity-disabled: .5 !default; + +/// Cursor for a disabled switch. +/// @type Cursor +$switch-cursor-disabled: not-allowed !default; + +// make them variables +// ask about accessibility on label +// change class name for text + +/// Adds styles for a switch container. Apply this to a container class. +@mixin switch-container { + position: relative; + margin-bottom: $switch-margin; + outline: 0; + + // These properties cascade down to the switch text + font-size: rem-calc(14); + font-weight: bold; + color: $white; + + user-select: none; +} + +/// Adds styles for a switch input. Apply this to an `<input>` within a switch. +@mixin switch-input { + position: absolute; + margin-bottom: 0; + opacity: 0; +} + +/// Adds styles for the background and paddle of a switch. Apply this to a `<label>` within a switch. +@mixin switch-paddle { + $switch-width: $switch-height * 2; + $paddle-height: $switch-height - ($switch-paddle-offset * 2); + $paddle-width: $switch-height - ($switch-paddle-offset * 2); + $paddle-active-offest: $switch-width - $paddle-width - $switch-paddle-offset; + + position: relative; + display: block; + width: $switch-width; + height: $switch-height; + + border-radius: $switch-radius; + background: $switch-background; + transition: $switch-paddle-transition; + + // Resetting these <label> presets so type styles cascade down + font-weight: inherit; + color: inherit; + + cursor: pointer; + + // Needed to override specificity + input + & { + margin: 0; + } + + // The paddle itself + &::after { + position: absolute; + top: $switch-paddle-offset; + #{$global-left}: $switch-paddle-offset; + + display: block; + width: $paddle-width; + height: $paddle-height; + + transform: translate3d(0, 0, 0); + border-radius: $switch-paddle-radius; + background: $switch-paddle-background; + transition: $switch-paddle-transition; + content: ''; + } + + // Change the visual style when the switch is active + input:checked ~ & { + background: $switch-background-active; + + &::after { + #{$global-left}: $paddle-active-offest; + } + } + + // indicate a disabled switch + input:disabled ~ & { + cursor: $switch-cursor-disabled; + opacity: $switch-opacity-disabled; + } + + input:focus ~ & { + @include disable-mouse-outline; + } +} + +/// Adds base styles for active/inactive text inside a switch. Apply this to text elements inside the switch `<label>`. +@mixin switch-text { + position: absolute; + top: 50%; + transform: translateY(-50%); +} + +/// Adds styles for the active state text within a switch. +@mixin switch-text-active { + #{$global-left}: 8%; + display: none; + + input:checked + label > & { + display: block; + } +} + +/// Adds styles for the inactive state text within a switch. +@mixin switch-text-inactive { + #{$global-right}: 15%; + + input:checked + label > & { + display: none; + } +} + +/// Changes the size of a switch by modifying the size of the body and paddle. Apply this to a switch container. +/// @param {Number} $font-size [1rem] - Font size of label text within the switch. +/// @param {Number} $switch-height [2rem] - Height of the switch body. +/// @param {Number} $paddle-offset [0.25rem] - Spacing between the switch paddle and the edge of the switch body. +@mixin switch-size( + $font-size: 1rem, + $switch-height: 2rem, + $paddle-offset: 0.25rem +) { + + $switch-width: $switch-height * 2; + $paddle-width: $switch-height - ($paddle-offset * 2); + $paddle-height: $switch-height - ($paddle-offset * 2); + $paddle-active-offest: $switch-width - $paddle-width - $paddle-offset; + + height: $switch-height; + + .switch-paddle { + width: $switch-width; + height: $switch-height; + font-size: $font-size; + } + + .switch-paddle::after { + top: $paddle-offset; + #{$global-left}: $paddle-offset; + width: $paddle-width; + height: $paddle-height; + } + + input:checked ~ .switch-paddle::after { + #{$global-left}: $paddle-active-offest; + } +} + +@mixin foundation-switch { + // Container class + .switch { + height: $switch-height; + @include switch-container; + } + + // <input> element + .switch-input { + @include switch-input; + } + + // <label> element + .switch-paddle { + @include switch-paddle; + } + + // Base label text styles + %switch-text { + @include switch-text; + } + + // Active label text styles + .switch-active { + @extend %switch-text; + @include switch-text-active; + } + + // Inactive label text styles + .switch-inactive { + @extend %switch-text; + @include switch-text-inactive; + } + + // Switch sizes + .switch.tiny { + @include switch-size(rem-calc(10), $switch-height-tiny, $switch-paddle-offset); + } + + .switch.small { + @include switch-size(rem-calc(12), $switch-height-small, $switch-paddle-offset); + } + + .switch.large { + @include switch-size(rem-calc(16), $switch-height-large, $switch-paddle-offset); + } +} diff --git a/src/foundation/components/_table.scss b/src/foundation/components/_table.scss new file mode 100644 index 0000000..4c44aa3 --- /dev/null +++ b/src/foundation/components/_table.scss @@ -0,0 +1,328 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +// sass-lint:disable no-qualifying-elements + +//// +/// @group table +//// + +/// Default color for table background. +/// @type Color +$table-background: $white !default; + +/// Default scale for darkening the striped table rows and the table border. +/// @type Number +$table-color-scale: 5% !default; + +/// Default style for table border. +/// @type List +$table-border: 1px solid smart-scale($table-background, $table-color-scale) !default; + +/// Default padding for table. +/// @type Number +$table-padding: rem-calc(8 10 10) !default; + +/// Default scale for darkening the table rows on hover. +/// @type Number +$table-hover-scale: 2% !default; + +/// Default color of standard rows on hover. +/// @type List +$table-row-hover: darken($table-background, $table-hover-scale) !default; + +/// Default color of striped rows on hover. +/// @type List +$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale) !default; + +/// If `true`, tables are striped by default and an .unstriped class is created. If `false`, a .striped class is created. +/// @type Boolean +$table-is-striped: true !default; + +/// Default background color for striped rows. +/// @type Color +$table-striped-background: smart-scale($table-background, $table-color-scale) !default; + +/// Default value for showing the stripe on rows of the tables, excluding the header and footer. If even, the even rows will have a background color. If odd, the odd rows will have a background color. If empty, or any other value, the table rows will have no striping. +/// @type Keyword +$table-stripe: even !default; + +/// Default color for header background. +/// @type Color +$table-head-background: smart-scale($table-background, $table-color-scale / 2) !default; + +/// Default color of header rows on hover. +/// @type List +$table-head-row-hover: darken($table-head-background, $table-hover-scale) !default; + +/// Default color for footer background. +/// @type Color +$table-foot-background: smart-scale($table-background, $table-color-scale) !default; + +/// Default color of footer rows on hover. +/// @type List +$table-foot-row-hover: darken($table-foot-background, $table-hover-scale) !default; + +/// Default font color for header. +/// @type Color +$table-head-font-color: $body-font-color !default; + +/// Default font color for footer. +/// @type Color +$table-foot-font-color: $body-font-color !default; + +/// Default value for showing the header when using stacked tables. +/// @type Boolean +$show-header-for-stacked: false !default; + +/// Breakpoint at which stacked table switches from mobile to desktop view. +/// @type Breakpoint +$table-stack-breakpoint: medium !default; + +@mixin -zf-table-stripe($stripe: $table-stripe) { + tr { + // If stripe is set to even, darken the even rows. + @if $stripe == even { + &:nth-child(even) { + border-bottom: 0; + background-color: $table-striped-background; + } + } + + // If stripe is set to odd, darken the odd rows. + @else if $stripe == odd { + &:nth-child(odd) { + background-color: $table-striped-background; + } + } + } +} + +@mixin -zf-table-unstripe() { + tr { + border-bottom: 0; + border-bottom: $table-border; + background-color: $table-background; + } +} + +@mixin -zf-table-children-styles($stripe: $table-stripe, $is-striped: $table-is-striped) { + thead, + tbody, + tfoot { + border: $table-border; + background-color: $table-background; + } + + // Caption + caption { + padding: $table-padding; + font-weight: $global-weight-bold; + } + + // Table head + thead { + background: $table-head-background; + color: $table-head-font-color; + } + + // Table foot + tfoot { + background: $table-foot-background; + color: $table-foot-font-color; + } + + // Table head and foot + thead, + tfoot { + // Rows within head and foot + tr { + background: transparent; + } + + // Cells within head and foot + th, + td { + padding: $table-padding; + font-weight: $global-weight-bold; + text-align: #{$global-left}; + } + } + + // Table rows + tbody { + th, + td { + padding: $table-padding; + } + } + + // If tables are striped + @if $is-striped == true { + tbody { + @include -zf-table-stripe($stripe); + } + + &.unstriped { + tbody { + @include -zf-table-unstripe(); + background-color: $table-background; + } + } + } + + // If tables are not striped + @else if $is-striped == false { + tbody { + @include -zf-table-unstripe(); + } + + &.striped { + tbody { + @include -zf-table-stripe($stripe); + } + } + } +} + +/// Adds the general styles for tables. +/// @param {Keyword} $stripe [$table-stripe] - Uses keywords even, odd, or none to darken rows of the table. The default value is even. +/// @param {Boolean} $nest [false] - Needed if you only want to apply this to a specific table. +@mixin table( + $stripe: $table-stripe, + $nest: false +) { + border-collapse: collapse; + width: 100%; + margin-bottom: $global-margin; + border-radius: $global-radius; + + @if $nest { + @include -zf-table-children-styles($stripe); + } + @else { + @at-root { + @include -zf-table-children-styles($stripe); + } + } +} + +/// Adds the ability to horizontally scroll the table when the content overflows horizontally. +@mixin table-scroll { + display: block; + width: 100%; + overflow-x: auto; +} + +/// Slightly darkens the table rows on hover. +@mixin table-hover { + thead tr { + //Darkens the table header rows on hover. + &:hover { + background-color: $table-head-row-hover; + } + } + + tfoot tr { + //Darkens the table footer rows on hover. + &:hover { + background-color: $table-foot-row-hover; + } + } + + tbody tr { + //Darkens the non-striped table rows on hover. + &:hover { + background-color: $table-row-hover; + } + } + + @if $table-is-striped == true { + // Darkens the even striped table rows. + @if($table-stripe == even) { + &:not(.unstriped) tr:nth-of-type(even):hover { + background-color: $table-row-stripe-hover; + } + } + + // Darkens the odd striped table rows. + @else if($table-stripe == odd) { + &:not(.unstriped) tr:nth-of-type(odd):hover { + background-color: $table-row-stripe-hover; + } + } + } + + @else if $table-is-striped == false { + // Darkens the even striped table rows. + @if($table-stripe == even) { + &.striped tr:nth-of-type(even):hover { + background-color: $table-row-stripe-hover; + } + } + + // Darkens the odd striped table rows. + @else if($table-stripe == odd) { + &.striped tr:nth-of-type(odd):hover { + background-color: $table-row-stripe-hover; + } + } + } +} + +/// Adds styles for a stacked table. Useful for small-screen layouts. +/// @param {Boolean} $header [$show-header-for-stacked] - Show the first th of header when stacked. +@mixin table-stack($header: $show-header-for-stacked) { + @if $header { + thead { + th { + display: block; + } + } + } + @else { + thead { + display: none; + } + } + + tfoot { + display: none; + } + + tr, + th, + td { + display: block; + } + + td { + border-top: 0; + } +} + +@mixin foundation-table($nest: false) { + table { + @include table($nest: $nest); + } + + table.stack { + @include breakpoint($table-stack-breakpoint down) { + @include table-stack; + } + } + + table.scroll { + @include table-scroll; + } + + table.hover { + @include table-hover; + } + + .table-scroll { + overflow-x: auto; + + } +} diff --git a/src/foundation/components/_tabs.scss b/src/foundation/components/_tabs.scss new file mode 100644 index 0000000..ecf85ed --- /dev/null +++ b/src/foundation/components/_tabs.scss @@ -0,0 +1,193 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group tabs +//// + +/// Default margin of the tab bar. +/// @type Number +$tab-margin: 0 !default; + +/// Default background color of a tab bar. +/// @type Color +$tab-background: $white !default; + +/// Font color of tab item. +/// @type Color +$tab-color: $primary-color !default; + +/// Active background color of a tab bar. +/// @type Color +$tab-background-active: $light-gray !default; + +/// Active font color of tab item. +/// @type Color +$tab-active-color: $primary-color !default; + +/// Font size of tab items. +/// @type Number +$tab-item-font-size: rem-calc(12) !default; + +/// Default background color on hover for items in a Menu. +$tab-item-background-hover: $white !default; + +/// Default padding of a tab item. +/// @type Number +$tab-item-padding: 1.25rem 1.5rem !default; + +/// Default background color of tab content. +/// @type Color +$tab-content-background: $white !default; + +/// Default border color of tab content. +/// @type Color +$tab-content-border: $light-gray !default; + +/// Default text color of tab content. +/// @type Color +$tab-content-color: $body-font-color !default; + +/// Default padding for tab content. +/// @type Number | List +$tab-content-padding: 1rem !default; + +/// Adds styles for a tab container. Apply this to a `<ul>`. +@mixin tabs-container ( + $margin: $tab-margin, + $background: $tab-background, + $border-color: $tab-content-border +) { + @include clearfix; + margin: $margin; + border: 1px solid $border-color; + background: $background; + list-style-type: none; +} + +/// Augments a tab container to have vertical tabs. Use this in conjunction with `tabs-container()`. +@mixin tabs-container-vertical { + > li { + display: block; + float: none; + width: auto; + } +} + +/// Adds styles for the links within a tab container. Apply this to the `<li>` elements inside a tab container. +@mixin tabs-title ( + $padding: $tab-item-padding, + $font-size: $tab-item-font-size, + $color: $tab-color, + $color-active: $tab-active-color, + $background-hover: $tab-item-background-hover, + $background-active: $tab-background-active +) { + float: #{$global-left}; + + > a { + @include disable-mouse-outline; + display: block; + padding: $padding; + font-size: $font-size; + line-height: 1; + color: $color; + + &:hover { + background: $background-hover; + color: scale-color($color, $lightness: -14%); + } + + &:focus, + &[aria-selected='true'] { + background: $background-active; + color: $color-active; + } + } +} + +/// Adds styles for the wrapper that surrounds a tab group's content panes. +@mixin tabs-content ( + $background: $tab-content-background, + $color: $tab-content-color, + $border-color: $tab-content-border +) { + border: 1px solid $border-color; + border-top: 0; + background: $background; + color: $color; + transition: all 0.5s ease; +} + +/// Augments a tab content container to have a vertical style, by shifting the border around. Use this in conjunction with `tabs-content()`. +@mixin tabs-content-vertical ( + $border-color: $tab-content-border +) { + border: 1px solid $border-color; + border-#{$global-left}: 0; +} + +/// Adds styles for an individual tab content panel within the tab content container. +@mixin tabs-panel ( + $padding: $tab-content-padding +) { + display: none; + padding: $padding; + + &.is-active { + display: block; + } +} + +@mixin foundation-tabs { + .tabs { + @include tabs-container; + } + + // Vertical + .tabs.vertical { + @include tabs-container-vertical; + } + + // Simple + .tabs.simple { + > li > a { + padding: 0; + + &:hover { + background: transparent; + } + } + } + + // Primary color + .tabs.primary { + background: $primary-color; + + > li > a { + color: color-pick-contrast($primary-color); + + &:hover, + &:focus { + background: smart-scale($primary-color); + } + } + } + + .tabs-title { + @include tabs-title; + } + + .tabs-content { + @include tabs-content; + } + + .tabs-content.vertical { + @include tabs-content-vertical; + } + + .tabs-panel { + @include tabs-panel; + } +} diff --git a/src/foundation/components/_thumbnail.scss b/src/foundation/components/_thumbnail.scss new file mode 100644 index 0000000..ed8109b --- /dev/null +++ b/src/foundation/components/_thumbnail.scss @@ -0,0 +1,67 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group thumbnail +//// + +/// Border around thumbnail images. +/// @type Border +$thumbnail-border: 4px solid $white !default; + +/// Bottom margin for thumbnail images. +/// @type Length +$thumbnail-margin-bottom: $global-margin !default; + +/// Box shadow under thumbnail images. +/// @type Shadow +$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2) !default; + +/// Box shadow under thumbnail images. +/// @type Shadow +$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5) !default; + +/// Transition proprties for thumbnail images. +/// @type Transition +$thumbnail-transition: box-shadow 200ms ease-out !default; + +/// Default radius for thumbnail images. +/// @type Number +$thumbnail-radius: $global-radius !default; + +/// Adds thumbnail styles to an element. +@mixin thumbnail { + display: inline-block; + max-width: 100%; + margin-bottom: $thumbnail-margin-bottom; + + border: $thumbnail-border; + border-radius: $thumbnail-radius; + box-shadow: $thumbnail-shadow; + + line-height: 0; +} + +@mixin thumbnail-link { + transition: $thumbnail-transition; + + &:hover, + &:focus { + box-shadow: $thumbnail-shadow-hover; + } + + image { + box-shadow: none; + } +} + +@mixin foundation-thumbnail { + .thumbnail { + @include thumbnail; + } + + a.thumbnail { + @include thumbnail-link; + } +} diff --git a/src/foundation/components/_title-bar.scss b/src/foundation/components/_title-bar.scss new file mode 100644 index 0000000..e0f370c --- /dev/null +++ b/src/foundation/components/_title-bar.scss @@ -0,0 +1,84 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group title-bar +//// + +/// Background color of a title bar. +/// @type Color +$titlebar-background: $black !default; + +/// Color of text inside a title bar. +/// @type Color +$titlebar-color: $white !default; + +/// Padding inside a title bar. +/// @type Length +$titlebar-padding: 0.5rem !default; + +/// Font weight of text inside a title bar. +/// @type Weight +$titlebar-text-font-weight: bold !default; + +/// Color of menu icons inside a title bar. +/// @type Color +$titlebar-icon-color: $white !default; + +/// Color of menu icons inside a title bar on hover. +/// @type Color +$titlebar-icon-color-hover: $medium-gray !default; + +/// Spacing between the menu icon and text inside a title bar. +/// @type Length +$titlebar-icon-spacing: 0.25rem !default; + +@mixin foundation-title-bar { + .title-bar { + padding: $titlebar-padding; + background: $titlebar-background; + color: $titlebar-color; + + @if $global-flexbox { + display: flex; + justify-content: flex-start; + align-items: center; + } + @else { + @include clearfix; + } + + .menu-icon { + margin-#{$global-left}: $titlebar-icon-spacing; + margin-#{$global-right}: $titlebar-icon-spacing; + } + } + + @if $global-flexbox { + .title-bar-left, + .title-bar-right { + flex: 1 1 0px; // sass-lint:disable-line zero-unit + } + + .title-bar-right { + text-align: right; + } + } + @else { + .title-bar-left { + float: left; + } + + .title-bar-right { + float: right; + text-align: right; + } + } + + .title-bar-title { + display: inline-block; + vertical-align: middle; + font-weight: $titlebar-text-font-weight; + } +} diff --git a/src/foundation/components/_tooltip.scss b/src/foundation/components/_tooltip.scss new file mode 100644 index 0000000..36dc155 --- /dev/null +++ b/src/foundation/components/_tooltip.scss @@ -0,0 +1,160 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group tooltip +//// + +/// Default cursor of the defined term. +/// @type Keyword +$has-tip-cursor: help !default; + +/// Default font weight of the defined term. +/// @type Keyword | Number +$has-tip-font-weight: $global-weight-bold !default; + +/// Default border bottom of the defined term. +/// @type List +$has-tip-border-bottom: dotted 1px $dark-gray !default; + +/// Default color of the tooltip background. +/// @type Color +$tooltip-background-color: $black !default; + +/// Default color of the tooltip font. +/// @type Color +$tooltip-color: $white !default; + +/// Default padding of the tooltip background. +/// @type Number +$tooltip-padding: 0.75rem !default; + +/// Default max width for tooltips. +/// @type Number +$tooltip-max-width: 10rem !default; + +/// Default font size of the tooltip text. By default, we recommend a smaller font size than the body copy. +/// @type Number +$tooltip-font-size: $small-font-size !default; + +/// Default pip width for tooltips. +/// @type Number +$tooltip-pip-width: 0.75rem !default; + +/// Default pip height for tooltips. This is helpful for calculating the distance of the tooltip from the tooltip word. +/// @type Number +$tooltip-pip-height: $tooltip-pip-width * 0.866 !default; + +/// Default radius for tooltips. +/// @type Number +$tooltip-radius: $global-radius !default; + +@mixin has-tip { + position: relative; + display: inline-block; + + border-bottom: $has-tip-border-bottom; + font-weight: $has-tip-font-weight; + cursor: $has-tip-cursor; +} + +@mixin tooltip { + position: absolute; + top: calc(100% + #{$tooltip-pip-height}); + z-index: 1200; + + max-width: $tooltip-max-width; + padding: $tooltip-padding; + + border-radius: $tooltip-radius; + background-color: $tooltip-background-color; + font-size: $tooltip-font-size; + color: $tooltip-color; + + &::before { + position: absolute; + } + + &.bottom { + &::before { + @include css-triangle($tooltip-pip-width, $tooltip-background-color, up); + bottom: 100%; + } + + &.align-center::before { + left: 50%; + transform: translateX(-50%); + } + } + + &.top { + &::before { + @include css-triangle($tooltip-pip-width, $tooltip-background-color, down); + top: 100%; + bottom: auto; + } + + &.align-center::before { + left: 50%; + transform: translateX(-50%); + } + } + + &.left { + &::before { + @include css-triangle($tooltip-pip-width, $tooltip-background-color, right); + left: 100%; + } + + &.align-center::before { + bottom: auto; + top: 50%; + transform: translateY(-50%); + } + } + + &.right { + &::before { + @include css-triangle($tooltip-pip-width, $tooltip-background-color, left); + right: 100%; + left: auto; + } + + &.align-center::before { + bottom: auto; + top: 50%; + transform: translateY(-50%); + } + } + + &.align-top::before { + bottom: auto; + top: 10%; + } + + &.align-bottom::before { + bottom: 10%; + top: auto; + } + + &.align-left::before { + left: 10%; + right: auto; + } + + &.align-right::before { + left: auto; + right: 10%; + } +} + +@mixin foundation-tooltip { + .has-tip { + @include has-tip; + } + + .tooltip { + @include tooltip; + } +} diff --git a/src/foundation/components/_top-bar.scss b/src/foundation/components/_top-bar.scss new file mode 100644 index 0000000..2503a7f --- /dev/null +++ b/src/foundation/components/_top-bar.scss @@ -0,0 +1,175 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group top-bar +//// + +/// Padding for the top bar. +/// @type Number +$topbar-padding: 0.5rem !default; + +/// Background color for the top bar. This color also cascades to menus within the top bar. +/// @type Color +$topbar-background: $light-gray !default; + +/// Background color submenus within the top bar. Usefull if $topbar-background is transparent. +/// @type Color +$topbar-submenu-background: $topbar-background !default; + +/// Spacing for the top bar title. +/// @type Number +$topbar-title-spacing: 0.5rem 1rem 0.5rem 0 !default; + +/// Maximum width of `<input>` elements inside the top bar. +/// @type Number +$topbar-input-width: 200px !default; + +/// Breakpoint at which top bar switches from mobile to desktop view. +/// @type Breakpoint +$topbar-unstack-breakpoint: medium !default; + +/// Adds styles for a top bar container. +@mixin top-bar-container { + @if $global-flexbox { + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; + } + @else { + @include clearfix; + } + + padding: $topbar-padding; + + &, + ul { + background-color: $topbar-background; + } + + // Check if $topbar-background is differnt from $topbar-background-submenu + @if ($topbar-background != $topbar-submenu-background) { + ul ul { + background-color: $topbar-submenu-background; + } + } + + // Restrain width of inputs by default to make them easier to arrange + input { + max-width: $topbar-input-width; + margin-#{$global-right}: 1rem; + } + + // The above styles shouldn't apply to input group fields + .input-group-field { + width: 100%; + margin-#{$global-right}: 0; + } + + input.button { // sass-lint:disable-line no-qualifying-elements + width: auto; + } +} + +/// Makes sections of a top bar stack on top of each other. +@mixin top-bar-stacked { + @if $global-flexbox { + flex-wrap: wrap; + + // Sub-sections + .top-bar-left, + .top-bar-right { + flex: 0 0 100%; + max-width: 100%; + } + } + @else { + // Sub-sections + .top-bar-left, + .top-bar-right { + width: 100%; + } + } +} + +/// Undoes the CSS applied by the `top-bar-stacked()` mixin. +@mixin top-bar-unstack { + @if $global-flexbox { + flex-wrap: nowrap; + + .top-bar-left { + flex: 1 1 auto; + margin-right: auto; + } + + .top-bar-right { + flex: 0 1 auto; + margin-left: auto; + } + } + @else { + .top-bar-left, + .top-bar-right { + width: auto; + } + } +} + +@mixin foundation-top-bar { + // Top bar container + .top-bar { + @include top-bar-container; + + // Stack on small screens by default + @include top-bar-stacked; + + @include breakpoint($topbar-unstack-breakpoint) { + @include top-bar-unstack; + } + + // Generate classes for stacking on each screen size (defined in $breakpoint-classes) + @each $size in $breakpoint-classes { + @if $size != $-zf-zero-breakpoint { + &.stacked-for-#{$size} { + @include breakpoint($size down) { + @include top-bar-stacked; + } + } + } + } + } + + // Sub-sections + @if $global-flexbox { + .top-bar-title { + flex: 0 0 auto; + margin: $topbar-title-spacing; + } + + .top-bar-left, + .top-bar-right { + flex: 0 0 auto; + } + } + @else { + .top-bar-title { + display: inline-block; + float: left; + padding: $topbar-title-spacing; + + .menu-icon { + bottom: 2px; + } + } + + .top-bar-left { + float: left; + } + + .top-bar-right { + float: right; + } + } +} diff --git a/src/foundation/components/_visibility.scss b/src/foundation/components/_visibility.scss new file mode 100644 index 0000000..30bf7cd --- /dev/null +++ b/src/foundation/components/_visibility.scss @@ -0,0 +1,135 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +/// Hide an element by default, only displaying it above a certain screen size. +/// @param {Keyword} $size - Breakpoint to use. **Must be a breakpoint defined in `$breakpoints`.** +@mixin show-for($size) { + $size: map-get($breakpoints, $size); + // Max value is 0.2px under the next breakpoint (0.02 / 16 = 0.00125). + // Use a precision under 1px to support browser zoom, but not to low to avoid rounding. + // See https://github.com/zurb/foundation-sites/issues/11313 + $size: -zf-bp-to-em($size) - .00125; + + @include breakpoint($size down) { + display: none !important; + } +} + +/// Hide an element by default, only displaying it within a certain breakpoint. +/// @param {Keyword} $size - Breakpoint to use. **Must be a breakpoint defined in `$breakpoints`.** +@mixin show-for-only($size) { + $lower-bound-size: map-get($breakpoints, $size); + $upper-bound-size: -zf-map-next($breakpoints, $size); + + // more often than not this will be correct, just one time round the loop it won't so set in scope here + $lower-bound: -zf-bp-to-em($lower-bound-size) - .00125; + // test actual lower-bound-size, if 0 set it to 0em + @if strip-unit($lower-bound-size) == 0 { + $lower-bound: -zf-bp-to-em($lower-bound-size); + } + + @if $upper-bound-size == null { + @media screen and (max-width: $lower-bound) { + display: none !important; + } + } + @else { + $upper-bound: -zf-bp-to-em($upper-bound-size); + + @media screen and (max-width: $lower-bound), screen and (min-width: $upper-bound) { + display: none !important; + } + } +} + + +/// Show an element by default, and hide it above a certain screen size. +/// @param {Keyword} $size - Breakpoint to use. **Must be a breakpoint defined in `$breakpoints`.** +@mixin hide-for($size) { + @include breakpoint($size) { + display: none !important; + } +} + +/// Show an element by default, and hide it above a certain screen size. +/// @param {Keyword} $size - Breakpoint to use. **Must be a breakpoint defined in `$breakpoints`.** +@mixin hide-for-only($size) { + @include breakpoint($size only) { + display: none !important; + } +} + +@mixin foundation-visibility-classes { + // Basic hiding classes + .hide { + display: none !important; + } + + .invisible { + visibility: hidden; + } + + // Responsive visibility classes + @each $size in $breakpoint-classes { + @if $size != $-zf-zero-breakpoint { + .hide-for-#{$size} { + @include hide-for($size); + } + + .show-for-#{$size} { + @include show-for($size); + } + } + + .hide-for-#{$size}-only { + @include hide-for-only($size); + } + + .show-for-#{$size}-only { + @include show-for-only($size); + } + } + + // Screen reader visibility classes + // Need a "hide-for-sr" class? Add aria-hidden='true' to the element + .show-for-sr, + .show-on-focus { + @include element-invisible; + } + + // Only display the element when it's focused + .show-on-focus { + &:active, + &:focus { + @include element-invisible-off; + } + } + + // Landscape and portrait visibility + .show-for-landscape, + .hide-for-portrait { + display: block !important; + + @include breakpoint(landscape) { + display: block !important; + } + + @include breakpoint(portrait) { + display: none !important; + } + } + + .hide-for-landscape, + .show-for-portrait { + display: none !important; + + @include breakpoint(landscape) { + display: none !important; + } + + @include breakpoint(portrait) { + display: block !important; + } + } +} diff --git a/src/foundation/forms/_checkbox.scss b/src/foundation/forms/_checkbox.scss new file mode 100644 index 0000000..60e8bfc --- /dev/null +++ b/src/foundation/forms/_checkbox.scss @@ -0,0 +1,41 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group forms +//// + +@mixin foundation-form-checkbox { + [type='file'], + [type='checkbox'], + [type='radio'] { + margin: 0 0 $form-spacing; + } + + // Styles for input/label siblings + [type='checkbox'] + label, + [type='radio'] + label { + display: inline-block; + vertical-align: baseline; + + margin-#{$global-left}: $form-spacing * 0.5; + margin-#{$global-right}: $form-spacing; + margin-bottom: 0; + + &[for] { + cursor: pointer; + } + } + + // Styles for inputs inside labels + label > [type='checkbox'], + label > [type='radio'] { + margin-#{$global-right}: $form-spacing * 0.5; + } + + // Normalize file input width + [type='file'] { + width: 100%; + } +} diff --git a/src/foundation/forms/_error.scss b/src/foundation/forms/_error.scss new file mode 100644 index 0000000..eb2fdc1 --- /dev/null +++ b/src/foundation/forms/_error.scss @@ -0,0 +1,89 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group abide +//// + +/// Sets if error styles should be added to inputs. +/// @type Boolean +$abide-inputs: true !default; + +/// Sets if error styles should be added to labels. +/// @type Boolean +$abide-labels: true !default; + +/// Background color to use for invalid text inputs. +/// @type Color +$input-background-invalid: get-color(alert) !default; + +/// Color to use for labels of invalid inputs. +/// @type Color +$form-label-color-invalid: get-color(alert) !default; + +/// Default font color for form error text. +/// @type Color +$input-error-color: get-color(alert) !default; + +/// Default font size for form error text. +/// @type Number +$input-error-font-size: rem-calc(12) !default; + +/// Default font weight for form error text. +/// @type Keyword +$input-error-font-weight: $global-weight-bold !default; + +/// Styles the background and border of an input field to have an error state. +/// +/// @param {Color} $background [$alert-color] - Color to use for the background and border. +/// @param {Number} $background-lighten [10%] - Lightness level of the background color. +@mixin form-input-error( + $background: $input-background-invalid, + $background-lighten: 10% +) { + &:not(:focus) { + border-color: $background; + background-color: mix($background, $white, $background-lighten); + + &::placeholder { + color: $background; + } + } +} + +/// Adds error styles to a form element, using the values in the settings file. +@mixin form-error { + display: none; + margin-top: $form-spacing * -0.5; + margin-bottom: $form-spacing; + + font-size: $input-error-font-size; + font-weight: $input-error-font-weight; + color: $input-error-color; +} + +@mixin foundation-form-error { + @if $abide-inputs { + // Error class for invalid inputs + .is-invalid-input { + @include form-input-error; + } + } + + @if $abide-labels { + // Error class for labels of invalid outputs + .is-invalid-label { + color: $form-label-color-invalid; + } + } + + // Form error element + .form-error { + @include form-error; + + &.is-visible { + display: block; + } + } +} diff --git a/src/foundation/forms/_fieldset.scss b/src/foundation/forms/_fieldset.scss new file mode 100644 index 0000000..8611d5a --- /dev/null +++ b/src/foundation/forms/_fieldset.scss @@ -0,0 +1,53 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group forms +//// + +/// Default border around custom fieldsets. +/// @type Border +$fieldset-border: 1px solid $medium-gray !default; + +/// Default padding inside custom fieldsets. +/// @type Number +$fieldset-padding: rem-calc(20) !default; + +/// Default margin around custom fieldsets. +/// @type Number +$fieldset-margin: rem-calc(18 0) !default; + +/// Default padding between the legend text and fieldset border. +/// @type Number +$legend-padding: rem-calc(0 3) !default; + +@mixin fieldset { + margin: $fieldset-margin; + padding: $fieldset-padding; + border: $fieldset-border; + + legend { + // Covers up the fieldset's border to create artificial padding + margin: 0; + margin-#{$global-left}: rem-calc(-3); + padding: $legend-padding; + } +} + +@mixin foundation-form-fieldset { + fieldset { + margin: 0; + padding: 0; + border: 0; + } + + legend { + max-width: 100%; + margin-bottom: $form-spacing * 0.5; + } + + .fieldset { + @include fieldset; + } +} diff --git a/src/foundation/forms/_forms.scss b/src/foundation/forms/_forms.scss new file mode 100644 index 0000000..1507fda --- /dev/null +++ b/src/foundation/forms/_forms.scss @@ -0,0 +1,34 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group forms +//// + +/// Global spacing for form elements. +/// @type Number +$form-spacing: rem-calc(16) !default; + +@import 'text'; +@import 'checkbox'; +@import 'label'; +@import 'help-text'; +@import 'input-group'; +@import 'fieldset'; +@import 'select'; +@import 'range'; +@import 'progress'; +@import 'meter'; +@import 'error'; + +@mixin foundation-forms { + @include foundation-form-text; + @include foundation-form-checkbox; + @include foundation-form-label; + @include foundation-form-helptext; + @include foundation-form-prepostfix; + @include foundation-form-fieldset; + @include foundation-form-select; + @include foundation-form-error; +} diff --git a/src/foundation/forms/_help-text.scss b/src/foundation/forms/_help-text.scss new file mode 100644 index 0000000..04e5237 --- /dev/null +++ b/src/foundation/forms/_help-text.scss @@ -0,0 +1,30 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group forms +//// + +/// Default color for help text. +/// @type Color +$helptext-color: $black !default; + +/// Default font size for help text. +/// @type Number +$helptext-font-size: rem-calc(13) !default; + +/// Default font style for help text. +/// @type Keyword +$helptext-font-style: italic !default; + +@mixin foundation-form-helptext { + .help-text { + $margin-top: ($form-spacing * 0.5) * -1; + + margin-top: $margin-top; + font-size: $helptext-font-size; + font-style: $helptext-font-style; + color: $helptext-color; + } +} diff --git a/src/foundation/forms/_input-group.scss b/src/foundation/forms/_input-group.scss new file mode 100644 index 0000000..46669e9 --- /dev/null +++ b/src/foundation/forms/_input-group.scss @@ -0,0 +1,142 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group forms +//// + +/// Color of labels prefixed to an input. +/// @type Color +$input-prefix-color: $black !default; + +/// Background color of labels prefixed to an input. +/// @type Color +$input-prefix-background: $light-gray !default; + +/// Border around labels prefixed to an input. +/// @type Border +$input-prefix-border: 1px solid $medium-gray !default; + +/// Left/right padding of an pre/postfixed input label +$input-prefix-padding: 1rem !default; + +@mixin foundation-form-prepostfix { + $height: ($input-font-size * $input-line-height) + (get-side($input-padding, 'top') + get-side($input-padding, 'bottom')) - rem-calc(1); + + .input-group { + display: if($global-flexbox, flex, table); + width: 100%; + margin-bottom: $form-spacing; + + @if $global-flexbox { + align-items: stretch; + } + + > :first-child { + &, &.input-group-button > * { + border-radius: if($global-text-direction == rtl, 0 $input-radius $input-radius 0, $input-radius 0 0 $input-radius); + } + } + + > :last-child { + &, &.input-group-button > * { + border-radius: if($global-text-direction == rtl, $input-radius 0 0 $input-radius, 0 $input-radius $input-radius 0); + } + } + } + + %input-group-child { + margin: 0; + white-space: nowrap; + + @if not $global-flexbox { + display: table-cell; + vertical-align: middle; + } + } + + .input-group-label { + @extend %input-group-child; + padding: 0 $input-prefix-padding; + border: $input-prefix-border; + background: $input-prefix-background; + + color: $input-prefix-color; + text-align: center; + white-space: nowrap; + + @if $global-flexbox { + display: flex; + flex: 0 0 auto; + align-items: center; + } + @else { + width: 1%; + height: 100%; + } + + @if has-value($input-prefix-border) { + &:first-child { + border-#{$global-right}: 0; + } + + &:last-child { + border-#{$global-left}: 0; + } + } + } + + .input-group-field { + @extend %input-group-child; + border-radius: 0; + + @if $global-flexbox { + flex: 1 1 0px; // sass-lint:disable-line zero-unit + min-width: 0; + } + } + + .input-group-button { + @extend %input-group-child; + padding-top: 0; + padding-bottom: 0; + text-align: center; + + @if $global-flexbox { + display: flex; + flex: 0 0 auto; + } + @else { + width: 1%; + height: 100%; + } + + a, + input, + button, + label { + @extend %input-group-child; + + @if $global-flexbox { + align-self: stretch; + height: auto; + } + @else { + height: $height; + } + padding-top: 0; + padding-bottom: 0; + font-size: $input-font-size; + } + } + + // Specificity bump needed to prevent override by buttons + @if not $global-flexbox { + .input-group { + .input-group-button { + display: table-cell; + } + } + } +} diff --git a/src/foundation/forms/_label.scss b/src/foundation/forms/_label.scss new file mode 100644 index 0000000..1c38851 --- /dev/null +++ b/src/foundation/forms/_label.scss @@ -0,0 +1,50 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group forms +//// + +/// Color for form labels. +/// @type Color +$form-label-color: $black !default; + +/// Font size for form labels. +/// @type Number +$form-label-font-size: rem-calc(14) !default; + +/// Font weight for form labels. +/// @type Keyword +$form-label-font-weight: $global-weight-normal !default; + +/// Line height for form labels. The higher the number, the more space between the label and its input field. +/// @type Number +$form-label-line-height: 1.8 !default; + +@mixin form-label { + display: block; + margin: 0; + + font-size: $form-label-font-size; + font-weight: $form-label-font-weight; + line-height: $form-label-line-height; + color: $form-label-color; +} + +@mixin form-label-middle { + $input-border-width: get-border-value($input-border, width); + + margin: 0 0 $form-spacing; + padding: ($form-spacing / 2 + rem-calc($input-border-width)) 0; +} + +@mixin foundation-form-label { + label { + @include form-label; + + &.middle { + @include form-label-middle; + } + } +} diff --git a/src/foundation/forms/_meter.scss b/src/foundation/forms/_meter.scss new file mode 100644 index 0000000..adc7457 --- /dev/null +++ b/src/foundation/forms/_meter.scss @@ -0,0 +1,116 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group meter +//// + +/// Height of a `<meter>` element. +/// @type Length +$meter-height: 1rem !default; + +/// Border radius of a `<meter>` element. +/// @type Length +$meter-radius: $global-radius !default; + +/// Background color of a `<meter>` element. +/// @type Color +$meter-background: $medium-gray !default; + +/// Meter fill for an optimal value in a `<meter>` element. +/// @type Color +$meter-fill-good: $success-color !default; + +/// Meter fill for an average value in a `<meter>` element. +/// @type Color +$meter-fill-medium: $warning-color !default; + +/// Meter fill for a suboptimal value in a `<meter>` element. +/// @type Color +$meter-fill-bad: $alert-color !default; + +@mixin foundation-meter-element { + meter { + display: block; + width: 100%; + height: $meter-height; + margin-bottom: 1rem; + + // Disable `-webkit-appearance: none` from getting prefixed, + // We have disabled autoprefixer first and are just only using + // `-moz-appearance: none` as a prefix and neglecting the webkit. + + /*! autoprefixer: off */ + -moz-appearance: none; // sass-lint:disable-line no-vendor-prefixes + appearance: none; + + @if has-value($meter-radius) { + border-radius: $meter-radius; + } + + // For Firefox + border: 0; + background: $meter-background; + + // Chrome/Safari/Edge + &::-webkit-meter-bar { + border: 0; + @if has-value($meter-radius) { + border-radius: $meter-radius; + } + + background: $meter-background; + } + + &::-webkit-meter-inner-element { + @if has-value($meter-radius) { + border-radius: $meter-radius; + } + } + + &::-webkit-meter-optimum-value { + background: $meter-fill-good; + + @if has-value($meter-radius) { + border-radius: $meter-radius; + } + } + + &::-webkit-meter-suboptimum-value { + background: $meter-fill-medium; + + @if has-value($meter-radius) { + border-radius: $meter-radius; + } + } + + &::-webkit-meter-even-less-good-value { + background: $meter-fill-bad; + + @if has-value($meter-radius) { + border-radius: $meter-radius; + } + } + + &::-moz-meter-bar { + background: $primary-color; + + @if has-value($meter-radius) { + border-radius: $meter-radius; + } + } + + &:-moz-meter-optimum::-moz-meter-bar { + background: $meter-fill-good; + } + + &:-moz-meter-sub-optimum::-moz-meter-bar { + background: $meter-fill-medium; + } + + &:-moz-meter-sub-sub-optimum::-moz-meter-bar { + background: $meter-fill-bad; + } + } +} diff --git a/src/foundation/forms/_progress.scss b/src/foundation/forms/_progress.scss new file mode 100644 index 0000000..9365470 --- /dev/null +++ b/src/foundation/forms/_progress.scss @@ -0,0 +1,94 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group progress-bar +//// + +/// Height of a progress bar. +/// @type Number +$progress-height: 1rem !default; + +/// Background color of a progress bar. +/// @type Color +$progress-background: $medium-gray !default; + +/// Bottom margin of a progress bar. +/// @type Number +$progress-margin-bottom: $global-margin !default; + +/// Default color of a progress bar's meter. +/// @type Color +$progress-meter-background: $primary-color !default; + +/// Default radius of a progress bar. +/// @type Number +$progress-radius: $global-radius !default; + +@mixin foundation-progress-element { + progress { + display: block; + width: 100%; + height: $progress-height; + margin-bottom: $progress-margin-bottom; + + appearance: none; + + @if has-value($progress-radius) { + border-radius: $progress-radius; + } + + // For Firefox + border: 0; + background: $progress-background; + + &::-webkit-progress-bar { + background: $progress-background; + + @if has-value($progress-radius) { + border-radius: $progress-radius; + } + } + + &::-webkit-progress-value { + background: $progress-meter-background; + + @if has-value($progress-radius) { + border-radius: $progress-radius; + } + } + + &::-moz-progress-bar { + background: $progress-meter-background; + + @if has-value($progress-radius) { + border-radius: $progress-radius; + } + } + + @each $name, $color in $foundation-palette { + &.#{$name} { + // Internet Explorer sets the fill with color + color: $color; + + &::-webkit-progress-value { + background: $color; + } + + &::-moz-progress-bar { + background: $color; + } + } + } + + // For IE and Edge + &::-ms-fill { // sass-lint:disable-line no-vendor-prefixes + @if has-value($progress-radius) { + border-radius: $progress-radius; + } + + border: 0; + } + } +} diff --git a/src/foundation/forms/_range.scss b/src/foundation/forms/_range.scss new file mode 100644 index 0000000..06599f6 --- /dev/null +++ b/src/foundation/forms/_range.scss @@ -0,0 +1,149 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group slider +//// + +/// Default height of the slider. +/// @type Number +$slider-height: 0.5rem !default; + +/// Default background color of the slider's track. +/// @type Color +$slider-background: $light-gray !default; + +/// Default color of the active fill color of the slider. +/// @type Color +$slider-fill-background: $medium-gray !default; + +/// Default height of the handle of the slider. +/// @type Number +$slider-handle-height: 1.4rem !default; + +/// Default width of the handle of the slider. +/// @type Number +$slider-handle-width: 1.4rem !default; + +/// Default color of the handle for the slider. +/// @type Color +$slider-handle-background: $primary-color !default; + +/// Default fade amount of a disabled slider. +/// @type Number +$slider-opacity-disabled: 0.25 !default; + +/// Default radius for slider. +/// @type Number +$slider-radius: $global-radius !default; + +@mixin foundation-range-input { + input[type='range'] { // sass-lint:disable-line no-qualifying-elements + $margin: ($slider-handle-height - $slider-height) / 2; + + display: block; + width: 100%; + height: auto; + margin-top: $margin; + margin-bottom: $margin; + + appearance: none; + border: 0; + line-height: 1; + cursor: pointer; + + @if has-value($slider-radius) { + border-radius: $slider-radius; + } + + &:focus { + outline: 0; + } + + &[disabled] { + opacity: $slider-opacity-disabled; + } + + // sass-lint:disable no-vendor-prefix + + // Chrome/Safari + &::-webkit-slider-runnable-track { + height: $slider-height; + background: $slider-background; + } + + &::-webkit-slider-thumb { + width: $slider-handle-width; + height: $slider-handle-height; + margin-top: -$margin; + + -webkit-appearance: none; // sass-lint:disable-line no-vendor-prefixes + background: $slider-handle-background; + + @if has-value($slider-radius) { + border-radius: $slider-radius; + } + } + + // Firefox + &::-moz-range-track { + height: $slider-height; + -moz-appearance: none; // sass-lint:disable-line no-vendor-prefixes + background: $slider-background; + } + + &::-moz-range-thumb { + width: $slider-handle-width; + height: $slider-handle-height; + margin-top: -$margin; + + -moz-appearance: none; // sass-lint:disable-line no-vendor-prefixes + background: $slider-handle-background; + + @if has-value($slider-radius) { + border-radius: $slider-radius; + } + } + + // Internet Explorer + &::-ms-track { + height: $slider-height; + + border: 0; + border-top: $margin solid $body-background; + border-bottom: $margin solid $body-background; + background: $slider-background; + + overflow: visible; + color: transparent; + } + + &::-ms-thumb { + width: $slider-handle-width; + height: $slider-handle-height; + border: 0; + background: $slider-handle-background; + + @if has-value($slider-radius) { + border-radius: $slider-radius; + } + } + + &::-ms-fill-lower { + background: $slider-fill-background; + } + + &::-ms-fill-upper { + background: $slider-background; + } + + @at-root { + output { + vertical-align: middle; + margin-left: 0.5em; + line-height: $slider-handle-height; + } + } + } +} diff --git a/src/foundation/forms/_select.scss b/src/foundation/forms/_select.scss new file mode 100644 index 0000000..3cda925 --- /dev/null +++ b/src/foundation/forms/_select.scss @@ -0,0 +1,90 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group forms +//// + +/// Background color for select menus. +/// @type Color +$select-background: $white !default; + +/// Color of the dropdown triangle inside select menus. Set to `transparent` to remove it entirely. +/// @type Color +$select-triangle-color: $dark-gray !default; + +/// Default radius for select menus. +/// @type Color +$select-radius: $global-radius !default; + +@mixin form-select { + $height: ($input-font-size * unitless-calc($input-line-height)) + (get-side($input-padding, 'top') + get-side($input-padding, 'bottom')) - rem-calc(1); + + height: $height; + margin: 0 0 $form-spacing; + padding: $input-padding; + + appearance: none; + border: $input-border; + border-radius: $select-radius; + background-color: $select-background; + + font-family: $input-font-family; + font-size: $input-font-size; + font-weight: $input-font-weight; + line-height: $input-line-height; + color: $input-color; + + @if $select-triangle-color != transparent { + @include background-triangle($select-triangle-color); + background-origin: content-box; + background-position: $global-right (-$form-spacing) center; + background-repeat: no-repeat; + background-size: 9px 6px; + + padding-#{$global-right}: ($form-spacing * 1.5); + } + + @if has-value($input-transition) { + transition: $input-transition; + } + + // Focus state + &:focus { + outline: none; + border: $input-border-focus; + background-color: $input-background-focus; + box-shadow: $input-shadow-focus; + + @if has-value($input-transition) { + transition: $input-transition; + } + } + + // Disabled state + &:disabled { + background-color: $input-background-disabled; + cursor: $input-cursor-disabled; + } + + // Hide the dropdown arrow shown in newer IE versions + &::-ms-expand { + display: none; + } + + &[multiple] { + height: auto; + background-image: none; + } + &:not([multiple]) { + padding-top: 0; + padding-bottom: 0; + } +} + +@mixin foundation-form-select { + select { + @include form-select; + } +} diff --git a/src/foundation/forms/_text.scss b/src/foundation/forms/_text.scss new file mode 100644 index 0000000..778f035 --- /dev/null +++ b/src/foundation/forms/_text.scss @@ -0,0 +1,179 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group forms +//// + +/// Font color of text inputs. +/// @type Color +$input-color: $black !default; + +/// Font color of placeholder text within text inputs. +/// @type Color +$input-placeholder-color: $medium-gray !default; + +/// Font family of text inputs. +/// @type Font +$input-font-family: inherit !default; + +/// Font size of text inputs. +/// @type Number +$input-font-size: rem-calc(16) !default; + +/// Font weight of text inputs. +/// @type Keyword +$input-font-weight: $global-weight-normal !default; + +/// Line height of text inputs. +/// @type Keyword +$input-line-height: $global-lineheight !default; + +/// Background color of text inputs. +/// @type Color +$input-background: $white !default; + +/// Background color of focused of text inputs. +/// @type Color +$input-background-focus: $white !default; + +/// Background color of disabled text inputs. +/// @type Color +$input-background-disabled: $light-gray !default; + +/// Border around text inputs. +/// @type Border +$input-border: 1px solid $medium-gray !default; + +/// Border around focused text inputs. +/// @type Color +$input-border-focus: 1px solid $dark-gray !default; + +/// Padding of text inputs. +/// @type Color +$input-padding: $form-spacing / 2 !default; + +/// Box shadow inside text inputs when not focused. +/// @type Shadow +$input-shadow: inset 0 1px 2px rgba($black, 0.1) !default; + +/// Box shadow outside text inputs when focused. +/// @type Shadow +$input-shadow-focus: 0 0 5px $medium-gray !default; + +/// Cursor to use when hovering over a disabled text input. +/// @type Cursor +$input-cursor-disabled: not-allowed !default; + +/// Properties to transition on text inputs. +/// @type Transition +$input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out !default; + +/// Enables the up/down buttons that Chrome and Firefox add to `<input type='number'>` elements. +/// @type Boolean +$input-number-spinners: true !default; + +/// Radius for text inputs. +/// @type Border +$input-radius: $global-radius !default; + +/// Border radius for form buttons, defaulted to global-radius. +/// @type Number +$form-button-radius: $global-radius !default; + +@mixin form-element { + $height: ($input-font-size * unitless-calc($input-line-height)) + (get-side($input-padding, 'top') + get-side($input-padding, 'bottom')) - rem-calc(1); + + display: block; + box-sizing: border-box; + width: 100%; + height: $height; + margin: 0 0 $form-spacing; + padding: $input-padding; + + border: $input-border; + border-radius: $input-radius; + background-color: $input-background; + box-shadow: $input-shadow; + + font-family: $input-font-family; + font-size: $input-font-size; + font-weight: $input-font-weight; + line-height: $input-line-height; + color: $input-color; + + @if has-value($input-transition) { + transition: $input-transition; + } + + // Focus state + &:focus { + outline: none; + border: $input-border-focus; + background-color: $input-background-focus; + box-shadow: $input-shadow-focus; + + @if has-value($input-transition) { + transition: $input-transition; + } + } +} + +@mixin foundation-form-text { + // Text inputs + #{text-inputs()}, + textarea { + @include form-element; + appearance: none; + } + + // Text areas + textarea { + max-width: 100%; + + &[rows] { + height: auto; + } + } + + input, + textarea { + // Disabled/readonly state + &:disabled, + &[readonly] { + background-color: $input-background-disabled; + cursor: $input-cursor-disabled; + } + } + + // Reset styles on button-like inputs + [type='submit'], + [type='button'] { + appearance: none; + border-radius: $form-button-radius; + } + + // Reset Normalize setting content-box to search elements + input[type='search'] { // sass-lint:disable-line no-qualifying-elements + box-sizing: border-box; + } + + // Number input styles + [type='number'] { + @if not $input-number-spinners { + -moz-appearance: textfield; // sass-lint:disable-line no-vendor-prefixes + + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + -webkit-appearance: none; // sass-lint:disable-line no-vendor-prefixes + margin: 0; + } + } + } + + // Placeholder text + ::placeholder { + color: $input-placeholder-color; + } +} diff --git a/src/foundation/foundation.scss b/src/foundation/foundation.scss new file mode 100644 index 0000000..a639583 --- /dev/null +++ b/src/foundation/foundation.scss @@ -0,0 +1,155 @@ +/** + * Foundation for Sites by ZURB + * Version 6.6.1 + * foundation.zurb.com + * Licensed under MIT Open Source + */ + +// --- Dependencies --- +@import 'vendor/normalize'; +@import '../_vendor/sassy-lists/stylesheets/helpers/missing-dependencies'; +@import '../_vendor/sassy-lists/stylesheets/helpers/true'; +@import '../_vendor/sassy-lists/stylesheets/functions/contain'; +@import '../_vendor/sassy-lists/stylesheets/functions/purge'; +@import '../_vendor/sassy-lists/stylesheets/functions/remove'; +@import '../_vendor/sassy-lists/stylesheets/functions/replace'; +@import '../_vendor/sassy-lists/stylesheets/functions/to-list'; + +// --- Settings --- +// import your own `settings` here or +// import and modify the default settings through +@import '_settings'; + +// --- Components --- +// Utilities +@import 'util/util'; +// Global styles +@import 'global'; +@import 'forms/forms'; +@import 'typography/typography'; + +// Grids +@import 'grid/grid'; +@import 'xy-grid/xy-grid'; +// Generic components +@import 'components/button'; +@import 'components/button-group'; +@import 'components/close-button'; +@import 'components/label'; +@import 'components/progress-bar'; +@import 'components/slider'; +@import 'components/switch'; +@import 'components/table'; +// Basic components +@import 'components/badge'; +@import 'components/breadcrumbs'; +@import 'components/callout'; +@import 'components/card'; +@import 'components/dropdown'; +@import 'components/pagination'; +@import 'components/tooltip'; + +// Containers +@import 'components/accordion'; +@import 'components/media-object'; +@import 'components/orbit'; +@import 'components/responsive-embed'; +@import 'components/tabs'; +@import 'components/thumbnail'; +// Menu-based containers +@import 'components/menu'; +@import 'components/menu-icon'; +@import 'components/accordion-menu'; +@import 'components/drilldown'; +@import 'components/dropdown-menu'; + +// Layout components +@import 'components/off-canvas'; +@import 'components/reveal'; +@import 'components/sticky'; +@import 'components/title-bar'; +@import 'components/top-bar'; + +// Helpers +@import 'components/float'; +@import 'components/flex'; +@import 'components/visibility'; +@import 'prototype/prototype'; + + +@mixin foundation-everything( + $flex: true, + $prototype: false, + $xy-grid: $xy-grid +) { + @if $flex { + $global-flexbox: true !global; + } + + @if $xy-grid { + $xy-grid: true !global; + } + + // Global styles + @include foundation-global-styles; + @include foundation-forms; + @include foundation-typography; + + // Grids + @if not $flex { + @include foundation-grid; + } + @else { + @if $xy-grid { + @include foundation-xy-grid-classes; + } + @else { + @include foundation-flex-grid; + } + } + + // Generic components + @include foundation-button; + @include foundation-button-group; + @include foundation-close-button; + @include foundation-label; + @include foundation-progress-bar; + @include foundation-slider; + @include foundation-switch; + @include foundation-table; + // Basic components + @include foundation-badge; + @include foundation-breadcrumbs; + @include foundation-callout; + @include foundation-card; + @include foundation-dropdown; + @include foundation-pagination; + @include foundation-tooltip; + + // Containers + @include foundation-accordion; + @include foundation-media-object; + @include foundation-orbit; + @include foundation-responsive-embed; + @include foundation-tabs; + @include foundation-thumbnail; + // Menu-based containers + @include foundation-menu; + @include foundation-menu-icon; + @include foundation-accordion-menu; + @include foundation-drilldown-menu; + @include foundation-dropdown-menu; + + // Layout components + @include foundation-off-canvas; + @include foundation-reveal; + @include foundation-sticky; + @include foundation-title-bar; + @include foundation-top-bar; + + // Helpers + @include foundation-float-classes; + @if $flex { @include foundation-flex-classes; } + @include foundation-visibility-classes; + @if $prototype { @include foundation-prototype-classes; } +} diff --git a/src/foundation/grid/_classes.scss b/src/foundation/grid/_classes.scss new file mode 100644 index 0000000..3469aa3 --- /dev/null +++ b/src/foundation/grid/_classes.scss @@ -0,0 +1,189 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group grid +//// + +/// Outputs CSS classes for the grid. +/// @access private +@mixin foundation-grid( + $row: 'row', + $column: 'column', + $column-row: 'column-row', + $gutter: 'gutter', + $push: 'push', + $pull: 'pull', + $center: 'centered', + $uncenter: 'uncentered', + $collapse: 'collapse', + $uncollapse: 'uncollapse', + $offset: 'offset', + $end: 'end', + $expanded: 'expanded', + $block: 'block' +) { + // Row + .#{$row} { + @include grid-row; + + // Collapsing + &.#{$collapse} { + > .#{$column} { + @include grid-col-collapse; + } + } + + // Nesting + & .#{$row} { + @include grid-row-nest($grid-column-gutter); + + &.#{$collapse} { + margin-right: 0; + margin-left: 0; + } + } + + // Expanded (full-width) row + &.#{$expanded} { + @include grid-row-size(expand); + + .#{$row} { + margin-right: auto; + margin-left: auto; + } + } + + &:not(.#{$expanded}) .#{$row} { + @include grid-row-size(expand); + } + + @if type-of($grid-column-gutter) == 'map' { + // Static (unresponsive) row gutters + // + @each $breakpoint, $value in $grid-column-gutter { + &.#{$gutter}-#{$breakpoint} { + > .#{$column} { + @include grid-col-gutter($value); + } + } + } + } + } + + // Column + .#{$column} { + @include grid-col; + + @if $grid-column-align-edge { + &.#{$end} { + @include grid-col-end; + } + } + } + + // Column row + // The double .row class is needed to bump up the specificity + .#{$column}.#{$row}.#{$row} { + float: none; + } + + // To properly nest a column row, padding and margin is removed + .#{$row} .#{$column}.#{$row}.#{$row} { + margin-right: 0; + margin-left: 0; + padding-right: 0; + padding-left: 0; + } + + @include -zf-each-breakpoint { + @for $i from 1 through $grid-column-count { + // Column width + .#{$-zf-size}-#{$i} { + @include grid-col-size($i); + } + + // Source ordering + @if $i < $grid-column-count { + @if $push { + .#{$-zf-size}-#{$push}-#{$i} { + @include grid-col-pos($i); + } + } + + @if $pull { + .#{$-zf-size}-#{$pull}-#{$i} { + @include grid-col-pos(-$i); + } + } + } + + // Offsets + $o: $i - 1; + + @if $offset { + .#{$-zf-size}-#{$offset}-#{$o} { + @include grid-col-off($o); + } + } + } + + // Block grid + @for $i from 1 through $block-grid-max { + .#{$-zf-size}-up-#{$i} { + @include grid-layout($i, '.#{$column}'); + } + } + + // Responsive collapsing + .#{$-zf-size}-#{$collapse} { + > .#{$column} { @include grid-col-collapse; } + + .#{$row} { + margin-right: 0; + margin-left: 0; + } + } + + .#{$expanded}.#{$row} .#{$-zf-size}-#{$collapse}.#{$row} { + margin-right: 0; + margin-left: 0; + } + + .#{$-zf-size}-#{$uncollapse} { + > .#{$column} { @include grid-col-gutter($-zf-size); } + } + + // Positioning + @if $center { + .#{$-zf-size}-#{$center} { + @include grid-col-pos(center); + } + } + + // Gutter adjustment + $-gutter-unpos-selector: ( + if($uncenter, '.#{$-zf-size}-#{$uncenter}', null), + if($push, '.#{$-zf-size}-#{$push}-0', null), + if($pull, '.#{$-zf-size}-#{$pull}-0', null), + ); + @if ($uncenter or $push or $pull) { + #{$-gutter-unpos-selector} { + @include grid-col-unpos; + } + } + } + + // Block grid columns + .#{$column}-#{$block} { + @include grid-column-margin; + } + + @if $column == 'column' and has-value($grid-column-alias) { + .#{$grid-column-alias} { + // sass-lint:disable-block placeholder-in-extend + @extend .column; + } + } +} diff --git a/src/foundation/grid/_column.scss b/src/foundation/grid/_column.scss new file mode 100644 index 0000000..26c8d15 --- /dev/null +++ b/src/foundation/grid/_column.scss @@ -0,0 +1,78 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group grid +//// + +/// Calculates the width of a column based on a number of factors. +/// +/// @param {Number|List} $columns +/// Width of the column. Accepts multiple values: +/// - A percentage value will make the column that exact size. +/// - A single digit will make the column span that number of columns wide, taking into account the column count of the parent row. +/// - A list of the format "x of y" (without quotes) will make a column that is *x* columns wide, assuming *y* total columns for the parent. +/// +/// @returns {Number} A calculated percentage value. +@function grid-column($columns) { + @return fraction-to-percentage($columns, $denominator: $grid-column-count); +} + +/// Creates a grid column. +/// +/// @param {Mixed} $columns [$grid-column-count] - Width of the column. Refer to the `grid-column()` function to see possible values. +/// @param {Mixed} $gutters [$grid-column-gutter] - Spacing between columns. Refer to the `grid-column-gutter()` function to see possible values. +@mixin grid-column( + $columns: $grid-column-count, + $gutters: $grid-column-gutter +) { + @include grid-column-size($columns); + float: $global-left; + + // Gutters + @include grid-column-gutter($gutters: $gutters); + + // Position + @include grid-col-pos(auto); +} + +/// Creates a grid column row. This is the equivalent of adding `.row` and `.column` to the same element. +/// +/// @param {Mixed} $gutters [$grid-column-gutter] - Width of the gutters on either side of the column row. Refer to the `grid-column-gutter()` function to see possible values. +@mixin grid-column-row( + $gutters: $grid-column-gutter +) { + @include grid-row; + @include grid-column($gutters: $gutters); + + &, + &:last-child { + float: none; + } +} + +/// Shorthand for `grid-column()`. +/// @alias grid-column +@function grid-col( + $columns: $grid-column-count +) { + @return grid-column($columns); +} + +/// Shorthand for `grid-column()`. +/// @alias grid-column +@mixin grid-col( + $columns: $grid-column-count, + $gutters: $grid-column-gutter +) { + @include grid-column($columns, $gutters); +} + +/// Shorthand for `grid-column-row()`. +/// @alias grid-column-row +@mixin grid-col-row( + $gutters: $grid-column-gutter +) { + @include grid-column-row($gutters); +} diff --git a/src/foundation/grid/_flex-grid.scss b/src/foundation/grid/_flex-grid.scss new file mode 100644 index 0000000..d3d70f9 --- /dev/null +++ b/src/foundation/grid/_flex-grid.scss @@ -0,0 +1,260 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group flex-grid +//// + +/// Creates a container for a flex grid row. +/// +/// @param {Keyword|List} $behavior [null] +/// Modifications to the default grid styles. `nest` indicates the row will be placed inside another row. `collapse` indicates that the columns inside this row will not have padding. `nest collapse` combines both behaviors. +/// @param {Keyword|Number} $size [$grid-row-width] Maximum size of the row. Set to `expand` to make the row taking the full width. +/// @param {Number} $columns [null] - Number of columns to use for this row. If set to `null` (the default), the global column count will be used. +/// @param {Boolean} $base [true] - Set to `false` to prevent basic styles from being output. Useful if you're calling this mixin on the same element twice, as it prevents duplicate CSS output. +/// @param {Boolean} $wrap [true] - Set to `false` to have row wrapping behavior set to nowrap +/// @param {Number|Map} $gutters [$grid-column-gutter] - Gutter map or single value to use when inverting margins, in case the row is nested. Responsive gutter settings by default. +@mixin flex-grid-row( + $behavior: null, + $size: $grid-row-width, + $columns: null, + $base: true, + $wrap: true, + $gutters: $grid-column-gutter +) { + $margin: auto; + $wrap: if($wrap, wrap, nowrap); + + @if index($behavior, nest) != null { + @include grid-row-nest($gutters); + + @if index($behavior, collapse) != null { + margin-right: 0; + margin-left: 0; + } + } + @else { + @include grid-row-size($size); + margin-right: auto; + margin-left: auto; + } + + @if $base { + display: flex; + flex-flow: row $wrap; + } + + @if $columns != null { + @include grid-context($columns, $base) { + @content; + } + } +} + +/// Calculates the `flex` property for a flex grid column. It accepts all of the same values as the basic `grid-column()` function, along with two extras: +/// - `expand` (the default) will make the column expand to fill space. +/// - `shrink` will make the column contract, so it only takes up the horizontal space it needs. +/// +/// @param {Mixed} $columns [expand] - Width of the column. +@function flex-grid-column($columns: expand) { + $flex: 1 1 0px; // sass-lint:disable-line zero-unit + + @if $columns == shrink { + $flex: 0 0 auto; + } + @else if $columns != expand { + $flex: 0 0 grid-column($columns); + } + + @return $flex; +} + +/// Creates a column for a flex grid. By default, the column will stretch to the full width of its container, but this can be overridden with sizing classes, or by using the `unstack` class on the parent flex row. +/// +/// @param {Mixed} $columns [expand] - Width of the column. Refer to the `flex-grid-column()` function to see possible values. +/// @param {Number|Map} $gutters [$grid-column-gutter] - Map or single value for gutters width. See the `grid-column-gutter` mixin. +@mixin flex-grid-column( + $columns: expand, + $gutters: $grid-column-gutter +) { + // Base properties + @include flex-grid-size($columns); + + // Gutters + @include grid-column-gutter($gutters: $gutters); + + // fixes recent Chrome version not limiting child width + // https://stackoverflow.com/questions/34934586/white-space-nowrap-and-flexbox-did-not-work-in-chrome + @if $columns == expand { + min-width: 0; + } +} + +/// Creates a block grid for a flex grid row. +/// +/// @param {Number} $n - Number of columns to display on each row. +/// @param {String} $selector - Selector to use to target columns within the row. +@mixin flex-grid-layout( + $n, + $selector: '.column' +) { + flex-wrap: wrap; + + > #{$selector} { + $pct: percentage(1/$n); + + flex: 0 0 $pct; + max-width: $pct; + } +} + +/// Changes the width flex grid column. +/// @param {Mixed} $columns [expand] - Width of the column. Refer to the `flex-grid-column()` function to see possible values. +@mixin flex-grid-size($columns: null) { + $columns: $columns or expand; + + flex: flex-grid-column($columns); + + // max-width fixes IE 10/11 not respecting the flex-basis property + @if $columns != expand and $columns != shrink { + max-width: grid-column($columns); + } +} + + +@mixin foundation-flex-grid { + // Row + .row { + @include flex-grid-row; + + // Nesting behavior + & .row { + @include flex-grid-row(nest, $base: false); + + &.collapse { + margin-right: 0; + margin-left: 0; + } + } + + // Expanded row + &.expanded { + @include grid-row-size(expand); + + .row { + margin-right: auto; + margin-left: auto; + } + } + + &:not(.expanded) .row { + @include grid-row-size(expand); + } + + &.collapse { + > .column { + @include grid-col-collapse; + } + } + + // Undo negative margins + // From collapsed child + &.is-collapse-child, + &.collapse > .column > .row { + margin-right: 0; + margin-left: 0; + } + } + + // Column + .column { + @include flex-grid-column; + } + + // Column row + // The double .row class is needed to bump up the specificity + .column.row.row { + float: none; + display: block; + } + + // To properly nest a column row, padding and margin is removed + .row .column.row.row { + margin-right: 0; + margin-left: 0; + padding-right: 0; + padding-left: 0; + } + + @include -zf-each-breakpoint { + @for $i from 1 through $grid-column-count { + // Sizing (percentage) + .#{$-zf-size}-#{$i} { + flex: flex-grid-column($i); + max-width: grid-column($i); + } + + // Offsets + $o: $i - 1; + + .#{$-zf-size}-offset-#{$o} { + @include grid-column-offset($o); + } + } + + // Block grid + @for $i from 1 through $block-grid-max { + .#{$-zf-size}-up-#{$i} { + @include flex-grid-layout($i); + } + } + + @if $-zf-size != $-zf-zero-breakpoint { + // Sizing (expand) + @include breakpoint($-zf-size) { + .#{$-zf-size}-expand { + flex: flex-grid-column(); + } + } + + // Auto-stacking/unstacking + @at-root (without: media) { + .row.#{$-zf-size}-unstack { + > .column { + flex: flex-grid-column(100%); + + @include breakpoint($-zf-size) { + flex: flex-grid-column(); + } + } + } + } + } + + // Responsive collapsing + .#{$-zf-size}-collapse { + > .column { @include grid-col-collapse; } + } + + .#{$-zf-size}-uncollapse { + > .column { @include grid-col-gutter($-zf-size); } + } + } + + // Sizing (shrink) + .shrink { + flex: flex-grid-column(shrink); + max-width: 100%; + } + + // Block grid columns + .column-block { + @include grid-column-margin; + } + + .columns { + @extend .column; // sass-lint:disable-line placeholder-in-extend + + } +} diff --git a/src/foundation/grid/_grid.scss b/src/foundation/grid/_grid.scss new file mode 100644 index 0000000..34b2b50 --- /dev/null +++ b/src/foundation/grid/_grid.scss @@ -0,0 +1,48 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group grid +//// + +/// The maximum width of a row. +/// @type Number +$grid-row-width: $global-width !default; + +/// The default column count of a grid. Changing this value affects the logic of the grid mixins, and the number of CSS classes output. +/// @type Number +$grid-column-count: 12 !default; + +/// The amount of space between columns at different screen sizes. To use just one size, set the variable to a number instead of a map. +/// @type Map | Length +/// @since 6.1.0 +$grid-column-gutter: ( + small: 20px, + medium: 30px, +) !default; + +/// If `true`, the last column in a row will align to the opposite edge of the row. +/// @type Boolean +$grid-column-align-edge: true !default; + +/// Selector used for an alias of column (with @extend). If `false`, no alias is created. +/// @type String +$grid-column-alias: 'columns' !default; + +/// The highest number of `.x-up` classes available when using the block grid CSS. +/// @type Number +$block-grid-max: 8 !default; + +// Internal value to store the end column float direction +$-zf-end-float: if($grid-column-align-edge, $global-right, $global-left); + +@import 'row'; +@import 'column'; +@import 'size'; +@import 'position'; +@import 'gutter'; +@import 'classes'; +@import 'layout'; + +@import 'flex-grid'; diff --git a/src/foundation/grid/_gutter.scss b/src/foundation/grid/_gutter.scss new file mode 100644 index 0000000..2b9ba3f --- /dev/null +++ b/src/foundation/grid/_gutter.scss @@ -0,0 +1,67 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group grid +//// + +/// Set the gutters on a column +/// @param {Number|Keyword} $gutter [auto] +/// Spacing between columns, accepts multiple values: +/// - A single value will make the gutter that exact size. +/// - A breakpoint name will make the gutter the corresponding size in the $gutters map. +/// - "auto" will make the gutter responsive, using the $gutters map values. +/// @param {Number|Map} $gutters [$grid-column-gutter] - Gutter map or single value to use. Responsive gutter settings by default. +@mixin grid-column-gutter( + $gutter: auto, + $gutters: $grid-column-gutter +) { + @include -zf-breakpoint-value($gutter, $gutters) { + $padding: rem-calc($-zf-bp-value) / 2; + + padding-right: $padding; + padding-left: $padding; + } +} + +/// Collapse the gutters on a column by removing the padding. **Note:** only use this mixin within a breakpoint. To collapse a column's gutters on all screen sizes, use the `$gutter` parameter of the `grid-column()` mixin instead. +@mixin grid-column-collapse { + @include grid-column-gutter(0); +} + +/// Shorthand for `grid-column-gutter()`. +/// @alias grid-column-gutter +@mixin grid-col-gutter( + $gutter: auto, + $gutters: $grid-column-gutter +) { + @include grid-column-gutter($gutter, $gutters); +} + +/// Shorthand for `grid-column-collapse()`. +/// @alias grid-column-collapse +@mixin grid-col-collapse { + @include grid-column-collapse; +} + +/// Sets bottom margin on grid columns to match gutters +/// @param {Number|Keyword} $margin [auto] +/// The bottom margin on grid columns, accepts multiple values: +/// - A single value will make the margin that exact size. +/// - A breakpoint name will make the margin the corresponding size in the $margins map. +/// - "auto" will make the margin responsive, using the $margins map values. +/// @param {Number|Map} $margins [$grid-column-gutter] - Map or single value to use. Responsive gutter settings by default. +@mixin grid-column-margin ( + $margin: auto, + $margins: $grid-column-gutter +) { + @include -zf-breakpoint-value($margin, $margins) { + $margin-bottom: rem-calc($-zf-bp-value); + margin-bottom: $margin-bottom; + + > :last-child { + margin-bottom: 0; + } + } +} diff --git a/src/foundation/grid/_layout.scss b/src/foundation/grid/_layout.scss new file mode 100644 index 0000000..1e08791 --- /dev/null +++ b/src/foundation/grid/_layout.scss @@ -0,0 +1,76 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group grid +//// + +/// Sizes child elements so that `$n` number of items appear on each row. +/// +/// @param {Number} $n - Number of elements to display per row. +/// @param {String} $selector ['.column'] - Selector(s) to use for child elements. +/// @param {Number|List} $gutter +/// The gutter to apply to child elements. Accepts multiple values: +/// - $grid-column-gutter will use the values in the $grid-column-gutter map, including breakpoint sizes. +/// - A fixed numeric value will apply this gutter to all breakpoints. +@mixin grid-layout( + $n, + $selector: '.column', + $gutter: null +) { + & > #{$selector} { + float: $global-left; + width: percentage(1/$n); + + // If a $gutter value is passed + @if($gutter) { + // Gutters + @if type-of($gutter) == 'map' { + @each $breakpoint, $value in $gutter { + $padding: rem-calc($value) / 2; + + @include breakpoint($breakpoint) { + padding-right: $padding; + padding-left: $padding; + } + } + } + @else if type-of($gutter) == 'number' and strip-unit($gutter) > 0 { + $padding: rem-calc($gutter) / 2; + padding-right: $padding; + padding-left: $padding; + } + } + + &:nth-of-type(1n) { + clear: none; + } + + &:nth-of-type(#{$n}n+1) { + clear: both; + } + + &:last-child { + float: $global-left; + } + } +} + +/// Adds extra CSS to block grid children so the last items in the row center automatically. Apply this to the columns, not the row. +/// +/// @param {Number} $n - Number of items that appear in each row. +@mixin grid-layout-center-last($n) { + @for $i from 1 to $n { + @if $i == 1 { + &:nth-child(#{$n}n+1):last-child { + margin-left: (100 - 100/$n * $i) / 2 * 1%; + } + } + @else { + &:nth-child(#{$n}n+1):nth-last-child(#{$i}) { + margin-left: (100 - 100/$n * $i) / 2 * 1%; + } + } + } +} diff --git a/src/foundation/grid/_position.scss b/src/foundation/grid/_position.scss new file mode 100644 index 0000000..24b49dd --- /dev/null +++ b/src/foundation/grid/_position.scss @@ -0,0 +1,100 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group grid +//// + +/// Reposition a column. +/// +/// @param {Number|Keyword} $position - It can be: +/// * A number: The column will move equal to the width of the column count +/// specified. A positive number will push the column to the right, while +/// a negative number will pull it to the left. +/// * `center`: Column will be centered +/// * `auto`: Column will be pushed to the left (or to the right for the last column). +@mixin grid-column-position($position) { + // Auto positioning + @if $position == auto { + &, &:last-child:not(:first-child) { + float: $global-left; + clear: none; + } + + // Last column alignment + @if $grid-column-align-edge { + &:last-child:not(:first-child) { + float: $global-right; + } + } + } + + // Push/pull + @else if type-of($position) == 'number' { + $offset: percentage($position / $grid-column-count); + + position: relative; + #{$global-left}: $offset; + } + + // Center positioning + @else if $position == center { + &, &:last-child:not(:first-child) { + float: none; + clear: both; + } + margin-right: auto; + margin-left: auto; + } + + @else { + @warn 'Wrong syntax for grid-column-position(). Enter a positive or negative number, "center" or "auto".'; + } +} + +/// Reset a position definition. +@mixin grid-column-unposition { + @include grid-column-position(auto); + position: static; + margin-right: 0; + margin-left: 0; +} + +/// Offsets a column to the right by `$n` columns. +/// @param {Number|List} $n - Width to offset by. You can pass in any value accepted by the `grid-column()` mixin, such as `6`, `50%`, or `1 of 2`. +@mixin grid-column-offset($n) { + margin-#{$global-left}: grid-column($n); +} + +/// Disable the default behavior of the last column in a row aligning to the opposite edge. +@mixin grid-column-end { + // This extra specificity is required for the property to be applied + &:last-child:last-child { + float: $global-left; + } +} + +/// Shorthand for `grid-column-position()`. +/// @alias grid-column-position +@mixin grid-col-pos($position) { + @include grid-column-position($position); +} + +/// Shorthand for `grid-column-unposition()`. +/// @alias grid-column-unposition +@mixin grid-col-unpos { + @include grid-column-unposition; +} + +/// Shorthand for `grid-column-offset()`. +/// @alias grid-column-offset +@mixin grid-col-off($n) { + @include grid-column-offset($n); +} + +/// Shorthand for `grid-column-end()`. +/// @alias grid-column-end +@mixin grid-col-end { + @include grid-column-end; +} diff --git a/src/foundation/grid/_row.scss b/src/foundation/grid/_row.scss new file mode 100644 index 0000000..e971855 --- /dev/null +++ b/src/foundation/grid/_row.scss @@ -0,0 +1,99 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group grid +//// + +/// Change the behavior of columns defined inside this mixin to use a different column count. +/// @content +/// +/// @param {Number} $columns - Number of columns to use. +/// @param {Boolean} $root [false] +/// If `false`, selectors inside this mixin will nest inside the parent selector. +/// If `true`, selectors will not nest. +@mixin grid-context( + $columns, + $root: false +) { + // Store the current column count so it can be re-set later + $old-grid-column-count: $grid-column-count; + $grid-column-count: $columns !global; + + @if $root { + @at-root { @content; } + } + @else { + @content; + } + + // Restore the old column count + $grid-column-count: $old-grid-column-count !global; +} + +/// Creates a grid row. +/// @content +/// +/// @param {Number} $columns [null] - Column count for this row. `null` will use the default column count. +/// @param {Keywords} $behavior [null] +/// Modifications to the default grid styles. `nest` indicates the row will be placed inside another row. `collapse` indicates that the columns inside this row will not have padding. `nest collapse` combines both behaviors. +/// @param {Keyword|Number} $size [$grid-row-width] Maximum size of the row. Set to `expand` to make the row taking the full width. +/// @param {Boolean} $cf [true] - Whether or not to include a clearfix. +/// @param {Number|Map} $gutters [$grid-column-gutter] - Gutter map or single value to use when inverting margins. Responsive gutter settings by default. +@mixin grid-row( + $columns: null, + $behavior: null, + $size: $grid-row-width, + $cf: true, + $gutters: $grid-column-gutter +) { + $margin: auto; + + @if index($behavior, nest) != null { + @include grid-row-nest($gutters); + + @if index($behavior, collapse) != null { + margin-right: 0; + margin-left: 0; + } + } + @else { + @include grid-row-size($size); + margin-right: auto; + margin-left: auto; + } + + @if $cf { + @include clearfix; + } + + @if $columns != null { + @include grid-context($columns) { + @content; + } + } +} + +/// Inverts the margins of a row to nest it inside of a column. +/// +/// @param {Number|Map} $gutters [$grid-column-gutter] - Gutter map or single value to use when inverting margins. Responsive gutter settings by default. +@mixin grid-row-nest($gutters: $grid-column-gutter) { + @include -zf-each-breakpoint { + $margin: rem-calc(-zf-get-bp-val($gutters, $-zf-size)) / 2 * -1; + + margin-right: $margin; + margin-left: $margin; + } +} + +/// Set a grid row size +/// +/// @param {Keyword|Number} $size [$grid-row-width] Maximum size of the row. Set to `expand` to make the row taking the full width. +@mixin grid-row-size($size: $grid-row-width) { + @if $size == expand { + $size: none; + } + + max-width: $size; +} diff --git a/src/foundation/grid/_size.scss b/src/foundation/grid/_size.scss new file mode 100644 index 0000000..c01c8ca --- /dev/null +++ b/src/foundation/grid/_size.scss @@ -0,0 +1,24 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group grid +//// + +/// Set the width of a grid column. +/// +/// @param {Number|List} $width [$grid-column-count] - Width to make the column. You can pass in any value accepted by the `grid-column()` function, such as `6`, `50%`, or `1 of 2`. +@mixin grid-column-size( + $columns: $grid-column-count +) { + width: grid-column($columns); +} + +/// Shorthand for `grid-column-size()`. +/// @alias grid-column-size +@mixin grid-col-size( + $columns: $grid-column-count +) { + @include grid-column-size($columns); +} diff --git a/src/foundation/prototype/_arrow.scss b/src/foundation/prototype/_arrow.scss new file mode 100644 index 0000000..3ba57c2 --- /dev/null +++ b/src/foundation/prototype/_arrow.scss @@ -0,0 +1,36 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-arrow +//// + +/// Map containing all the `arrow` direction +/// @type Map +$prototype-arrow-directions: ( + down, + up, + right, + left +) !default; + +/// Width of the Arrow, `0.4375rem` by default. +/// @type Number +$prototype-arrow-size: 0.4375rem; + +/// Color of the Arrow, `$black` by default. +/// @type Color +$prototype-arrow-color: $black; + +@mixin foundation-prototype-arrow { + @each $prototype-arrow-direction in $prototype-arrow-directions { + .arrow-#{$prototype-arrow-direction} { + @include css-triangle( + $prototype-arrow-size, + $prototype-arrow-color, + $prototype-arrow-direction + ); + } + } +} diff --git a/src/foundation/prototype/_border-box.scss b/src/foundation/prototype/_border-box.scss new file mode 100644 index 0000000..e04b5f0 --- /dev/null +++ b/src/foundation/prototype/_border-box.scss @@ -0,0 +1,35 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-border-box +//// + +/// Responsive breakpoints for border box. +/// @type Boolean +$prototype-border-box-breakpoints: $global-prototype-breakpoints !default; + +/// Border box utility +@mixin border-box { + box-sizing: border-box !important; +} + +@mixin foundation-prototype-border-box { + .border-box { + @include border-box; + } + + @if ($prototype-border-box-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-border-box { + @include border-box; + } + } + } + } + } +} diff --git a/src/foundation/prototype/_border-none.scss b/src/foundation/prototype/_border-none.scss new file mode 100644 index 0000000..5408038 --- /dev/null +++ b/src/foundation/prototype/_border-none.scss @@ -0,0 +1,35 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-border-none +//// + +/// Responsive breakpoints for border none. +/// @type Boolean +$prototype-border-none-breakpoints: $global-prototype-breakpoints !default; + +/// Border none utility +@mixin border-none { + border: none !important; +} + +@mixin foundation-prototype-border-none { + .border-none { + @include border-none; + } + + @if ($prototype-border-none-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-border-none { + @include border-none; + } + } + } + } + } +} diff --git a/src/foundation/prototype/_bordered.scss b/src/foundation/prototype/_bordered.scss new file mode 100644 index 0000000..2d4d6ae --- /dev/null +++ b/src/foundation/prototype/_bordered.scss @@ -0,0 +1,54 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-bordered +//// + +/// Responsive breakpoints for bordered utility. +/// @type Boolean +$prototype-bordered-breakpoints: $global-prototype-breakpoints !default; + +/// Default value for `prototype-border-width` +/// @type Number +$prototype-border-width: rem-calc(1) !default; + +/// Default value for `prototype-border-type` +/// @type String +$prototype-border-type: solid !default; + +/// Default value for `prototype-border-color` defaulted to `medium-gray` +/// @type Color +$prototype-border-color: $medium-gray !default; + +/// Bordered Utility: Adds a light border to an element by default. +/// @param {Number} $width [$prototype-border-width] Width of the border +/// @param {String} $type [$prototype-border-type] Type of the border +/// @param {Color} $color [$prototype-border-color] Color of the border +@mixin bordered( + $width: $prototype-border-width, + $type: $prototype-border-type, + $color: $prototype-border-color +) { + border: $width $type $color; +} + +@mixin foundation-prototype-bordered { + .bordered { + @include bordered; + } + + @if ($prototype-bordered-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-bordered { + @include bordered; + } + } + } + } + } +} diff --git a/src/foundation/prototype/_box.scss b/src/foundation/prototype/_box.scss new file mode 100644 index 0000000..0d4cf8e --- /dev/null +++ b/src/foundation/prototype/_box.scss @@ -0,0 +1,23 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-box +//// + +/// Box Mixin: Easily create a square, rectangle or a circle +/// @param {Number} $width[] Width of the box +/// @param {Number} $height[$width] Height of the box, defaults to `$width` to easily make a square +/// @param {Boolean} $circle[false] Makes the box a circle, by default `false`. +@mixin box( + $width, + $height: $width, + $circle: false +) { + width: $width; + height: $height; + @if $circle { + border-radius: 50% !important; + } +} diff --git a/src/foundation/prototype/_display.scss b/src/foundation/prototype/_display.scss new file mode 100644 index 0000000..23f8b5c --- /dev/null +++ b/src/foundation/prototype/_display.scss @@ -0,0 +1,50 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-display +//// + +/// Responsive breakpoints for display classes +/// @type Boolean +$prototype-display-breakpoints: $global-prototype-breakpoints !default; + +/// Map containing all the `display` classes +/// @type Map +$prototype-display: ( + inline, + inline-block, + block, + table, + table-cell +) !default; + +/// Display classes, by default coming through a map `$prototype-display` +/// @param {String} $display [] Display classes +@mixin display($display) { + display: $display !important; +} + +@mixin foundation-prototype-display { + @each $display in $prototype-display { + .display-#{$display} { + @include display($display); + } + } + + @if ($prototype-display-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @each $display in $prototype-display { + @if $size != $-zf-zero-breakpoint { + .#{$size}-display-#{$display} { + @include display($display); + } + } + } + } + } + } +} diff --git a/src/foundation/prototype/_font-styling.scss b/src/foundation/prototype/_font-styling.scss new file mode 100644 index 0000000..cf6f570 --- /dev/null +++ b/src/foundation/prototype/_font-styling.scss @@ -0,0 +1,95 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-font-styling +//// + +/// Responsive breakpoints for font styling types +/// @type Boolean +$prototype-font-breakpoints: $global-prototype-breakpoints !default; + +/// Letter spacing for `.font-wide` +/// @type Number +$prototype-wide-letter-spacing: rem-calc(4) !default; + +/// Default weight for `.font-normal`, defaulted to `global-weight-normal` +/// @type Number +$prototype-font-normal: $global-weight-normal !default; + +/// Default weight for `.font-bold`, defaulted to `global-weight-bold` +/// @type Number +$prototype-font-bold: $global-weight-bold !default; + +/// Font wide letter spacing! +/// @param {Number} $letter-spacing [$prototype-wide-letter-spacing] Wide letter spacing for the font +@mixin font-wide( + $letter-spacing: $prototype-wide-letter-spacing +) { + letter-spacing: $letter-spacing; +} + +/// Font Weight Normal, default value coming through `global-weight-normal` +/// @param {Number} $weight [$prototype-font-normal] Weight of the font (normal) +@mixin font-normal( + $weight: $prototype-font-normal +) { + font-weight: $weight; +} + +/// Font Weight Bold, default value coming through `global-weight-bold` +/// @param {Number} $weight [$prototype-font-bold] Weight of the font (bold) +@mixin font-bold( + $weight: $prototype-font-bold +) { + font-weight: $weight; +} + +/// Font Style Italic +@mixin font-italic { + font-style: italic !important; +} + +@mixin foundation-prototype-font-styling { + .font-wide{ + @include font-wide; + } + + .font-normal { + @include font-normal; + } + + .font-bold { + @include font-bold; + } + + .font-italic { + @include font-italic; + } + + @if ($prototype-font-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-font-wide{ + @include font-wide; + } + + .#{$size}-font-normal { + @include font-normal; + } + + .#{$size}-font-bold { + @include font-bold; + } + + .#{$size}-font-italic { + @include font-italic; + } + } + } + } + } +} diff --git a/src/foundation/prototype/_list-style-type.scss b/src/foundation/prototype/_list-style-type.scss new file mode 100644 index 0000000..c9678c4 --- /dev/null +++ b/src/foundation/prototype/_list-style-type.scss @@ -0,0 +1,95 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-list-style-type +//// + +/// Responsive breakpoints for list styling types +/// @type Boolean +$prototype-list-breakpoints: $global-prototype-breakpoints !default; + +/// Map containing all the `style-type-unordered` classes +/// @type Map +$prototype-style-type-unordered: ( + disc, + circle, + square +) !default; + +/// Map containing all the `style-type-ordered` classes +/// @type Map +$prototype-style-type-ordered: ( + decimal, + lower-alpha, + lower-latin, + lower-roman, + upper-alpha, + upper-latin, + upper-roman +) !default; + + +/// Style type for unordered Lists, by default coming through a map `$prototype-style-type-unordered` +/// @param {String} $style-type-unordered [] Style type for unordered Lists +@mixin style-type-unordered($style-type-unordered) { + list-style-type: $style-type-unordered !important; +} + +/// Style type for ordered Lists, by default coming through a map `$prototype-style-type-ordered` +/// @param {String} $style-type-ordered [] Style type for ordered Lists +@mixin style-type-ordered($style-type-ordered) { + list-style-type: $style-type-ordered !important; +} + +@mixin list-unordered { + @each $style-type-unordered in $prototype-style-type-unordered { + ul.list-#{$style-type-unordered} { + @include style-type-unordered($style-type-unordered); + } + } + + @if ($prototype-list-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @each $style-type-unordered in $prototype-style-type-unordered { + @if $size != $-zf-zero-breakpoint { + ul.#{$size}-list-#{$style-type-unordered} { + @include style-type-unordered($style-type-unordered); + } + } + } + } + } + } +} + +@mixin list-ordered { + @each $style-type-ordered in $prototype-style-type-ordered { + ol.list-#{$style-type-ordered} { + @include style-type-ordered($style-type-ordered); + } + } + + @if ($prototype-list-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @each $style-type-ordered in $prototype-style-type-ordered { + @if $size != $-zf-zero-breakpoint { + ol.#{$size}-list-#{$style-type-ordered} { + @include style-type-ordered($style-type-ordered); + } + } + } + } + } + } +} + +@mixin foundation-prototype-list-style-type { + @include list-unordered; + @include list-ordered; +} diff --git a/src/foundation/prototype/_overflow.scss b/src/foundation/prototype/_overflow.scss new file mode 100644 index 0000000..8ac43d5 --- /dev/null +++ b/src/foundation/prototype/_overflow.scss @@ -0,0 +1,72 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-overflow +//// + +/// Responsive breakpoints for overflow helper classes +/// @type Boolean +$prototype-overflow-breakpoints: $global-prototype-breakpoints !default; + +/// Map containing all the `overflow` classes +/// @type Map +$prototype-overflow: ( + visible, + hidden, + scroll +) !default; + +/// Overflow classes, by default coming through a map `$prototype-overflow` +/// @param {String} $overflow [] Overflow classes +@mixin overflow($overflow) { + overflow: $overflow !important; +} + +/// Overflow classes on horizontal axis, by default coming through a map `$prototype-overflow` +/// @param {String} $overflow [] Overflow classes (horizontal axis) +@mixin overflow-x($overflow) { + overflow-x: $overflow !important; +} + +/// Overflow classes on vertical axis, by default coming through a map `$prototype-overflow` +/// @param {String} $overflow [] Overflow classes (vertical axis) +@mixin overflow-y($overflow) { + overflow-y: $overflow !important; +} + +@mixin foundation-prototype-overflow { + @each $overflow in $prototype-overflow { + .overflow-#{$overflow} { + @include overflow($overflow); + } + .overflow-x-#{$overflow} { + @include overflow-x($overflow); + } + .overflow-y-#{$overflow} { + @include overflow-y($overflow); + } + } + + @if ($prototype-overflow-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @each $overflow in $prototype-overflow { + @if $size != $-zf-zero-breakpoint { + .#{$size}-overflow-#{$overflow} { + @include overflow($overflow); + } + .#{$size}-overflow-x-#{$overflow} { + @include overflow-x($overflow); + } + .#{$size}-overflow-y-#{$overflow} { + @include overflow-y($overflow); + } + } + } + } + } + } +} diff --git a/src/foundation/prototype/_position.scss b/src/foundation/prototype/_position.scss new file mode 100644 index 0000000..db01507 --- /dev/null +++ b/src/foundation/prototype/_position.scss @@ -0,0 +1,114 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-position +//// + +/// Responsive breakpoints for position helpers +/// @type Boolean +$prototype-position-breakpoints: $global-prototype-breakpoints !default; + +/// Map containing all the `position` classes +/// @type Map +$prototype-position: ( + static, + relative, + absolute, + fixed +) !default; + +/// z-index for fixed positioning +/// @type Number +$prototype-position-z-index: 975 !default; + +/// Position classes, by default coming through a map `$prototype-position`, whereas all the offset values are multiplied by `$global-position` which by default is equal to `1rem`. +/// @param {String} $position [] Position classes, Either `static`, `relative`, `absolute` or `fixed` +/// @param {Number} $top [null] - Top offset +/// @param {Number} $right [null] - Right offset +/// @param {Number} $bottom [null] - Bottom offset +/// @param {Number} $left [null] - Left offset +@mixin position( + $position, + $top: null, + $right: null, + $bottom: null, + $left: null +) { + position: $position !important; + @if $top != null { + top: $top * $global-position !important; + } + @if $right != null { + right: $right * $global-position !important; + } + @if $bottom != null { + bottom: $bottom * $global-position !important; + } + @if $left != null { + left: $left * $global-position !important; + } +} + +/// Position Fixed on top corners +/// @param {Number} $z-index [$prototype-position-z-index] z-index for `position-fixed-top` +@mixin position-fixed-top( + $z-index: $prototype-position-z-index +) { + @include position(fixed, 0, 0, null, 0); + z-index: $z-index; +} + +/// Position Fixed on bottom corners +/// @param {Number} $z-index [$prototype-position-z-index] z-index for `position-fixed-bottom` +@mixin position-fixed-bottom( + $z-index: $prototype-position-z-index +) { + @include position(fixed, null, 0, 0, 0); + z-index: $z-index; +} + +@mixin foundation-prototype-position { + // Position: Static, Relative, Fixed, Absolute + @each $position in $prototype-position { + .position-#{$position} { + @include position($position); + } + } + + // Position: Fixed Top, Fixed Bottom + .position-fixed-top { + @include position-fixed-top; + } + .position-fixed-bottom { + @include position-fixed-bottom; + } + + @if ($prototype-position-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + // Position: Static, Relative, Fixed, Absolute + @each $position in $prototype-position { + @if $size != $-zf-zero-breakpoint { + .#{$size}-position-#{$position} { + @include position($position); + } + } + } + + // Position: Fixed Top, Fixed Bottom + @if $size != $-zf-zero-breakpoint { + .#{$size}-position-fixed-top { + @include position-fixed-top; + } + + .#{$size}-position-fixed-bottom { + @include position-fixed-bottom; + } + } + } + } + } +} diff --git a/src/foundation/prototype/_prototype.scss b/src/foundation/prototype/_prototype.scss new file mode 100644 index 0000000..c0189db --- /dev/null +++ b/src/foundation/prototype/_prototype.scss @@ -0,0 +1,87 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype +//// + +// Relational Mixins +@import 'relation'; + +// Box Mixin +@import 'box'; + +// Rotate Mixin +@import 'rotate'; + +// Text utilities +@import 'text-utilities'; + +// Text transformation classes +@import 'text-transformation'; + +// Text Decoration classes +@import 'text-decoration'; + +// Font Styling +@import 'font-styling'; + +// List Style type +@import 'list-style-type'; + +// Rounded Utility +@import 'rounded'; + +// Bordered Utility +@import 'bordered'; + +// Shadow Utility +@import 'shadow'; + +// Arrow Utility +@import 'arrow'; + +// Separator Utility +@import 'separator'; + +// Overflow helper classes +@import 'overflow'; + +// Display classes +@import 'display'; + +// Position Helpers +@import 'position'; + +// Border box +@import 'border-box'; + +// Border none Utilty +@import 'border-none'; + +// Sizing Utilities +@import 'sizing'; + +// Spacing Utilities +@import 'spacing'; + +@mixin foundation-prototype-classes { + @include foundation-prototype-text-utilities; + @include foundation-prototype-text-transformation; + @include foundation-prototype-text-decoration; + @include foundation-prototype-font-styling; + @include foundation-prototype-list-style-type; + @include foundation-prototype-rounded; + @include foundation-prototype-bordered; + @include foundation-prototype-shadow; + @include foundation-prototype-arrow; + @include foundation-prototype-separator; + @include foundation-prototype-overflow; + @include foundation-prototype-display; + @include foundation-prototype-position; + @include foundation-prototype-border-box; + @include foundation-prototype-border-none; + @include foundation-prototype-sizing; + @include foundation-prototype-spacing; +} diff --git a/src/foundation/prototype/_relation.scss b/src/foundation/prototype/_relation.scss new file mode 100644 index 0000000..87a3b35 --- /dev/null +++ b/src/foundation/prototype/_relation.scss @@ -0,0 +1,157 @@ +/// Select all children from the first to `$num`. +/// @param {Number} $num[] First `n` numbers of total children +@mixin first($num) { + @if $num == 1 { + &:first-child { + @content; + } + } @else { + &:nth-child(-n + #{$num}) { + @content; + } + } +} + +/// Select the first exact child +@mixin first-child { + &:first-of-type { + @content; + } +} + +/// Select all children from the last to `$num`. +/// @param {Number} $num[] Last `n` numbers of total children +@mixin last($num) { + &:nth-last-child(-n + #{$num}) { + @content; + } +} + +/// Select the last exact child +@mixin last-child { + &:last-of-type { + @content; + } +} + +/// Select children every `$num`. +/// @param {Number} $num[] Every `n` number of all children +@mixin every($num) { + &:nth-child(#{$num}n) { + @content; + } +} + +/// Select only the first and last child. +@mixin first-last { + &:first-child, + &:last-child { + @content; + } +} + +/// Select all children after the first to `$num`. +/// @param {Number} $num[] After First `n` numbers of total children +@mixin after-first($num) { + &:nth-child(n + #{$num + 1}) { + @content; + } +} + +/// Select all children before `$num` from the last. +/// @param {Number} $num[] From Last `n` numbers of total children +@mixin from-last($num) { + &:nth-last-child(#{$num}) { + @content; + } +} + +/// Select the `$num` child from the first and the `$num` child from the last. +/// @param {Number} $num[] `n` number called from first and last +@mixin from-first-last($num) { + &:nth-child(#{$num}), + &:nth-last-child(#{$num}) { + @content; + } +} + +/// Select all children but `$num`. +/// @param {Number} $num[] `n` number that should be excluded from all other children +@mixin all-but($num) { + &:not(:nth-child(#{$num})) { + @content; + } +} + +/// Select all children between the `$num` first and the `$num` last. +/// @param {Number} $num[] `n` number excluded from first and last from all other children +@mixin all-but-first-last($num) { + &:nth-child(n + #{$num}):nth-last-child(n + #{$num}) { + @content; + } +} + +/// Will only select the child if it's unique. That means that if there are at least 2 children, the style will not be applied. +@mixin unique { + &:only-child { + @content; + } +} + +/// Will only select children if they are not unique. That means that if there are at least 2 children, the style will be applied. +@mixin not-unique() { + &:not(:only-child) { + @content; + } +} + +/// Select all children between `$first` and `$last`. +/// @param {Number} $first[] First `nth` number +/// @param {Number} $last[] Last `nth` number +@mixin between($first, $last) { + &:nth-child(n + #{$first}):nth-child(-n + #{$last}) { + @content; + } +} + +/// Select all even children. +@mixin even { + &:nth-child(even) { + @content; + } +} + +/// Select all even children between `$first` and `$last`. +/// @param {Number} $first[] First `nth` number +/// @param {Number} $last[] Last `nth` number +@mixin even-between($first, $last) { + &:nth-child(even):nth-child(n + #{$first}):nth-child(-n + #{$last}) { + @content; + } +} + +/// Select all odd children. +@mixin odd { + &:nth-child(odd) { + @content; + } +} + +/// Select all odd children between `$first` and `$last`. +/// @param {Number} $first[] First `nth` number +/// @param {Number} $last[] Last `nth` number +@mixin odd-between($first, $last) { + &:nth-child(odd):nth-child(n + #{$first}):nth-child(-n + #{$last}) { + @content; + } +} + +/// Select all `$num` children between `$first` and `$last`. +/// @param {Number} $num[] Every `n` number between `$first` and `$last`. +/// @param {Number} $first[] First `n` number +/// @param {Number} $last[] Last `n` number +@mixin number-between($num, $first, $last) { + &:nth-child(#{$num}n):nth-child(n + #{$first}):nth-child(-n + #{$last}) { + @content; + } +} diff --git a/src/foundation/prototype/_rotate.scss b/src/foundation/prototype/_rotate.scss new file mode 100644 index 0000000..8231db8 --- /dev/null +++ b/src/foundation/prototype/_rotate.scss @@ -0,0 +1,31 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-rotate +//// + +/// Rotate Mixin: Rotate an element to a certain deg +/// @param {Number} $deg[] Degree of rotation +@mixin rotate($deg) { + transform:rotate($deg + deg); +} + +/// RotateX Mixin: Rotate an element to a certain deg on X-Axis +/// @param {Number} $deg[] Degree of rotation +@mixin rotateX($deg) { + transform:rotateX($deg + deg); +} + +/// RotateY Mixin: Rotate an element to a certain deg on Y-Axis +/// @param {Number} $deg[] Degree of rotation +@mixin rotateY($deg) { + transform:rotateY($deg + deg); +} + +/// RotateZ Mixin: Rotate an element to a certain deg on Z-Axis +/// @param {Number} $deg[] Degree of rotation +@mixin rotateZ($deg) { + transform:rotateZ($deg + deg); +}
\ No newline at end of file diff --git a/src/foundation/prototype/_rounded.scss b/src/foundation/prototype/_rounded.scss new file mode 100644 index 0000000..225fb55 --- /dev/null +++ b/src/foundation/prototype/_rounded.scss @@ -0,0 +1,61 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-rounded +//// + +/// Responsive breakpoints for rounded utility. +/// @type Boolean +$prototype-rounded-breakpoints: $global-prototype-breakpoints !default; + +/// Default value for `prototype-border-radius` +/// @type Number +$prototype-border-radius: rem-calc(3) !default; + +/// Rounded utility (all corners): Adds radius corners (all corners) to an element by default. +/// @param {Number} $radius [$prototype-border-radius] Border radius (all corners) +@mixin border-radius( + $radius: $prototype-border-radius +) { + border-radius: $radius; +} + +/// Rounded square utility or rectangle utility (all corners): Rounds all corners to an element by default to make a pill shape. +@mixin border-rounded { + border-radius: 5000px !important; +} + +@mixin foundation-prototype-rounded { + .rounded { + @include border-rounded; + + .switch-paddle { + @include border-rounded; + &:after { + border-radius: 50%; // For switches + } + } + } + + .radius { + @include border-radius; + } + + @if ($prototype-rounded-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-rounded { + @include border-rounded; + } + .#{$size}-radius { + @include border-radius; + } + } + } + } + } +} diff --git a/src/foundation/prototype/_separator.scss b/src/foundation/prototype/_separator.scss new file mode 100644 index 0000000..9baf121 --- /dev/null +++ b/src/foundation/prototype/_separator.scss @@ -0,0 +1,96 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-separator +//// + +/// Responsive breakpoints for separator. +/// @type Boolean +$prototype-separator-breakpoints: $global-prototype-breakpoints !default; + +/// Default alignment of a separator. +/// @type String +$prototype-separator-align: center !default; + +/// Height of a separator. +/// @type Number +$prototype-separator-height: rem-calc(2) !default; + +/// Width of a separator. +/// @type Number +$prototype-separator-width: 3rem !default; + +/// Default color of a separator. +/// @type Color +$prototype-separator-background: $primary-color !default; + +/// Top Margin of a separator. +/// @type Number +$prototype-separator-margin-top: $global-margin !default; + +/// Title separator Utility, mostly used to style the main heading of a section +/// @param {String} $align [$prototype-separator-align] - separator Alignment +/// @param {Number} $height [$prototype-separator-height] - Width +/// @param {Number} $width [$prototype-separator-width] - Height +/// @param {Color} $background [$prototype-separator-background] - Background +/// @param {Number} $top [$prototype-separator-margin-top] - Margin Top +@mixin separator ( + $align: $prototype-separator-align, + $height: $prototype-separator-height, + $width: $prototype-separator-width, + $background: $prototype-separator-background, + $top: $prototype-separator-margin-top +) { + text-align: $align !important; + @include clearfix; + + &::after { + @include position(relative); + width: $width; + border-bottom: $height solid $background; + margin: $top auto 0; + + @if $align == left { + margin-left: 0 !important; + } + + @if $align == right { + margin-right: 0 !important; + } + } +} + +@mixin foundation-prototype-separator { + .separator-center { + @include separator(center); + } + + .separator-left { + @include separator(left); + } + + .separator-right { + @include separator(right); + } + + @if ($prototype-separator-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-separator-center { + @include separator(center); + } + .#{$size}-separator-left { + @include separator(left); + } + .#{$size}-separator-right { + @include separator(right); + } + } + } + } + } +} diff --git a/src/foundation/prototype/_shadow.scss b/src/foundation/prototype/_shadow.scss new file mode 100644 index 0000000..abb44a5 --- /dev/null +++ b/src/foundation/prototype/_shadow.scss @@ -0,0 +1,43 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-shadow +//// + +/// Responsive breakpoints for shadow utility. +/// @type Boolean +$prototype-shadow-breakpoints: $global-prototype-breakpoints !default; + +/// Default value for `prototype-box-shadow` +/// @type Number +$prototype-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), + 0 2px 10px 0 rgba(0,0,0,.12) !default; + +/// Shadow Utility: Adds a light box shadow to an element by default. +/// @param {Number} $shadow [$prototype-box-shadow] Box Shadow of a component +@mixin shadow( + $shadow: $prototype-box-shadow +) { + box-shadow: $shadow; +} + +@mixin foundation-prototype-shadow { + .shadow { + @include shadow; + } + + @if ($prototype-shadow-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-shadow { + @include shadow; + } + } + } + } + } +} diff --git a/src/foundation/prototype/_sizing.scss b/src/foundation/prototype/_sizing.scss new file mode 100644 index 0000000..9b48d48 --- /dev/null +++ b/src/foundation/prototype/_sizing.scss @@ -0,0 +1,73 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-sizing +//// + +/// Responsive breakpoints for spacing classes (margin and padding) +/// @type Boolean +$prototype-sizing-breakpoints: $global-prototype-breakpoints !default; + +/// Map containing all the `sizing` classes +/// @type Map +$prototype-sizing: ( + width, + height +) !default; + +/// Map containing all the sizes. +/// @type Map +$prototype-sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100% +) !default; + +/// Max Width 100 utility. +@mixin max-width-100 { + max-width: 100% !important; +} + +/// Max Height 100 utility. +@mixin max-height-100 { + max-height: 100% !important; +} + +@mixin foundation-prototype-sizing { + // Element Sizing + @each $sizing in $prototype-sizing { + @each $length, $percentage in $prototype-sizes { + .#{$sizing}-#{$length} { + #{$sizing}: $percentage !important; + } + } + } + + // Max width & height + .max-width-100 { + @include max-width-100; + } + .max-height-100 { + @include max-height-100; + } + + @if ($prototype-sizing-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + @each $sizing in $prototype-sizing { + @each $length, $percentage in $prototype-sizes { + .#{$size}-#{$sizing}-#{$length} { + #{$sizing}: $percentage !important; + } + } + } + } + } + } + } +} diff --git a/src/foundation/prototype/_spacing.scss b/src/foundation/prototype/_spacing.scss new file mode 100644 index 0000000..3129a2b --- /dev/null +++ b/src/foundation/prototype/_spacing.scss @@ -0,0 +1,179 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-spacing +//// + +/// Responsive breakpoints for spacing classes (margin and padding) +/// @type Boolean +$prototype-spacing-breakpoints: $global-prototype-breakpoints !default; + +/// Default number of spacers count (margin and padding) +/// @type Number +$prototype-spacers-count: 3 !default; + +/// Margin helper mixin, all the values are multiplied by `$global-margin` which by default is equal to `1rem` +/// @param {Number} $top [null] - Margin Top +/// @param {Number} $right [null] - Margin Right +/// @param {Number} $bottom [null] - Margin Bottom +/// @param {Number} $left [null] - Margin Left +@mixin margin( + $top: null, + $right: null, + $bottom: null, + $left: null +) { + @if $top != null { + margin-top: $top * $global-margin !important; + } + @if $right != null { + margin-right: $right * $global-margin !important; + } + @if $bottom != null { + margin-bottom: $bottom * $global-margin !important; + } + @if $left != null { + margin-left: $left * $global-margin !important; + } +} + +/// Padding helper mixin, all the values are multiplied by `$global-padding` which by default is equal to `1rem` +/// @param {Number} $top [null] - Padding Top +/// @param {Number} $right [null] - Padding Right +/// @param {Number} $bottom [null] - Padding Bottom +/// @param {Number} $left [null] - Padding Left +@mixin padding( + $top: null, + $right: null, + $bottom: null, + $left: null +) { + @if $top != null { + padding-top: $top * $global-padding !important; + } + @if $right != null { + padding-right: $right * $global-padding !important; + } + @if $bottom != null { + padding-bottom: $bottom * $global-padding !important; + } + @if $left != null { + padding-left: $left * $global-padding !important; + } +} + +/// Margin classes for specific direction properties +/// @param {String} $dir [] Direction +/// @param {Number} $spacer [] Spacer +@mixin margin-direction($dir, $spacer) { + @if ($dir == top) { + @include margin($top: $spacer); + } + @else if ($dir == right) { + @include margin($right: $spacer); + } + @else if ($dir == bottom) { + @include margin($bottom: $spacer); + } + @else if ($dir == left) { + @include margin($left: $spacer); + } + @else if ($dir == horizontal) { + @include margin($right: $spacer, $left: $spacer); + } + @else if ($dir == vertical) { + @include margin($top: $spacer, $bottom: $spacer); + } +} + +/// Padding classes for specific direction properties +/// @param {String} $dir [] Direction +/// @param {Number} $spacer [] Spacer +@mixin padding-direction($dir, $spacer) { + @if ($dir == top) { + @include padding($top: $spacer); + } + @else if ($dir == right) { + @include padding($right: $spacer); + } + @else if ($dir == bottom) { + @include padding($bottom: $spacer); + } + @else if ($dir == left) { + @include padding($left: $spacer); + } + @else if ($dir == horizontal) { + @include padding($right: $spacer, $left: $spacer); + } + @else if ($dir == vertical) { + @include padding($top: $spacer, $bottom: $spacer); + } +} + +@mixin foundation-prototype-spacing { + @for $spacer from 0 through $prototype-spacers-count { + + @each $prop in (margin, padding) { + // All Sides + .#{$prop}-#{$spacer} { + @if ($prop == margin) { + margin: $spacer * $global-margin !important; + } + @else if ($prop == padding) { + padding: $spacer * $global-padding !important; + } + } + + @each $dir in (top, right, bottom, left, horizontal, vertical) { + // Top Side + .#{$prop}-#{$dir}-#{$spacer} { + @if ($prop == margin) { + @include margin-direction($dir, $spacer); + } + @else if ($prop == padding) { + @include padding-direction($dir, $spacer); + } + } + } + } + } + + @if ($prototype-spacing-breakpoints) { + @for $spacer from 0 through $prototype-spacers-count { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size} { + @each $prop in (margin, padding) { + // All Sides + &-#{$prop}-#{$spacer} { + @if ($prop == margin) { + margin: $spacer * $global-margin !important; + } + @else if ($prop == padding) { + padding: $spacer * $global-padding !important; + } + } + + @each $dir in (top, right, bottom, left, horizontal, vertical) { + // Top Side + &-#{$prop}-#{$dir}-#{$spacer} { + @if ($prop == margin) { + @include margin-direction($dir, $spacer); + } + @else if ($prop == padding) { + @include padding-direction($dir, $spacer); + } + } + } + } + } + } + } + } + } + } +} diff --git a/src/foundation/prototype/_text-decoration.scss b/src/foundation/prototype/_text-decoration.scss new file mode 100644 index 0000000..8f3c913 --- /dev/null +++ b/src/foundation/prototype/_text-decoration.scss @@ -0,0 +1,48 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-text-decoration +//// + +/// Responsive breakpoints for text decoration classes +/// @type Boolean +$prototype-decoration-breakpoints: $global-prototype-breakpoints !default; + +/// Map containing all the `text-decoration` classes +/// @type Map +$prototype-text-decoration: ( + overline, + underline, + line-through, +) !default; + +/// Text Decoration, by default coming through a map `$prototype-text-decoration` +/// @param {String} $decoration [] Text Decoration +@mixin text-decoration($decoration) { + text-decoration: $decoration !important; +} + +@mixin foundation-prototype-text-decoration { + @each $decoration in $prototype-text-decoration { + .text-#{$decoration} { + @include text-decoration($decoration); + } + } + + @if ($prototype-decoration-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @each $decoration in $prototype-text-decoration { + @if $size != $-zf-zero-breakpoint { + .#{$size}-text-#{$decoration} { + @include text-decoration($decoration); + } + } + } + } + } + } +} diff --git a/src/foundation/prototype/_text-transformation.scss b/src/foundation/prototype/_text-transformation.scss new file mode 100644 index 0000000..ca9c2e4 --- /dev/null +++ b/src/foundation/prototype/_text-transformation.scss @@ -0,0 +1,48 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-text-transformation +//// + +/// Responsive breakpoints for text transformation classes +/// @type Boolean +$prototype-transformation-breakpoints: $global-prototype-breakpoints !default; + +/// Map containing all the `text-transformation` classes +/// @type Map +$prototype-text-transformation: ( + lowercase, + uppercase, + capitalize +) !default; + +/// Text Transformation, by default coming through a map `$prototype-text-transformation` +/// @param {String} $transformation [] Text Transformation +@mixin text-transform($transformation) { + text-transform: $transformation !important; +} + +@mixin foundation-prototype-text-transformation { + @each $transformation in $prototype-text-transformation { + .text-#{$transformation} { + @include text-transform($transformation); + } + } + + @if ($prototype-transformation-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @each $transformation in $prototype-text-transformation { + @if $size != $-zf-zero-breakpoint { + .#{$size}-text-#{$transformation} { + @include text-transform($transformation); + } + } + } + } + } + } +} diff --git a/src/foundation/prototype/_text-utilities.scss b/src/foundation/prototype/_text-utilities.scss new file mode 100644 index 0000000..8c327a2 --- /dev/null +++ b/src/foundation/prototype/_text-utilities.scss @@ -0,0 +1,88 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group prototype-text-utilities +//// + +/// Responsive breakpoints for text utilities +/// @type Boolean +$prototype-utilities-breakpoints: $global-prototype-breakpoints !default; + +/// Default Value for `text-overflow` variable +/// @type String +$prototype-text-overflow: ellipsis !default; + +/// Image Replacement utility. `text-hide` +@mixin text-hide { + font: 0/0 a !important; + color: transparent !important; + text-shadow: none !important; + background-color: transparent !important; + border: 0 !important; +} + +/// Truncating the text, elipsis by default. +/// @param {String} $overflow [$prototype-text-overflow] Text Truncate +@mixin text-truncate( + $overflow: $prototype-text-overflow +) { + max-width: 100% !important; + overflow: hidden !important; + text-overflow: $overflow; + white-space: nowrap !important; +} + +/// No wrapping of the text. `text-nowrap` +@mixin text-nowrap { + white-space: nowrap !important; +} + +/// Wrapping of the text. `text-wrap` +@mixin text-wrap { + word-wrap: break-word !important; +} + +@mixin foundation-prototype-text-utilities { + .text-hide { + @include text-hide; + } + + .text-truncate { + @include text-truncate; + } + + .text-nowrap { + @include text-nowrap; + } + + .text-wrap { + @include text-wrap; + } + + @if ($prototype-utilities-breakpoints) { + // Loop through Responsive Breakpoints + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-text-hide { + @include text-hide; + } + + .#{$size}-text-truncate { + @include text-truncate; + } + + .#{$size}-text-nowrap { + @include text-nowrap; + } + + .#{$size}-text-wrap { + @include text-wrap; + } + } + } + } + } +} diff --git a/src/foundation/settings/_settings.scss b/src/foundation/settings/_settings.scss new file mode 100644 index 0000000..8a9ab56 --- /dev/null +++ b/src/foundation/settings/_settings.scss @@ -0,0 +1,895 @@ +// Foundation for Sites Settings +// ----------------------------- +// +// Table of Contents: +// +// 1. Global +// 2. Breakpoints +// 3. The Grid +// 4. Base Typography +// 5. Typography Helpers +// 6. Abide +// 7. Accordion +// 8. Accordion Menu +// 9. Badge +// 10. Breadcrumbs +// 11. Button +// 12. Button Group +// 13. Callout +// 14. Card +// 15. Close Button +// 16. Drilldown +// 17. Dropdown +// 18. Dropdown Menu +// 19. Flexbox Utilities +// 20. Forms +// 21. Label +// 22. Media Object +// 23. Menu +// 24. Meter +// 25. Off-canvas +// 26. Orbit +// 27. Pagination +// 28. Progress Bar +// 29. Prototype Arrow +// 30. Prototype Border-Box +// 31. Prototype Border-None +// 32. Prototype Bordered +// 33. Prototype Display +// 34. Prototype Font-Styling +// 35. Prototype List-Style-Type +// 36. Prototype Overflow +// 37. Prototype Position +// 38. Prototype Rounded +// 39. Prototype Separator +// 40. Prototype Shadow +// 41. Prototype Sizing +// 42. Prototype Spacing +// 43. Prototype Text-Decoration +// 44. Prototype Text-Transformation +// 45. Prototype Text-Utilities +// 46. Responsive Embed +// 47. Reveal +// 48. Slider +// 49. Switch +// 50. Table +// 51. Tabs +// 52. Thumbnail +// 53. Title Bar +// 54. Tooltip +// 55. Top Bar +// 56. Xy Grid + +@import 'util/util'; + +// 1. Global +// --------- + +$global-font-size: 100%; +$global-width: rem-calc(1200); +$global-lineheight: 1.5; +$foundation-palette: ( + primary: #1779ba, + secondary: #767676, + success: #3adb76, + warning: #ffae00, + alert: #cc4b37, +); +$light-gray: #e6e6e6; +$medium-gray: #cacaca; +$dark-gray: #8a8a8a; +$black: #0a0a0a; +$white: #fefefe; +$body-background: $white; +$body-font-color: $black; +$body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; +$body-antialiased: true; +$global-margin: 1rem; +$global-padding: 1rem; +$global-position: 1rem; +$global-weight-normal: normal; +$global-weight-bold: bold; +$global-radius: 0; +$global-menu-padding: 0.7rem 1rem; +$global-menu-nested-margin: 1rem; +$global-text-direction: ltr; +$global-flexbox: true; +$global-prototype-breakpoints: false; +$global-button-cursor: auto; +$global-color-pick-contrast-tolerance: 0; +$print-transparent-backgrounds: true; +$print-hrefs: true; + +@include add-foundation-colors; + +// 2. Breakpoints +// -------------- + +$breakpoints: ( + small: 0, + medium: 640px, + large: 1024px, + xlarge: 1200px, + xxlarge: 1440px, +); +$breakpoints-hidpi: ( + hidpi-1: 1, + hidpi-1-5: 1.5, + hidpi-2: 2, + retina: 2, + hidpi-3: 3 +); +$print-breakpoint: large; +$breakpoint-classes: (small medium large); + +// 3. The Grid +// ----------- + +$grid-row-width: $global-width; +$grid-column-count: 12; +$grid-column-gutter: ( + small: 20px, + medium: 30px, +); +$grid-column-align-edge: true; +$grid-column-alias: 'columns'; +$block-grid-max: 8; + +// 4. Base Typography +// ------------------ + +$header-font-family: $body-font-family; +$header-font-weight: $global-weight-normal; +$header-font-style: normal; +$font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace; +$header-color: inherit; +$header-lineheight: 1.4; +$header-margin-bottom: 0.5rem; +$header-styles: ( + small: ( + 'h1': ('font-size': 24), + 'h2': ('font-size': 20), + 'h3': ('font-size': 19), + 'h4': ('font-size': 18), + 'h5': ('font-size': 17), + 'h6': ('font-size': 16), + ), + medium: ( + 'h1': ('font-size': 48), + 'h2': ('font-size': 40), + 'h3': ('font-size': 31), + 'h4': ('font-size': 25), + 'h5': ('font-size': 20), + 'h6': ('font-size': 16), + ), +); +$header-text-rendering: optimizeLegibility; +$small-font-size: 80%; +$header-small-font-color: $medium-gray; +$paragraph-lineheight: 1.6; +$paragraph-margin-bottom: 1rem; +$paragraph-text-rendering: optimizeLegibility; +$enable-code-inline: true; +$anchor-color: $primary-color; +$anchor-color-hover: scale-color($anchor-color, $lightness: -14%); +$anchor-text-decoration: none; +$anchor-text-decoration-hover: none; +$hr-width: $global-width; +$hr-border: 1px solid $medium-gray; +$hr-margin: rem-calc(20) auto; +$list-lineheight: $paragraph-lineheight; +$list-margin-bottom: $paragraph-margin-bottom; +$list-style-type: disc; +$list-style-position: outside; +$list-side-margin: 1.25rem; +$list-nested-side-margin: 1.25rem; +$defnlist-margin-bottom: 1rem; +$defnlist-term-weight: $global-weight-bold; +$defnlist-term-margin-bottom: 0.3rem; +$blockquote-color: $dark-gray; +$blockquote-padding: rem-calc(9 20 0 19); +$blockquote-border: 1px solid $medium-gray; +$enable-cite-block: true; +$keystroke-font: $font-family-monospace; +$keystroke-color: $black; +$keystroke-background: $light-gray; +$keystroke-padding: rem-calc(2 4 0); +$keystroke-radius: $global-radius; +$abbr-underline: 1px dotted $black; + +// 5. Typography Helpers +// --------------------- + +$lead-font-size: $global-font-size * 1.25; +$lead-lineheight: 1.6; +$subheader-lineheight: 1.4; +$subheader-color: $dark-gray; +$subheader-font-weight: $global-weight-normal; +$subheader-margin-top: 0.2rem; +$subheader-margin-bottom: 0.5rem; +$stat-font-size: 2.5rem; +$cite-color: $dark-gray; +$cite-font-size: rem-calc(13); +$cite-pseudo-content: '\2014 \0020'; +$code-color: $black; +$code-font-family: $font-family-monospace; +$code-font-weight: $global-weight-normal; +$code-background: $light-gray; +$code-border: 1px solid $medium-gray; +$code-padding: rem-calc(2 5 1); +$code-block-padding: 1rem; +$code-block-margin-bottom: 1.5rem; + +// 6. Abide +// -------- + +$abide-inputs: true; +$abide-labels: true; +$input-background-invalid: get-color(alert); +$form-label-color-invalid: get-color(alert); +$input-error-color: get-color(alert); +$input-error-font-size: rem-calc(12); +$input-error-font-weight: $global-weight-bold; + +// 7. Accordion +// ------------ + +$accordion-background: $white; +$accordion-plusminus: true; +$accordion-plus-content: '\002B'; +$accordion-minus-content: '\2013'; +$accordion-title-font-size: rem-calc(12); +$accordion-item-color: $primary-color; +$accordion-item-background-hover: $light-gray; +$accordion-item-padding: 1.25rem 1rem; +$accordion-content-background: $white; +$accordion-content-border: 1px solid $light-gray; +$accordion-content-color: $body-font-color; +$accordion-content-padding: 1rem; + +// 8. Accordion Menu +// ----------------- + +$accordionmenu-padding: $global-menu-padding; +$accordionmenu-nested-margin: $global-menu-nested-margin; +$accordionmenu-submenu-padding: $accordionmenu-padding; +$accordionmenu-arrows: true; +$accordionmenu-arrow-color: $primary-color; +$accordionmenu-item-background: null; +$accordionmenu-border: null; +$accordionmenu-submenu-toggle-background: null; +$accordion-submenu-toggle-border: $accordionmenu-border; +$accordionmenu-submenu-toggle-width: 40px; +$accordionmenu-submenu-toggle-height: $accordionmenu-submenu-toggle-width; +$accordionmenu-arrow-size: 6px; + +// 9. Badge +// -------- + +$badge-background: $primary-color; +$badge-color: $white; +$badge-color-alt: $black; +$badge-palette: $foundation-palette; +$badge-padding: 0.3em; +$badge-minwidth: 2.1em; +$badge-font-size: 0.6rem; + +// 10. Breadcrumbs +// --------------- + +$breadcrumbs-margin: 0 0 $global-margin 0; +$breadcrumbs-item-font-size: rem-calc(11); +$breadcrumbs-item-color: $primary-color; +$breadcrumbs-item-color-current: $black; +$breadcrumbs-item-color-disabled: $medium-gray; +$breadcrumbs-item-margin: 0.75rem; +$breadcrumbs-item-uppercase: true; +$breadcrumbs-item-separator: true; +$breadcrumbs-item-separator-item: '/'; +$breadcrumbs-item-separator-item-rtl: '\\'; +$breadcrumbs-item-separator-color: $medium-gray; + +// 11. Button +// ---------- + +$button-font-family: inherit; +$button-font-weight: null; +$button-padding: 0.85em 1em; +$button-margin: 0 0 $global-margin 0; +$button-fill: solid; +$button-background: $primary-color; +$button-background-hover: scale-color($button-background, $lightness: -15%); +$button-color: $white; +$button-color-alt: $black; +$button-radius: $global-radius; +$button-border: 1px solid transparent; +$button-hollow-border-width: 1px; +$button-sizes: ( + tiny: 0.6rem, + small: 0.75rem, + default: 0.9rem, + large: 1.25rem, +); +$button-palette: $foundation-palette; +$button-opacity-disabled: 0.25; +$button-background-hover-lightness: -20%; +$button-hollow-hover-lightness: -50%; +$button-transition: background-color 0.25s ease-out, color 0.25s ease-out; +$button-responsive-expanded: false; + +// 12. Button Group +// ---------------- + +$buttongroup-margin: 1rem; +$buttongroup-spacing: 1px; +$buttongroup-child-selector: '.button'; +$buttongroup-expand-max: 6; +$buttongroup-radius-on-each: true; + +// 13. Callout +// ----------- + +$callout-background: $white; +$callout-background-fade: 85%; +$callout-border: 1px solid rgba($black, 0.25); +$callout-margin: 0 0 1rem 0; +$callout-sizes: ( + small: 0.5rem, + default: 1rem, + large: 3rem, +); +$callout-font-color: $body-font-color; +$callout-font-color-alt: $body-background; +$callout-radius: $global-radius; +$callout-link-tint: 30%; + +// 14. Card +// -------- + +$card-background: $white; +$card-font-color: $body-font-color; +$card-divider-background: $light-gray; +$card-border: 1px solid $light-gray; +$card-shadow: none; +$card-border-radius: $global-radius; +$card-padding: $global-padding; +$card-margin-bottom: $global-margin; + +// 15. Close Button +// ---------------- + +$closebutton-position: right top; +$closebutton-z-index: 10; +$closebutton-default-size: medium; +$closebutton-offset-horizontal: ( + small: 0.66rem, + medium: 1rem, +); +$closebutton-offset-vertical: ( + small: 0.33em, + medium: 0.5rem, +); +$closebutton-size: ( + small: 1.5em, + medium: 2em, +); +$closebutton-lineheight: 1; +$closebutton-color: $dark-gray; +$closebutton-color-hover: $black; + +// 16. Drilldown +// ------------- + +$drilldown-transition: transform 0.15s linear; +$drilldown-arrows: true; +$drilldown-padding: $global-menu-padding; +$drilldown-nested-margin: 0; +$drilldown-background: $white; +$drilldown-submenu-padding: $drilldown-padding; +$drilldown-submenu-background: $white; +$drilldown-arrow-color: $primary-color; +$drilldown-arrow-size: 6px; + +// 17. Dropdown +// ------------ + +$dropdown-padding: 1rem; +$dropdown-background: $body-background; +$dropdown-border: 1px solid $medium-gray; +$dropdown-font-size: 1rem; +$dropdown-width: 300px; +$dropdown-radius: $global-radius; +$dropdown-sizes: ( + tiny: 100px, + small: 200px, + large: 400px, +); + +// 18. Dropdown Menu +// ----------------- + +$dropdownmenu-arrows: true; +$dropdownmenu-arrow-color: $anchor-color; +$dropdownmenu-arrow-size: 6px; +$dropdownmenu-arrow-padding: 1.5rem; +$dropdownmenu-min-width: 200px; +$dropdownmenu-background: null; +$dropdownmenu-submenu-background: $white; +$dropdownmenu-padding: $global-menu-padding; +$dropdownmenu-nested-margin: 0; +$dropdownmenu-submenu-padding: $dropdownmenu-padding; +$dropdownmenu-border: 1px solid $medium-gray; +$dropdown-menu-item-color-active: get-color(primary); +$dropdown-menu-item-background-active: transparent; + +// 19. Flexbox Utilities +// --------------------- + +$flex-source-ordering-count: 6; +$flexbox-responsive-breakpoints: true; + +// 20. Forms +// --------- + +$fieldset-border: 1px solid $medium-gray; +$fieldset-padding: rem-calc(20); +$fieldset-margin: rem-calc(18 0); +$legend-padding: rem-calc(0 3); +$form-spacing: rem-calc(16); +$helptext-color: $black; +$helptext-font-size: rem-calc(13); +$helptext-font-style: italic; +$input-prefix-color: $black; +$input-prefix-background: $light-gray; +$input-prefix-border: 1px solid $medium-gray; +$input-prefix-padding: 1rem; +$form-label-color: $black; +$form-label-font-size: rem-calc(14); +$form-label-font-weight: $global-weight-normal; +$form-label-line-height: 1.8; +$select-background: $white; +$select-triangle-color: $dark-gray; +$select-radius: $global-radius; +$input-color: $black; +$input-placeholder-color: $medium-gray; +$input-font-family: inherit; +$input-font-size: rem-calc(16); +$input-font-weight: $global-weight-normal; +$input-line-height: $global-lineheight; +$input-background: $white; +$input-background-focus: $white; +$input-background-disabled: $light-gray; +$input-border: 1px solid $medium-gray; +$input-border-focus: 1px solid $dark-gray; +$input-padding: $form-spacing / 2; +$input-shadow: inset 0 1px 2px rgba($black, 0.1); +$input-shadow-focus: 0 0 5px $medium-gray; +$input-cursor-disabled: not-allowed; +$input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out; +$input-number-spinners: true; +$input-radius: $global-radius; +$form-button-radius: $global-radius; + +// 21. Label +// --------- + +$label-background: $primary-color; +$label-color: $white; +$label-color-alt: $black; +$label-palette: $foundation-palette; +$label-font-size: 0.8rem; +$label-padding: 0.33333rem 0.5rem; +$label-radius: $global-radius; + +// 22. Media Object +// ---------------- + +$mediaobject-margin-bottom: $global-margin; +$mediaobject-section-padding: $global-padding; +$mediaobject-image-width-stacked: 100%; + +// 23. Menu +// -------- + +$menu-margin: 0; +$menu-nested-margin: $global-menu-nested-margin; +$menu-items-padding: $global-menu-padding; +$menu-simple-margin: 1rem; +$menu-item-color-active: $white; +$menu-item-color-alt-active: $black; +$menu-item-background-active: get-color(primary); +$menu-icon-spacing: 0.25rem; +$menu-state-back-compat: true; +$menu-centered-back-compat: true; +$menu-icons-back-compat: true; + +// 24. Meter +// --------- + +$meter-height: 1rem; +$meter-radius: $global-radius; +$meter-background: $medium-gray; +$meter-fill-good: $success-color; +$meter-fill-medium: $warning-color; +$meter-fill-bad: $alert-color; + +// 25. Off-canvas +// -------------- + +$offcanvas-sizes: ( + small: 250px, +); +$offcanvas-vertical-sizes: ( + small: 250px, +); +$offcanvas-background: $light-gray; +$offcanvas-shadow: 0 0 10px rgba($black, 0.7); +$offcanvas-inner-shadow-size: 20px; +$offcanvas-inner-shadow-color: rgba($black, 0.25); +$offcanvas-overlay-zindex: 11; +$offcanvas-push-zindex: 12; +$offcanvas-overlap-zindex: 13; +$offcanvas-reveal-zindex: 12; +$offcanvas-transition-length: 0.5s; +$offcanvas-transition-timing: ease; +$offcanvas-fixed-reveal: true; +$offcanvas-exit-background: rgba($white, 0.25); +$maincontent-class: 'off-canvas-content'; + +// 26. Orbit +// --------- + +$orbit-bullet-background: $medium-gray; +$orbit-bullet-background-active: $dark-gray; +$orbit-bullet-diameter: 1.2rem; +$orbit-bullet-margin: 0.1rem; +$orbit-bullet-margin-top: 0.8rem; +$orbit-bullet-margin-bottom: 0.8rem; +$orbit-caption-background: rgba($black, 0.5); +$orbit-caption-padding: 1rem; +$orbit-control-background-hover: rgba($black, 0.5); +$orbit-control-padding: 1rem; +$orbit-control-zindex: 10; + +// 27. Pagination +// -------------- + +$pagination-font-size: rem-calc(14); +$pagination-margin-bottom: $global-margin; +$pagination-item-color: $black; +$pagination-item-padding: rem-calc(3 10); +$pagination-item-spacing: rem-calc(1); +$pagination-radius: $global-radius; +$pagination-item-background-hover: $light-gray; +$pagination-item-background-current: $primary-color; +$pagination-item-color-current: $white; +$pagination-item-color-disabled: $medium-gray; +$pagination-ellipsis-color: $black; +$pagination-mobile-items: false; +$pagination-mobile-current-item: false; +$pagination-arrows: true; +$pagination-arrow-previous: '\00AB'; +$pagination-arrow-next: '\00BB'; + +// 28. Progress Bar +// ---------------- + +$progress-height: 1rem; +$progress-background: $medium-gray; +$progress-margin-bottom: $global-margin; +$progress-meter-background: $primary-color; +$progress-radius: $global-radius; + +// 29. Prototype Arrow +// ------------------- + +$prototype-arrow-directions: ( + down, + up, + right, + left +); +$prototype-arrow-size: 0.4375rem; +$prototype-arrow-color: $black; + +// 30. Prototype Border-Box +// ------------------------ + +$prototype-border-box-breakpoints: $global-prototype-breakpoints; + +// 31. Prototype Border-None +// ------------------------- + +$prototype-border-none-breakpoints: $global-prototype-breakpoints; + +// 32. Prototype Bordered +// ---------------------- + +$prototype-bordered-breakpoints: $global-prototype-breakpoints; +$prototype-border-width: rem-calc(1); +$prototype-border-type: solid; +$prototype-border-color: $medium-gray; + +// 33. Prototype Display +// --------------------- + +$prototype-display-breakpoints: $global-prototype-breakpoints; +$prototype-display: ( + inline, + inline-block, + block, + table, + table-cell +); + +// 34. Prototype Font-Styling +// -------------------------- + +$prototype-font-breakpoints: $global-prototype-breakpoints; +$prototype-wide-letter-spacing: rem-calc(4); +$prototype-font-normal: $global-weight-normal; +$prototype-font-bold: $global-weight-bold; + +// 35. Prototype List-Style-Type +// ----------------------------- + +$prototype-list-breakpoints: $global-prototype-breakpoints; +$prototype-style-type-unordered: ( + disc, + circle, + square +); +$prototype-style-type-ordered: ( + decimal, + lower-alpha, + lower-latin, + lower-roman, + upper-alpha, + upper-latin, + upper-roman +); + +// 36. Prototype Overflow +// ---------------------- + +$prototype-overflow-breakpoints: $global-prototype-breakpoints; +$prototype-overflow: ( + visible, + hidden, + scroll +); + +// 37. Prototype Position +// ---------------------- + +$prototype-position-breakpoints: $global-prototype-breakpoints; +$prototype-position: ( + static, + relative, + absolute, + fixed +); +$prototype-position-z-index: 975; + +// 38. Prototype Rounded +// --------------------- + +$prototype-rounded-breakpoints: $global-prototype-breakpoints; +$prototype-border-radius: rem-calc(3); + +// 39. Prototype Separator +// ----------------------- + +$prototype-separator-breakpoints: $global-prototype-breakpoints; +$prototype-separator-align: center; +$prototype-separator-height: rem-calc(2); +$prototype-separator-width: 3rem; +$prototype-separator-background: $primary-color; +$prototype-separator-margin-top: $global-margin; + +// 40. Prototype Shadow +// -------------------- + +$prototype-shadow-breakpoints: $global-prototype-breakpoints; +$prototype-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), + 0 2px 10px 0 rgba(0,0,0,.12); + +// 41. Prototype Sizing +// -------------------- + +$prototype-sizing-breakpoints: $global-prototype-breakpoints; +$prototype-sizing: ( + width, + height +); +$prototype-sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100% +); + +// 42. Prototype Spacing +// --------------------- + +$prototype-spacing-breakpoints: $global-prototype-breakpoints; +$prototype-spacers-count: 3; + +// 43. Prototype Text-Decoration +// ----------------------------- + +$prototype-decoration-breakpoints: $global-prototype-breakpoints; +$prototype-text-decoration: ( + overline, + underline, + line-through, +); + +// 44. Prototype Text-Transformation +// --------------------------------- + +$prototype-transformation-breakpoints: $global-prototype-breakpoints; +$prototype-text-transformation: ( + lowercase, + uppercase, + capitalize +); + +// 45. Prototype Text-Utilities +// ---------------------------- + +$prototype-utilities-breakpoints: $global-prototype-breakpoints; +$prototype-text-overflow: ellipsis; + +// 46. Responsive Embed +// -------------------- + +$responsive-embed-margin-bottom: rem-calc(16); +$responsive-embed-ratios: ( + default: 4 by 3, + widescreen: 16 by 9, +); + +// 47. Reveal +// ---------- + +$reveal-background: $white; +$reveal-width: 600px; +$reveal-max-width: $global-width; +$reveal-padding: $global-padding; +$reveal-border: 1px solid $medium-gray; +$reveal-radius: $global-radius; +$reveal-zindex: 1005; +$reveal-overlay-background: rgba($black, 0.45); + +// 48. Slider +// ---------- + +$slider-width-vertical: 0.5rem; +$slider-transition: all 0.2s ease-in-out; +$slider-height: 0.5rem; +$slider-background: $light-gray; +$slider-fill-background: $medium-gray; +$slider-handle-height: 1.4rem; +$slider-handle-width: 1.4rem; +$slider-handle-background: $primary-color; +$slider-opacity-disabled: 0.25; +$slider-radius: $global-radius; + +// 49. Switch +// ---------- + +$switch-background: $medium-gray; +$switch-background-active: $primary-color; +$switch-height: 2rem; +$switch-height-tiny: 1.5rem; +$switch-height-small: 1.75rem; +$switch-height-large: 2.5rem; +$switch-radius: $global-radius; +$switch-margin: $global-margin; +$switch-paddle-background: $white; +$switch-paddle-offset: 0.25rem; +$switch-paddle-radius: $global-radius; +$switch-paddle-transition: all 0.25s ease-out; +$switch-opacity-disabled: .5; +$switch-cursor-disabled: not-allowed; + +// 50. Table +// --------- + +$table-background: $white; +$table-color-scale: 5%; +$table-border: 1px solid smart-scale($table-background, $table-color-scale); +$table-padding: rem-calc(8 10 10); +$table-hover-scale: 2%; +$table-row-hover: darken($table-background, $table-hover-scale); +$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale); +$table-is-striped: true; +$table-striped-background: smart-scale($table-background, $table-color-scale); +$table-stripe: even; +$table-head-background: smart-scale($table-background, $table-color-scale / 2); +$table-head-row-hover: darken($table-head-background, $table-hover-scale); +$table-foot-background: smart-scale($table-background, $table-color-scale); +$table-foot-row-hover: darken($table-foot-background, $table-hover-scale); +$table-head-font-color: $body-font-color; +$table-foot-font-color: $body-font-color; +$show-header-for-stacked: false; +$table-stack-breakpoint: medium; + +// 51. Tabs +// -------- + +$tab-margin: 0; +$tab-background: $white; +$tab-color: $primary-color; +$tab-background-active: $light-gray; +$tab-active-color: $primary-color; +$tab-item-font-size: rem-calc(12); +$tab-item-background-hover: $white; +$tab-item-padding: 1.25rem 1.5rem; +$tab-content-background: $white; +$tab-content-border: $light-gray; +$tab-content-color: $body-font-color; +$tab-content-padding: 1rem; + +// 52. Thumbnail +// ------------- + +$thumbnail-border: 4px solid $white; +$thumbnail-margin-bottom: $global-margin; +$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2); +$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); +$thumbnail-transition: box-shadow 200ms ease-out; +$thumbnail-radius: $global-radius; + +// 53. Title Bar +// ------------- + +$titlebar-background: $black; +$titlebar-color: $white; +$titlebar-padding: 0.5rem; +$titlebar-text-font-weight: bold; +$titlebar-icon-color: $white; +$titlebar-icon-color-hover: $medium-gray; +$titlebar-icon-spacing: 0.25rem; + +// 54. Tooltip +// ----------- + +$has-tip-cursor: help; +$has-tip-font-weight: $global-weight-bold; +$has-tip-border-bottom: dotted 1px $dark-gray; +$tooltip-background-color: $black; +$tooltip-color: $white; +$tooltip-padding: 0.75rem; +$tooltip-max-width: 10rem; +$tooltip-font-size: $small-font-size; +$tooltip-pip-width: 0.75rem; +$tooltip-pip-height: $tooltip-pip-width * 0.866; +$tooltip-radius: $global-radius; + +// 55. Top Bar +// ----------- + +$topbar-padding: 0.5rem; +$topbar-background: $light-gray; +$topbar-submenu-background: $topbar-background; +$topbar-title-spacing: 0.5rem 1rem 0.5rem 0; +$topbar-input-width: 200px; +$topbar-unstack-breakpoint: medium; + +// 56. Xy Grid +// ----------- + +$xy-grid: true; +$grid-container: $global-width; +$grid-columns: 12; +$grid-margin-gutters: ( + small: 20px, + medium: 30px +); +$grid-padding-gutters: $grid-margin-gutters; +$grid-container-padding: $grid-padding-gutters; +$grid-container-max: $global-width; +$xy-block-grid-max: 8; + diff --git a/src/foundation/typography/_alignment.scss b/src/foundation/typography/_alignment.scss new file mode 100644 index 0000000..77ffd61 --- /dev/null +++ b/src/foundation/typography/_alignment.scss @@ -0,0 +1,22 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +@mixin foundation-text-alignment { + @each $size in $breakpoint-classes { + @include breakpoint($size) { + @each $align in (left, right, center, justify) { + @if $size != $-zf-zero-breakpoint { + .#{$size}-text-#{$align} { + text-align: $align; + } + } + @else { + .text-#{$align} { + text-align: $align; + } + } + } + } + } +} diff --git a/src/foundation/typography/_base.scss b/src/foundation/typography/_base.scss new file mode 100644 index 0000000..8ea58c9 --- /dev/null +++ b/src/foundation/typography/_base.scss @@ -0,0 +1,474 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group typography-base +//// + +// Base Typography +// - - - - - - - - - - - - - - - +// These are styles applied to basic HTML tags, including: +// - Paragraphs <p> +// - Bold/italics <b> <strong> <i> <em> +// - Small text <small> +// - Headings <h1>-<h6> +// - Anchors <a> +// - Dividers <hr> +// - Lists <ul> <ol> <dl> +// - Blockquotes <blockquote> +// - Code blocks <code> +// - Abbreviations <abbr> +// - Citations <cite> +// - Keystrokes <kbd> + +/// Font family for header elements. +/// @type String | List +$header-font-family: $body-font-family !default; + +/// Font weight of headers. +/// @type String +$header-font-weight: $global-weight-normal !default; + +/// Font style (e.g. italicized) of headers. +/// @type String +$header-font-style: normal !default; + +/// Font stack used for elements that use monospaced type, such as code samples +/// @type String | List +$font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace !default; + +/// Color of headers. +/// @type Color +$header-color: inherit !default; + +/// Line height of headers. +/// @type Number +$header-lineheight: 1.4 !default; + +/// Bottom margin of headers. +/// @type Number +$header-margin-bottom: 0.5rem !default; + +/// Styles for headings at various screen sizes. Each key is a breakpoint, and each value is a map of heading styles. +/// @type Map +$header-styles: ( + small: ( + 'h1': ('font-size': 24), + 'h2': ('font-size': 20), + 'h3': ('font-size': 19), + 'h4': ('font-size': 18), + 'h5': ('font-size': 17), + 'h6': ('font-size': 16), + ), + medium: ( + 'h1': ('font-size': 48), + 'h2': ('font-size': 40), + 'h3': ('font-size': 31), + 'h4': ('font-size': 25), + 'h5': ('font-size': 20), + 'h6': ('font-size': 16), + ), +) !default; + +// $header-styles map is built from $header-sizes in order to ensure downward compatibility +// when $header-sizes is depreciated, $header-styles needs to get !default values like settings.scss +@function build_from_header-sizes($header-sizes) { + @warn 'Note, that $header-sizes has been replaced with $header-styles. $header-sizes still works, but it is going to be depreciated.'; + $header-styles: (); + @each $size, $headers in $header-sizes { + $header-map: (); + @each $header, $font-size in $headers { + $header-map: map-merge($header-map, ($header: ('font-size': $font-size))); + } + $header-styles: map-merge($header-styles, ($size: $header-map)); + } + @return $header-styles; +} + +// If it exists $headers-sizes is used to build $header-styles. See the documentation. +@if variable-exists(header-sizes) { + $header-styles: build_from_header-sizes($header-sizes); +} + +/// Text rendering method of headers. +/// @type String +$header-text-rendering: optimizeLegibility !default; + +/// Font size of `<small>` elements. +/// @type Number +$small-font-size: 80% !default; + +/// Color of `<small>` elements when placed inside headers. +/// @type Color +$header-small-font-color: $medium-gray !default; + +/// Line height of text inside `<p>` elements. +/// @type Number +$paragraph-lineheight: 1.6 !default; + +/// Bottom margin of paragraphs. +/// @type Number +$paragraph-margin-bottom: 1rem !default; + +/// Text rendering method for paragraph text. +/// @type String +$paragraph-text-rendering: optimizeLegibility !default; + +/// Use the `.code-inline` component as default for `<code>` elements. +/// @type Boolean +$enable-code-inline: true; + +/// Default color for links. +/// @type Color +$anchor-color: $primary-color !default; + +/// Default color for links on hover. +/// @type Color +$anchor-color-hover: scale-color($anchor-color, $lightness: -14%) !default; + +/// Default text decoration for links. +/// @type String +$anchor-text-decoration: none !default; + +/// Default text decoration for links on hover. +/// @type String +$anchor-text-decoration-hover: none !default; + +/// Maximum width of a divider. +/// @type Number +$hr-width: $global-width !default; + +/// Default border for a divider. +/// @type List +$hr-border: 1px solid $medium-gray !default; + +/// Default margin for a divider. +/// @type Number | List +$hr-margin: rem-calc(20) auto !default; + +/// Line height for items in a list. +/// @type Number +$list-lineheight: $paragraph-lineheight !default; + +/// Bottom margin for items in a list. +/// @type Number +$list-margin-bottom: $paragraph-margin-bottom !default; + +/// Bullet type to use for unordered lists (e.g., `square`, `circle`, `disc`). +/// @type String +$list-style-type: disc !default; + +/// Positioning for bullets on unordered list items. +/// @type String +$list-style-position: outside !default; + +/// Left (or right) margin for lists. +/// @type Number +$list-side-margin: 1.25rem !default; + +/// Left (or right) margin for a list inside a list. +/// @type Number +$list-nested-side-margin: 1.25rem !default; + +/// Bottom margin for `<dl>` elements. +/// @type Number +$defnlist-margin-bottom: 1rem !default; + +/// Font weight for `<dt>` elements. +/// @type String +$defnlist-term-weight: $global-weight-bold !default; + +/// Spacing between `<dt>` and `<dd>` elements. +/// @type Number +$defnlist-term-margin-bottom: 0.3rem !default; + +/// Text color of `<blockquote>` elements. +/// @type Color +$blockquote-color: $dark-gray !default; + +/// Padding inside a `<blockquote>` element. +/// @type Number | List +$blockquote-padding: rem-calc(9 20 0 19) !default; + +/// Side border for `<blockquote>` elements. +/// @type List +$blockquote-border: 1px solid $medium-gray !default; + +/// Use the `.cite-block` component as default for `<cite>` elements. +/// @type Boolean +$enable-cite-block: true; + +/// Font family for `<kbd>` elements. +/// @type String | List +$keystroke-font: $font-family-monospace !default; + +/// Text color for `<kbd>` elements. +/// @type Color +$keystroke-color: $black !default; + +/// Background color for `<kbd>` elements. +/// @type Color +$keystroke-background: $light-gray !default; + +/// Padding for `<kbd>` elements. +/// @type Number | List +$keystroke-padding: rem-calc(2 4 0) !default; + +/// Border radius for `<kbd>` elements. +/// @type Number | List +$keystroke-radius: $global-radius !default; + +/// Bottom border style for `<abbr>` elements. +/// @type List +$abbr-underline: 1px dotted $black !default; + +@mixin foundation-typography-base { + // Typography resets + div, + dl, + dt, + dd, + ul, + ol, + li, + h1, + h2, + h3, + h4, + h5, + h6, + pre, + form, + p, + blockquote, + th, + td { + margin: 0; + padding: 0; + } + + // Paragraphs + p { + margin-bottom: $paragraph-margin-bottom; + + font-size: inherit; + line-height: $paragraph-lineheight; + text-rendering: $paragraph-text-rendering; + } + + // Emphasized text + em, + i { + font-style: italic; + line-height: inherit; + } + + // Strong text + strong, + b { + font-weight: $global-weight-bold; + line-height: inherit; + } + + // Small text + small { + font-size: $small-font-size; + line-height: inherit; + } + + // Headings + h1, .h1, + h2, .h2, + h3, .h3, + h4, .h4, + h5, .h5, + h6, .h6 { + font-family: $header-font-family; + font-style: $header-font-style; + font-weight: $header-font-weight; + color: $header-color; + text-rendering: $header-text-rendering; + + small { + line-height: 0; + color: $header-small-font-color; + } + } + + // Heading styles + @each $size, $headers in $header-styles { + @include breakpoint($size) { + @each $header, $header-defs in $headers { + $font-size-temp: 1rem; + #{$header}, .#{$header} { + + @if map-has-key($header-defs, font-size) { + $font-size-temp: rem-calc(map-get($header-defs, font-size)); + font-size: $font-size-temp; + } @else if map-has-key($header-defs, fs) { + $font-size-temp: rem-calc(map-get($header-defs, fs)); + font-size: $font-size-temp; + } @else if $size == $-zf-zero-breakpoint { + font-size: $font-size-temp; + } + @if map-has-key($header-defs, line-height) { + line-height: unitless-calc(map-get($header-defs, line-height), $font-size-temp); + } @else if map-has-key($header-defs, lh) { + line-height: unitless-calc(map-get($header-defs, lh), $font-size-temp); + } @else if $size == $-zf-zero-breakpoint { + line-height: unitless-calc($header-lineheight, $font-size-temp); + } + + @if map-has-key($header-defs, margin-top) { + margin-top: rem-calc(map-get($header-defs, margin-top)); + } @else if map-has-key($header-defs, mt) { + margin-top: rem-calc(map-get($header-defs, mt)); + } @else if $size == $-zf-zero-breakpoint { + margin-top: 0; + } + @if map-has-key($header-defs, margin-bottom) { + margin-bottom: rem-calc(map-get($header-defs, margin-bottom)); + } @else if map-has-key($header-defs, mb) { + margin-bottom: rem-calc(map-get($header-defs, mb)); + } @else if $size == $-zf-zero-breakpoint { + margin-bottom: rem-calc($header-margin-bottom); + } + } + } + } + } + + // Links + a { + line-height: inherit; + color: $anchor-color; + text-decoration: $anchor-text-decoration; + + cursor: pointer; + + &:hover, + &:focus { + color: $anchor-color-hover; + @if $anchor-text-decoration-hover != $anchor-text-decoration { + text-decoration: $anchor-text-decoration-hover; + } + } + + img { + border: 0; + } + } + + // Horizontal rule + hr { + clear: both; + + max-width: $hr-width; + height: 0; + margin: $hr-margin; + + border-top: 0; + border-right: 0; + border-bottom: $hr-border; + border-left: 0; + } + + // Lists + ul, + ol, + dl { + margin-bottom: $list-margin-bottom; + list-style-position: $list-style-position; + line-height: $list-lineheight; + } + + // List items + li { + font-size: inherit; + } + + // Unordered lists + ul { + margin-#{$global-left}: $list-side-margin; + list-style-type: $list-style-type; + } + + // Ordered lists + ol { + margin-#{$global-left}: $list-side-margin; + } + + // Nested unordered/ordered lists + ul, ol { + & & { + margin-#{$global-left}: $list-nested-side-margin; + margin-bottom: 0; + } + } + + // Definition lists + dl { + margin-bottom: $defnlist-margin-bottom; + + dt { + margin-bottom: $defnlist-term-margin-bottom; + font-weight: $defnlist-term-weight; + } + } + + // Blockquotes + blockquote { + margin: 0 0 $paragraph-margin-bottom; + padding: $blockquote-padding; + border-#{$global-left}: $blockquote-border; + + &, p { + line-height: $paragraph-lineheight; + color: $blockquote-color; + } + } + + // Inline Citations + @if ($enable-cite-block == true) { + cite { + // Extending a class is not recommended. + // TODO: Break the typography-base/typography-helpers separation + @extend .cite-block; + } + } + + // Abbreviations + abbr, abbr[title] { + border-bottom: $abbr-underline; + cursor: help; + text-decoration: none; + } + + // Figures + figure { + margin: 0; + } + + // Code + @if ($enable-code-inline == true) { + code { + @extend .code-inline; + } + } + + // Keystrokes + kbd { + margin: 0; + padding: $keystroke-padding; + + background-color: $keystroke-background; + + font-family: $keystroke-font; + color: $keystroke-color; + + @if has-value($keystroke-radius) { + border-radius: $keystroke-radius; + } + } +} diff --git a/src/foundation/typography/_helpers.scss b/src/foundation/typography/_helpers.scss new file mode 100644 index 0000000..b2c8dd4 --- /dev/null +++ b/src/foundation/typography/_helpers.scss @@ -0,0 +1,180 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group typography-helpers +//// + +/// Default font size for lead paragraphs. +/// @type Number +$lead-font-size: $global-font-size * 1.25 !default; + +/// Default line height for lead paragraphs. +/// @type String +$lead-lineheight: 1.6 !default; + +/// Default line height for subheaders. +/// @type Number +$subheader-lineheight: 1.4 !default; + +/// Default font color for subheaders. +/// @type Color +$subheader-color: $dark-gray !default; + +/// Default font weight for subheaders. +/// @type String +$subheader-font-weight: $global-weight-normal !default; + +/// Default top margin for subheaders. +/// @type Number +$subheader-margin-top: 0.2rem !default; + +/// Default bottom margin for subheaders. +/// @type Number +$subheader-margin-bottom: 0.5rem !default; + +/// Default font size for statistic numbers. +/// @type Number +$stat-font-size: 2.5rem !default; + +/// Text color for `.cite-block` component. +/// @type Color +$cite-color: $dark-gray !default; + +/// Font size for `.cite-block` component. +/// @type Number +$cite-font-size: rem-calc(13) !default; + +/// Pseudo content for `.cite-block` component. +/// @type String +$cite-pseudo-content: '\2014 \0020' !default; + +/// Text color of `.code-inline` and `.code-block` components. +/// @type Color +$code-color: $black !default; + +/// Font family of `.code-inline` and `.code-block` components. +/// @type String | List +$code-font-family: $font-family-monospace !default; + +/// Font weight of text in `.code-inline` and `.code-block` components. +/// @type String +$code-font-weight: $global-weight-normal !default; + +/// Background color of `.code-inline` and `.code-block` components. +/// @type Color +$code-background: $light-gray !default; + +/// Border around `.code-inline` and `.code-block` components. +/// @type List +$code-border: 1px solid $medium-gray !default; + +/// Padding around text of the `.code-inline` component. +/// @type Number | List +$code-padding: rem-calc(2 5 1) !default; + +/// Padding around text of the `.code-block` component. +/// @type Number | List +$code-block-padding: 1rem !default; + +/// Margin under the `.code-block` component. +/// @type Number +$code-block-margin-bottom: 1.5rem !default; + +@mixin cite-block { + display: block; + color: $cite-color; + font-size: $cite-font-size; + + &:before { + content: $cite-pseudo-content; + } +} + +/// Add basic styles for a code helper. +/// See `code-inline` and `code-block` mixins. +@mixin code-style { + border: $code-border; + background-color: $code-background; + + font-family: $code-font-family; + font-weight: $code-font-weight; + color: $code-color; +} + +/// Make code helper from the `code-style` mixin inline. +/// Used to generate `.code-inline` +@mixin code-inline { + display: inline; + max-width: 100%; + word-wrap: break-word; + + padding: $code-padding; +} + +/// Make code helper from the `code-style` mixin a block. +/// Used to generate `.code-block` +@mixin code-block { + display: block; + overflow: auto; + white-space: pre; + + padding: $code-block-padding; + margin-bottom: $code-block-margin-bottom; +} + +@mixin foundation-typography-helpers { + // Use to create a subheading under a main header + // Make sure you pair the two elements in a <header> element, like this: + // <header> + // <h1>Heading</h1> + // <h2>Subheading</h2> + // </header> + .subheader { + margin-top: $subheader-margin-top; + margin-bottom: $subheader-margin-bottom; + + font-weight: $subheader-font-weight; + line-height: $subheader-lineheight; + color: $subheader-color; + } + + // Use to style an introductory lead, deck, blurb, etc. + .lead { + font-size: $lead-font-size; + line-height: $lead-lineheight; + } + + // Use to style a large number to display a statistic + .stat { + font-size: $stat-font-size; + line-height: 1; + + p + & { + margin-top: -1rem; + } + } + + ul, ol { + // Use to remove numbers from ordered list & bullets from unordered list + &.no-bullet { + margin-#{$global-left}: 0; + list-style: none; + } + } + + .cite-block { + @include cite-block; + } + + .code-inline { + @include code-style; + @include code-inline; + } + + .code-block { + @include code-style; + @include code-block; + } +} diff --git a/src/foundation/typography/_print.scss b/src/foundation/typography/_print.scss new file mode 100644 index 0000000..5088c60 --- /dev/null +++ b/src/foundation/typography/_print.scss @@ -0,0 +1,96 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +/// If `true`, all elements will have transparent backgrounds when printed, to save on ink. +/// @type Boolean +/// @group global +$print-transparent-backgrounds: true !default; + +/// If `true`, displays next to all links their "href" when printed. +/// @type Boolean +/// @group global +$print-hrefs: true !default; + +// sass-lint:disable-all + +@mixin foundation-print-styles { + .show-for-print { display: none !important; } + + @media print { + * { + // Ensure a "black-on-white" print by removing backgrounds, + // using black text everywhere and forcing the browser to economize ink. + @if $print-transparent-backgrounds { + background: transparent !important; + color: black !important; // Black prints faster: h5bp.com/s + color-adjust: economy; + } + // Otherwise, prevent any economy by the browser. + @else { + color-adjust: exact; + } + + box-shadow: none !important; + text-shadow: none !important; + } + + .show-for-print { display: block !important; } + .hide-for-print { display: none !important; } + + table.show-for-print { display: table !important; } + thead.show-for-print { display: table-header-group !important; } + tbody.show-for-print { display: table-row-group !important; } + tr.show-for-print { display: table-row !important; } + td.show-for-print { display: table-cell !important; } + th.show-for-print { display: table-cell !important; } + + // Display the URL of a link after the text + a, + a:visited { text-decoration: underline;} + @if $print-hrefs { + a[href]:after { content: ' (' attr(href) ')'; } + } + + // Don't display the URL for images or JavaScript/internal links + .ir a:after, + a[href^='javascript:']:after, + a[href^='#']:after { content: ''; } + + // Display what an abbreviation stands for after the text + abbr[title]:after { content: ' (' attr(title) ')'; } + + // Prevent page breaks in the middle of a blockquote or preformatted text block + pre, + blockquote { + border: 1px solid $dark-gray; + page-break-inside: avoid; + } + + // h5bp.com/t + thead { display: table-header-group; } + + tr, + img { page-break-inside: avoid; } + + img { max-width: 100% !important; } + + @page { margin: 0.5cm; } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + // Avoid page breaks after a heading + h2, + h3 { page-break-after: avoid; } + + // Helper to re-allow page breaks in the middle of certain elements (e.g. pre, blockquote, tr) + .print-break-inside { + page-break-inside: auto; + } + } +} diff --git a/src/foundation/typography/_typography.scss b/src/foundation/typography/_typography.scss new file mode 100644 index 0000000..c794126 --- /dev/null +++ b/src/foundation/typography/_typography.scss @@ -0,0 +1,26 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group typography +//// + +// Base typography styles (tags only) +@import 'base'; + +// Typography helper classes (classes only) +@import 'helpers'; + +// Text alignment classes +@import 'alignment'; + +// Print styles +@import 'print'; + +@mixin foundation-typography { + @include foundation-typography-base; + @include foundation-typography-helpers; + @include foundation-text-alignment; + @include foundation-print-styles; +} diff --git a/src/foundation/util/_breakpoint.scss b/src/foundation/util/_breakpoint.scss new file mode 100644 index 0000000..9c2f818 --- /dev/null +++ b/src/foundation/util/_breakpoint.scss @@ -0,0 +1,435 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group breakpoints +//// + +/// A list of named breakpoints. You can use these with the `breakpoint()` mixin to quickly create media queries. +/// @type Map +$breakpoints: ( + small: 0, + medium: 640px, + large: 1024px, + xlarge: 1200px, + xxlarge: 1440px, +) !default; + +/// A list of named HiDPI breakpoints. You can use these with the `breakpoint()` mixin to quickly create media queries for resolutions. +/// Values must represent the device pixels / web pixels ration and be unitless or in DPPX. +/// @type Map +$breakpoints-hidpi: ( + hidpi-1: 1, + hidpi-1-5: 1.5, + hidpi-2: 2, + retina: 2, + hidpi-3: 3 +) !default; + +/// The largest named breakpoint in which to include print as a media type +/// @type Keyword +$print-breakpoint: large !default; + +$-zf-zero-breakpoint: small !default; + +$-zf-breakpoints-keys: map-to-list($breakpoints, 'keys'); + +@if nth(map-values($breakpoints), 1) != 0 { + @error 'The first key in the $breakpoints map must have a value of "0".'; +} +@else { + $-zf-zero-breakpoint: nth(map-keys($breakpoints), 1); +} + +/// All of the names in this list will be output as classes in your CSS, like `.small-12`, `.medium-6`, and so on. Each value in this list must also be in the `$breakpoints` map. +/// @type List +$breakpoint-classes: (small medium large) !default; + +/// Generates a media query string matching the input value. Refer to the documentation for the `breakpoint()` mixin to see what the possible inputs are. +/// +/// @param {Keyword|Number} $val [small] - Breakpoint name, or px, rem, or em value to process. +@function breakpoint($val: $-zf-zero-breakpoint) { + // Web standard Pixels per inch. (1ddpx / $std-web-dpi) = 1dpi + // See https://www.w3.org/TR/css-values-3/#absolute-lengths + $std-web-dpi: 96; + + // Size or keyword + $bp: nth($val, 1); + // Value of the following breakpoint + $bp-next: null; + // Value for max-width media queries + $bp-min: null; + // Value for min-width media queries + $bp-max: null; + // Direction of media query (up, down, or only) + $dir: if(length($val) > 1, nth($val, 2), up); + // If named, name of the breakpoint + $name: null; + // If the breakpoint is a HiDPI breakpoint + $hidpi: false; + + // Orientation media queries have a unique syntax + @if $bp == 'landscape' or $bp == 'portrait' { + @return '(orientation: #{$bp})'; + } + + // If a breakpoint name is given, get its value from the $breakpoints/$breakpoints-hidpi map. + @if type-of($bp) == 'string' { + @if map-has-key($breakpoints, $bp) { + $name: $bp; + $bp: map-get($breakpoints, $name); + $bp-next: -zf-map-next($breakpoints, $name); + } + @else if map-has-key($breakpoints-hidpi, $bp) { + $name: $bp; + $bp: map-get($breakpoints-hidpi, $name); + $bp-next: -zf-map-next-number($breakpoints-hidpi, $bp); + $hidpi: true; + } + @else { + $bp: 0; + @warn 'breakpoint(): "#{$val}" is not defined in your `$breakpoints` or `$breakpoints-hidpi` setting.'; + } + } + + @if not $name and $dir == 'only' { + @warn 'breakpoint(): Only named media queries can have an `only` range.'; + @return null; + } + + // Only 'only' and 'up' have a min limit. + @if $dir == 'only' or $dir == 'up' { + $bp-min: if($hidpi, strip-unit($bp), -zf-bp-to-em($bp)); + } + // Only 'only' and 'down' have a max limit. + @if $dir == 'only' or $dir == 'down' { + // If the breakpoint is a value, use it as max limit. + @if not $name { + $bp-max: if($hidpi, strip-unit($bp), -zf-bp-to-em($bp)); + } + // If the breakpoint is named, the max limit is the following breakpoint - 1px. + @else if $bp-next { + // Max value is 0.2px under the next breakpoint (0.02 / 16 = 0.00125). + // Use a precision under 1px to support browser zoom, but not to low to avoid rounding. + // See https://github.com/zurb/foundation-sites/issues/11313 + $bp-max: if($hidpi, $bp-next - (1/$std-web-dpi), -zf-bp-to-em($bp-next) - 0.00125); + } + } + + // Generate the media query string from min and max limits. + @if $hidpi { + // Generate values in DPI instead of DPPX for an IE9-11/Opera mini compatibility. + // See https://caniuse.com/#feat=css-media-resolution + $bp-min-dpi: if($bp-min, $bp-min * $std-web-dpi * 1dpi, $bp-min); + $bp-max-dpi: if($bp-max, $bp-max * $std-web-dpi * 1dpi, $bp-max); + @return zf-str-join( + -zf-bp-join($bp-min, $bp-max, '-webkit-min-device-pixel-ratio', '-webkit-max-device-pixel-ratio'), + -zf-bp-join($bp-min-dpi, $bp-max-dpi, 'min-resolution', 'max-resolution'), + ', '); + } + @else { + @return -zf-bp-join($bp-min, $bp-max); + } +} + +/// Wraps a media query around the content you put inside the mixin. This mixin accepts a number of values: +/// - If a string is passed, the mixin will look for it in the `$breakpoints` and `$breakpoints-hidpi` maps, and use a media query there. +/// - If a pixel value is passed, it will be converted to an em value using `$global-font-size` as the base. +/// - If a rem value is passed, the unit will be changed to em. +/// - If an em value is passed, the value will be used as-is. +/// +/// If multiple values are passed, the mixin will generate a media query for each of them as described above. +/// Since the content is duplicated for each breakpoint, this mixin should only be used with properties that +/// change across breakpoints. +/// +/// @param {Keyword|Number} $values... - Breakpoint name or px/rem/em value to process. +/// +/// @output If the breakpoint is "0px and larger", outputs the content as-is. Otherwise, outputs the content wrapped in a media query. +@mixin breakpoint($values...) { + @for $i from 1 through length($values) { + $value: nth($values, $i); + $str: breakpoint($value); + $bp: index($-zf-breakpoints-keys, nth($value, 1)); + $pbp: index($-zf-breakpoints-keys, $print-breakpoint); + // Direction of media query (up, down, or only) + $dir: if(length($value) > 1, nth($value, 2), up); + + $old-zf-size: null; + + // Make breakpoint size available as a variable + @if global-variable-exists(-zf-size) { + $old-zf-size: $-zf-size; + } + $-zf-size: nth($value, 1) !global; // get the first value to account for `only` and `down` keywords + + // If $str is still an empty string, no media query is needed + @if $str == '' { + @content; + } + + // Otherwise, wrap the content in a media query + @else { + // For named breakpoints less than or equal to $print-breakpoint, add print to the media types + // generate print if the breakpoint affects the print-breakpoint (or smaller). + // This means the current condition only needs to be extended so 'down' always generates print. + @if $bp != null and ($bp <= $pbp or $dir == down) { + @media print, screen and #{$str} { + @content; + } + } + @else { + @media screen and #{$str} { + @content; + } + } + } + + $-zf-size: $old-zf-size !global; + } +} + +/// Converts the breakpoints map to a URL-encoded string, like this: `key1=value1&key2=value2`. The value is then dropped into the CSS for a special `<meta>` tag, which is read by the Foundation JavaScript. This is how we transfer values from Sass to JavaScript, so they can be defined in one place. +/// @access private +/// +/// @param {Map} $map - Map to convert. +/// +/// @returns {String} A string containing the map's contents. +@function -zf-bp-serialize($map) { + $str: ''; + @each $key, $value in $map { + $str: $str + $key + '=' + -zf-bp-to-em($value) + '&'; + } + $str: str-slice($str, 1, -2); + + @return $str; +} + +/// Find the next key in a map. +/// @access private +/// +/// @param {Map} $map - Map to traverse. +/// @param {Mixed} $key - Key to use as a starting point. +/// +/// @returns {Mixed} The value for the key after `$key`, if `$key` was found. If `$key` was not found, or `$key` was the last value in the map, returns `null`. +@function -zf-map-next($map, $key) { + + // Store the keys of the map as a list + $values: map-keys($map); + + $i: 0; + + // If the Key Exists, Get the index of the key within the map and add 1 to it for the next breakpoint in the map + @if (map-has-key($map, $key)) { + $i: index($values, $key) + 1; + } + + // If the key doesn't exist, or it's the last key in the map, return null + @if ($i > length($map) or $i == 0) { + @return null; + } + // Otherwise, return the value + @else { + @return map-get($map, nth($values, $i)); + } + +} + +/// Find the next number in a map. +/// @access private +/// +/// @param {Map} $map - Map to traverse. +/// @param {Mixed} $number - Number to use as a starting point. +/// +/// @returns {Mixed} The number following `$number`, if `$number` was found. If `$number` was not found, or `$number` was the biggest number in the map, returns `null`. +@function -zf-map-next-number($map, $number) { + + $next_number: null; + + @each $k, $v in $map { + @if type-of($v) == 'number' and $v > $number and ($next_number == null or $v < $next_number) { + $next_number: $v; + } + } + + @return $next_number; +} + +/// Return a list of our named breakpoints less than $key. Useful for dealing with +/// responsive gutters for the grid. +/// @access private +/// +/// @param {String} $key - Key to use as last breakpoint. +/// +/// @returns {Array} The list of breakpoints up to and. If $key is auto, returns breakpoints above the zero +@function -zf-breakpoints-less-than($key) { + $list: (); + $found_key: false; + + @each $name in $-zf-breakpoints-keys { + @if ($name == $key) { + $found_key: true; + } + @if not $found_key { + $list: append($list, $name); + } + } + @return $list; +} + +/// Return a list of our named breakpoints less than $key. Useful for dealing with +/// responsive gutters for the grid. +/// @access private +/// +/// @param {String} $breakpoint - a named or non-named breakpoint. +/// +/// @returns {Array} The list of breakpoints up to and. If $key is auto, returns breakpoints above the zero +@function -zf-closest-named-breakpoint($breakpoint) { + $last: $-zf-zero-breakpoint; + $found: false; + + $value: unitless-calc($breakpoint, 1px); + @each $key, $val in $breakpoints { + @if not $found { + @if unitless-calc($val) > $value { + $found: true; + } @else { + $last: $key; + } + } + } + + @return $last; +} + +/// Get a value for a breakpoint from a responsive config map or single value. +/// - If the config is a single value, return it regardless of `$value`. +/// - If the config is a map and has the key `$value`, the exact breakpoint value is returned. +/// - If the config is a map and does *not* have the breakpoint, the value matching the next lowest breakpoint in the config map is returned. +/// @access private +/// +/// @param {Number|Map} $map - Responsive config map or single value. +/// @param {Keyword} $value - Breakpoint name to use. +/// +/// @return {Mixed} The corresponding breakpoint value. +@function -zf-get-bp-val($map, $value) { + // If the given map is a single value, return it + @if type-of($map) == 'number' { + @return $map; + } + + + // Check if the breakpoint name exists globally + @if not map-has-key($breakpoints, $value) { + @if type-of($value) == 'number' { + $value: -zf-closest-named-breakpoint($value); + } @else { + @return null; + } + } + // Check if the breakpoint name exists in the local config map + @else if map-has-key($map, $value) { + // If it does, just return the value + @return map-get($map, $value); + } + // Otherwise, find the next lowest breakpoint and return that value + @else { + $anchor: null; + $found: false; + + @each $key, $val in $breakpoints { + @if not $found { + @if map-has-key($map, $key) { + $anchor: $key; + } + @if $key == $value { + $found: true; + } + } + } + + @return map-get($map, $anchor); + } +} + +/// Return the best breakpoint to use according to the calling context. It returns in order: +/// 1. the given `$value` argument if it is not null. +/// 2. the global breakpoint context `$-zf-size` if it is not null (like if called inside then `breakpoint()` mixin) +/// 3. the given `$default` argument. +/// @access private +/// +/// @param {Keyword} $value [null] - Breakpoint to use in priority if non-null. +/// @param {Keyword} $default [null] - Breakpoint to use by default if no other value can be used. +/// +/// @return {Keyword} The resolved breakpoint. +@function -zf-current-breakpoint($value: null, $default: null) { + @if ($value != null) { + @return $value; + } + @else if (variable-exists(-zf-size) and type-of($-zf-size) != 'number') and $-zf-size != null { + @return $-zf-size; + } + @else { + @return $default; + } +} + +/// Return media query string from the given min and/or max limits. +/// If a limit is equal to `null` or `0`, it is ignored. +/// @access private +/// +/// @param {Number} $min [0] - Min media query limit. +/// @param {Number} $max [0] - Max media query limit. +/// @param {String} $min-name ['min-width'] - Name of the min media query limit. +/// @param {String} $delimiter ['max-width'] - Name of the max media query limit. +/// +/// @returns {String} Media Query string. +@function -zf-bp-join( + $min: 0, + $max: 0, + $min-name: 'min-width', + $max-name: 'max-width' +) { + @return zf-str-join( + if($min and $min > 0, '(#{$min-name}: #{$min})', null), + if($max and $max > 0, '(#{$max-name}: #{$max})', null), + ' and '); +} + +$small-up: ''; +$small-only: ''; + +@if map-has-key($breakpoints, small) { + $small-up: screen; + $small-only: unquote('screen and #{breakpoint(small only)}'); +} + +$medium-up: ''; +$medium-only: ''; + +@if map-has-key($breakpoints, medium) { + $medium-up: unquote('screen and #{breakpoint(medium)}'); + $medium-only: unquote('screen and #{breakpoint(medium only)}'); +} + +$large-up: ''; +$large-only: ''; + +@if map-has-key($breakpoints, large) { + $large-up: unquote('screen and #{breakpoint(large)}'); + $large-only: unquote('screen and #{breakpoint(large only)}'); +} + +$xlarge-up: ''; +$xlarge-only: ''; + +@if map-has-key($breakpoints, xlarge) { + $xlarge-up: unquote('screen and #{breakpoint(xlarge)}'); + $xlarge-only: unquote('screen and #{breakpoint(xlarge only)}'); +} + +$xxlarge-up: ''; + +@if map-has-key($breakpoints, xxlarge) { + $xxlarge-up: unquote('screen and #{breakpoint(xxlarge)}'); +} diff --git a/src/foundation/util/_color.scss b/src/foundation/util/_color.scss new file mode 100644 index 0000000..f88588e --- /dev/null +++ b/src/foundation/util/_color.scss @@ -0,0 +1,139 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +@import 'math'; + +$contrast-warnings: true !default; + +//// +/// @group functions +//// + +/// Checks the luminance of `$color`. +/// +/// @param {Color} $color - Color to check the luminance of. +/// +/// @returns {Number} The luminance of `$color`. +@function color-luminance($color) { + // Adapted from: https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/color.js + // Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + $rgba: red($color), green($color), blue($color); + $rgba2: (); + + @for $i from 1 through 3 { + $rgb: nth($rgba, $i); + $rgb: $rgb / 255; + + $rgb: if($rgb < 0.03928, $rgb / 12.92, pow(($rgb + 0.055) / 1.055, 2.4)); + + $rgba2: append($rgba2, $rgb); + } + + @return 0.2126 * nth($rgba2, 1) + 0.7152 * nth($rgba2, 2) + 0.0722 * nth($rgba2, 3); +} + +/// Checks the contrast ratio of two colors. +/// +/// @param {Color} $color1 - First color to compare. +/// @param {Color} $color2 - Second color to compare. +/// +/// @returns {Number} The contrast ratio of the compared colors. +@function color-contrast($color1, $color2) { + // Adapted from: https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/color.js + // Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef + $luminance1: color-luminance($color1) + 0.05; + $luminance2: color-luminance($color2) + 0.05; + $ratio: $luminance1 / $luminance2; + + @if $luminance2 > $luminance1 { + $ratio: 1 / $ratio; + } + + $ratio: round($ratio * 10) / 10; + + @return $ratio; +} + +/// Checks the luminance of `$base`, and returns the color from `$colors` (list of colors) that has the most contrast. +/// +/// @param {Color} $base - Color to check luminance. +/// @param {List} $colors [($white, $black)] - Colors to compare. +/// @param {Number} $tolerance [$global-color-pick-contrast-tolerance] - Contrast tolerance. +/// +/// @returns {Color} the color from `$colors` (list of colors) that has the most contrast. +@function color-pick-contrast($base, $colors: ($white, $black), $tolerance: $global-color-pick-contrast-tolerance) { + $contrast: color-contrast($base, nth($colors, 1)); + $best: nth($colors, 1); + + @for $i from 2 through length($colors) { + $current-contrast: color-contrast($base, nth($colors, $i)); + @if ($current-contrast - $contrast > $tolerance) { + $contrast: color-contrast($base, nth($colors, $i)); + $best: nth($colors, $i); + } + } + + @if ($contrast-warnings and $contrast < 3) { + @warn "Contrast ratio of #{$best} on #{$base} is pretty bad, just #{$contrast}"; + } + + @return $best; +} + +/// Scales a color to be darker if it's light, or lighter if it's dark. Use this function to tint a color appropriate to its lightness. +/// +/// @param {Color} $color - Color to scale. +/// @param {Percentage} $scale [5%] - Amount to scale up or down. +/// @param {Percentage} $threshold [40%] - Threshold of lightness to check against. +/// +/// @returns {Color} A scaled color. +@function smart-scale($color, $scale: 5%, $threshold: 40%) { + @if lightness($color) > $threshold { + $scale: -$scale; + } + @return scale-color($color, $lightness: $scale); +} + +/// Get color from foundation-palette +/// +/// @param {key} color key from foundation-palette +/// +/// @returns {Color} color from foundation-palette +@function get-color($key) { + @if map-has-key($foundation-palette, $key) { + @return map-get($foundation-palette, $key); + } + @else { + @error 'given $key is not available in $foundation-palette'; + } +} + +/// Transfers the colors in the `$foundation-palette` map into variables, such as `$primary-color` and `$secondary-color`. Call this mixin below the Global section of your settings file to properly migrate your codebase. +@mixin add-foundation-colors() { + @if map-has-key($foundation-palette, primary) { + $primary-color: map-get($foundation-palette, primary) !global; + } @else { + $primary-color: #1779ba !global; + } + @if map-has-key($foundation-palette, secondary) { + $secondary-color: map-get($foundation-palette, secondary) !global; + } @else { + $secondary-color: #767676 !global; + } + @if map-has-key($foundation-palette, success) { + $success-color: map-get($foundation-palette, success) !global; + } @else { + $success-color: #3adb76 !global; + } + @if map-has-key($foundation-palette, warning) { + $warning-color: map-get($foundation-palette, warning) !global; + } @else { + $warning-color: #ffae00 !global; + } + @if map-has-key($foundation-palette, alert) { + $alert-color: map-get($foundation-palette, alert) !global; + } @else { + $alert-color: #cc4b37 !global; + } +} diff --git a/src/foundation/util/_direction.scss b/src/foundation/util/_direction.scss new file mode 100644 index 0000000..7874ec4 --- /dev/null +++ b/src/foundation/util/_direction.scss @@ -0,0 +1,31 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group functions +//// + +/// Returns the opposite direction of $dir +/// +/// @param {Keyword} $dir - Used direction between "top", "right", "bottom" and "left". +/// @return {Keyword} Opposite direction of $dir +@function direction-opposite( + $dir +) { + $dirs: (top, right, bottom, left); + $place: index($dirs, $dir); + + @if $place == null { + @error 'direction-opposite: Invalid $dir parameter, expected a value from "#{$dirs}", found "#{$dir}".'; + @return null; + } + + // Calculate the opposite place in a circle, with a starting index of 1 + $length: length($dirs); + $demi: $length / 2; + $opposite-place: (($place + $demi - 1) % $length) + 1; + + @return nth($dirs, $opposite-place); +} + diff --git a/src/foundation/util/_flex.scss b/src/foundation/util/_flex.scss new file mode 100644 index 0000000..2a48b6d --- /dev/null +++ b/src/foundation/util/_flex.scss @@ -0,0 +1,90 @@ +@function -zf-flex-justify($text-direction){ + $-zf-flex-justify: ( + 'left': if($text-direction == rtl, flex-end, flex-start), + 'right': if($text-direction == rtl, flex-start, flex-end), + 'center': center, + 'justify': space-between, + 'spaced': space-around, + ); + + @return $-zf-flex-justify; +} + + +$-zf-flex-align: ( + 'top': flex-start, + 'bottom': flex-end, + 'middle': center, + 'stretch': stretch, +); + +$-zf-flex-direction: ( + 'row': row, + 'row-reverse': row-reverse, + 'column': column, + 'column-reverse': column-reverse, +); + +/// Enables flexbox by adding `display: flex` to the element. +@mixin flex { + display: flex; +} + +/// Horizontally or vertically aligns the items within a flex container. +/// +/// @param {Keyword} $x [null] - Horizontal alignment to use. Can be `left`, `right`, `center`, `justify`, or `spaced`. Or, set it to `null` (the default) to not set horizontal alignment. +/// @param {Keyword} $y [null] - Vertical alignment to use. Can be `top`, `bottom`, `middle`, or `stretch`. Or, set it to `null` (the default) to not set vertical alignment. +@mixin flex-align($x: null, $y: null) { + @if $x { + @if map-has-key($-zf-flex-justify, $x) { + $x: map-get($-zf-flex-justify, $x); + } + @else { + @warn 'flex-grid-row-align(): #{$x} is not a valid value for horizontal alignment. Use left, right, center, justify, or spaced.'; + } + } + + @if $y { + @if map-has-key($-zf-flex-align, $y) { + $y: map-get($-zf-flex-align, $y); + } + @else { + @warn 'flex-grid-row-align(): #{$y} is not a valid value for vertical alignment. Use top, bottom, middle, or stretch.'; + } + } + + justify-content: $x; + align-items: $y; +} + +/// Vertically align a single column within a flex row. Apply this mixin to a flex column. +/// +/// @param {Keyword} $y [null] - Vertical alignment to use. Can be `top`, `bottom`, `middle`, or `stretch`. Or, set it to `null` (the default) to not set vertical alignment. +@mixin flex-align-self($y: null) { + @if $y { + @if map-has-key($-zf-flex-align, $y) { + $y: map-get($-zf-flex-align, $y); + } + @else { + @warn 'flex-grid-column-align(): #{$y} is not a valid value for alignment. Use top, bottom, middle, or stretch.'; + } + } + + align-self: $y; +} + +/// Changes the source order of a flex child. Children with lower numbers appear first in the layout. +/// @param {Number} $order [0] - Order number to apply. +@mixin flex-order($order: 0) { + order: $order; +} + +/// Change flex-direction +/// @param {Keyword} $direction [row] - Flex direction to use. Can be +/// - row (default): same as text direction +/// - row-reverse: opposite to text direction +/// - column: same as row but top to bottom +/// - column-reverse: same as row-reverse top to bottom +@mixin flex-direction($direction: row) { + flex-direction: $direction; +} diff --git a/src/foundation/util/_math.scss b/src/foundation/util/_math.scss new file mode 100644 index 0000000..947a576 --- /dev/null +++ b/src/foundation/util/_math.scss @@ -0,0 +1,147 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group functions +//// + +/// Finds the greatest common divisor of two integers. +/// +/// @param {Number} $a - First number to compare. +/// @param {Number} $b - Second number to compare. +/// +/// @returns {Number} The greatest common divisor. +@function gcd($a, $b) { + // From: http://rosettacode.org/wiki/Greatest_common_divisor#JavaScript + @if ($b != 0) { + @return gcd($b, $a % $b); + } + @else { + @return abs($a); + } +} + +/// Handles decimal exponents by trying to convert them into a fraction and then use a nth-root-algorithm for parts of the calculation +/// +/// @param {Number} $base - The base number. +/// @param {Number} $exponent - The exponent. +/// +/// @returns {Number} The product of the exponentiation. +@function pow($base, $exponent, $prec: 16) { + @if (floor($exponent) != $exponent) { + $prec2 : pow(10, $prec); + $exponent: round($exponent * $prec2); + $denominator: gcd($exponent, $prec2); + @return nth-root(pow($base, $exponent / $denominator), $prec2 / $denominator, $prec); + } + + $value: $base; + @if $exponent > 1 { + @for $i from 2 through $exponent { + $value: $value * $base; + } + } + @else if $exponent < 1 { + @for $i from 0 through -$exponent { + $value: $value / $base; + } + } + + @return $value; +} + +@function nth-root($num, $n: 2, $prec: 12) { + // From: http://rosettacode.org/wiki/Nth_root#JavaScript + $x: 1; + + @for $i from 0 through $prec { + $x: 1 / $n * (($n - 1) * $x + ($num / pow($x, $n - 1))); + } + + @return $x; +} + +/// Calculates the height as a percentage of the width for a given ratio. +/// @param {List} $ratio - Ratio to use to calculate the height, formatted as `x by y`. +/// @return {Number} A percentage value for the height relative to the width of a responsive container. +@function ratio-to-percentage($ratio) { + $w: nth($ratio, 1); + $h: nth($ratio, 3); + @return $h / $w * 100%; +} + +/// Parse the given `$fraction` to numerators and denumerators. +/// +/// @param {*} $fraction - Value representing a fraction to parse. It can be formatted as `50%`, `1 of 2`, `1/2` or `50` (no denominator would be returned). +/// +/// @return {List} List of parsed values with numerator at first position and denumerator as second. These values may be null. +@function zf-parse-fraction($fraction) { + + @if type-of($fraction) == 'number' { + // "50%" + @if unit($fraction) == '%' { + @return (strip-unit($fraction), 100); + } + @else if (unit($fraction) == '') { + // "0.5" + @if $fraction < 1 { + @return ($fraction * 100, 100); + } + // "50" + @else { + @return ($fraction, null); + } + } + } + + @else if type-of($fraction) == 'list' { + // "50 of 100", "50/100"... + @if length($fraction) == 3 + and type-of(nth($fraction, 1) == 'number') + and type-of(nth($fraction, 3) == 'number') { + @return (nth($fraction, 1), nth($fraction, 3)); + } + } + + @return (null, null); +} + +/// Returns whether the given `$value` represents a fraction. Supports formats like `50%`, `1 of 2`, `1 per 2` or `1/2`. +/// +/// @param {*} $value - Value to test. +/// @param {Boolean} $allow-no-denominator [false] - If `true`, simple numbers without denominators like `50` are supported. +/// +/// @return {Boolean} `true` if `$value` represents a fraction, `false` otherwise. +@function zf-is-fraction($value, $allow-no-denominator: false) { + $parsed: zf-parse-fraction($value); + @return not(nth($parsed, 1) == null + or (nth($parsed, 2) == null and $allow-no-denominator == false)); +} + +/// Calculate a percentage from a given fraction. +/// +/// @param {Number|List} $fraction - Value representing a fraction to use to calculate the percentage, formatted as `50` (relative to `$denominator`), `50%`, `1 of 2` or `1/2`. +/// @param {Number|List} $denominator - Default value to use as denominator when `$fraction` represents an absolute value. +@function fraction-to-percentage( + $fraction, + $denominator: null +) { + $parsed: zf-parse-fraction($fraction); + $parsed-nominator: nth($parsed, 1); + $parsed-denominator: nth($parsed, 2); + + @if $parsed-nominator == null { + @error 'Wrong syntax for "fraction-to-percentage()". Use a number, decimal, percentage, or "n of n" / "n/n".'; + } + @if $parsed-denominator == null { + @if type-of($denominator) == 'number' { + $parsed-denominator: $denominator; + } + @else { + @error 'Error with "fraction-to-percentage()". A default "$denominator" is required to support absolute values'; + } + } + + @return percentage($parsed-nominator / $parsed-denominator); +} diff --git a/src/foundation/util/_mixins.scss b/src/foundation/util/_mixins.scss new file mode 100644 index 0000000..c256dc6 --- /dev/null +++ b/src/foundation/util/_mixins.scss @@ -0,0 +1,373 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group functions +//// + +/// Creates an inner box-shadow for only one side +/// +/// @param {Keyword} $side - Side the shadow is supposed to appear. Can be `top`, `left`, `right` or `bottom`. +/// @param {Number} $size - Width for the target side. +/// @param {Color} $color - Color of the shadow. +@mixin inner-side-shadow( + $side: bottom, + $size: 20px, + $color: rgba($black, 0.25) +) { + + $helper: round($size * 0.65); + + @if ($side == top) { + box-shadow: inset 0 $helper $size (-1)*$helper $color; + } @else if ($side == left) { + box-shadow: inset $helper 0 $size (-1)*$helper $color; + } @else if ($side == right) { + box-shadow: inset (-1)*$helper 0 $size (-1)*$helper $color; + } @else if ($side == bottom) { + box-shadow: inset 0 (-1)*$helper $size (-1)*$helper $color; + } +} + +/// Creates a CSS triangle, which can be used for dropdown arrows, dropdown pips, and more. Use this mixin inside a `&::before` or `&::after` selector, to attach the triangle to an existing element. +/// +/// @param {Number} $triangle-size - Width of the triangle. +/// @param {Color} $triangle-color - Color of the triangle. +/// @param {Keyword} $triangle-direction - Direction the triangle points. Can be `up`, `right`, `down`, or `left`. +@mixin css-triangle( + $triangle-size, + $triangle-color, + $triangle-direction +) { + display: block; + width: 0; + height: 0; + + border: inset $triangle-size; + + content: ''; + + @if ($triangle-direction == down) { + border-bottom-width: 0; + border-top-style: solid; + border-color: $triangle-color transparent transparent; + } + @if ($triangle-direction == up) { + border-top-width: 0; + border-bottom-style: solid; + border-color: transparent transparent $triangle-color; + } + @if ($triangle-direction == right) { + border-right-width: 0; + border-left-style: solid; + border-color: transparent transparent transparent $triangle-color; + } + @if ($triangle-direction == left) { + border-left-width: 0; + border-right-style: solid; + border-color: transparent $triangle-color transparent transparent; + } +} + +/// Creates a menu icon with a set width, height, number of bars, and colors. The mixin uses the height of the icon and the weight of the bars to determine spacing. <div class="docs-example-burger"></div> +/// +/// @param {Color} $color [$black] - Color to use for the icon. +/// @param {Color} $color-hover [$dark-gray] - Color to use when the icon is hovered over. +/// @param {Number} $width [20px] - Width of the icon. +/// @param {Number} $height [16px] - Height of the icon. +/// @param {Number} $weight [2px] - Height of individual bars in the icon. +/// @param {Number} $bars [3] - Number of bars in the icon. +@mixin hamburger( + $color: $black, + $color-hover: $dark-gray, + $width: 20px, + $height: 16px, + $weight: 2px, + $bars: 3 +) { + // box-shadow CSS output + $shadow: (); + $hover-shadow: (); + + // Spacing between bars is calculated based on the total height of the icon and the weight of each bar + $spacing: ($height - ($weight * $bars)) / ($bars - 1); + + @if unit($spacing) == 'px' { + $spacing: floor($spacing); + } + + @for $i from 2 through $bars { + $offset: ($weight + $spacing) * ($i - 1); + $shadow: append($shadow, 0 $offset 0 $color, comma); + } + + // Icon container + position: relative; + display: inline-block; + vertical-align: middle; + width: $width; + height: $height; + cursor: pointer; + + // Icon bars + &::after { + position: absolute; + top: 0; + left: 0; + + display: block; + width: 100%; + height: $weight; + + background: $color; + box-shadow: $shadow; + + content: ''; + } + + // Hover state + @if $color-hover { + // Generate CSS + @for $i from 2 through $bars { + $offset: ($weight + $spacing) * ($i - 1); + $hover-shadow: append($hover-shadow, 0 $offset 0 $color-hover, comma); + } + + &:hover::after { + background: $color-hover; + box-shadow: $hover-shadow; + } + } +} + +/// Adds a downward-facing triangle as a background image to an element. The image is formatted as an SVG, making it easy to change the color. Because Internet Explorer doesn't support encoded SVGs as background images, a PNG fallback is also included. +/// There are two PNG fallbacks: a black triangle and a white triangle. The one used depends on the lightness of the input color. +/// +/// @param {Color} $color [$black] - Color to use for the triangle. +@mixin background-triangle($color: $black) { + $rgb: 'rgb%28#{round(red($color))}, #{round(green($color))}, #{round(blue($color))}%29'; + + background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: #{$rgb}'></polygon></svg>"); + + @media screen and (min-width:0\0) { + @if lightness($color) < 60% { + // White triangle + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIpJREFUeNrEkckNgDAMBBfRkEt0ObRBBdsGXUDgmQfK4XhH2m8czQAAy27R3tsw4Qfe2x8uOO6oYLb6GlOor3GF+swURAOmUJ+RwtEJs9WvTGEYxBXqI1MQAZhCfUQKRzDMVj+TwrAIV6jvSUEkYAr1LSkcyTBb/V+KYfX7xAeusq3sLDtGH3kEGACPWIflNZfhRQAAAABJRU5ErkJggg=='); + } + @else { + // Black triangle + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAMBJREFUeNrEllsOhCAMRVszC9IlzU7KCmVHTJsoMWYMUtpyv9BgbuXQB5ZSdgBYYY4ycgBivk8KYFsQMfMiTTBP4o3nUzCKzOabLJbLy2/g31evGkAginR4/ZegKH5qX3bJCscA3t0x3kgO5tQFyhhFf50xRqFLbyMUNJQzgyjGS/wgCpvKqkRBpuWrE4V9d+1E4dPUXqIg107SQOE/2DRQxMwTDygIInVDET9T3lCoj/6j/VCmGjZOl2lKpZ8AAwDQP7zIimDGFQAAAABJRU5ErkJggg=='); + } + } +} + +/// Applies the micro clearfix hack popularized by Nicolas Gallagher. Include this mixin on a container if its children are all floated, to give the container a proper height. +/// The clearfix is augmented with specific styles to prevent borders in flexbox environments +/// @link http://nicolasgallagher.com/micro-clearfix-hack/ Micro Clearfix Hack +/// @link http://danisadesigner.com/blog/flexbox-clear-fix-pseudo-elements/ Flexbox fix +@mixin clearfix { + &::before, + &::after { + display: table; + content: ' '; + + @if $global-flexbox { + flex-basis: 0; + order: 1; + } + } + + &::after { + clear: both; + } +} + +/// Adds CSS for a "quantity query" selector that automatically sizes elements based on how many there are inside a container. +/// @link http://alistapart.com/article/quantity-queries-for-css Quantity Queries for CSS +/// +/// @param {Number} $max - Maximum number of items to detect. The higher this number is, the more CSS that's required to cover each number of items. +/// @param {Keyword} $elem [li] - Tag to use for sibling selectors. +@mixin auto-width($max, $elem: li) { + @for $i from 2 through $max { + &:nth-last-child(#{$i}):first-child, + &:nth-last-child(#{$i}):first-child ~ #{$elem} { + width: percentage(1 / $i); + } + } +} + +/// Removes the focus ring around an element when a mouse input is detected. +@mixin disable-mouse-outline { + [data-whatinput='mouse'] & { + outline: 0; + } +} + +/// Makes an element visually hidden, but still accessible to keyboards and assistive devices. +/// @link http://snook.ca/archives/html_and_css/hiding-content-for-accessibility Hiding Content for Accessibility +/// @link http://hugogiraudel.com/2016/10/13/css-hide-and-seek/ +/// +/// @param {Boolean} $enforce - If `true`, use `!important` on applied properties +@mixin element-invisible( + $enforce: true +) { + $important: if($enforce, '!important', null); + + position: absolute #{$important}; + width: 1px #{$important}; + height: 1px #{$important}; + padding: 0 #{$important}; + overflow: hidden #{$important}; + clip: rect(0,0,0,0) #{$important}; + white-space: nowrap #{$important}; + border: 0 #{$important}; +} + +/// Reverses the CSS output created by the `element-invisible()` mixin. +/// @param {Boolean} $enforce - If `true`, use `!important` on applied properties +@mixin element-invisible-off( + $enforce: true +) { + $important: if($enforce, '!important', null); + + position: static #{$important}; + width: auto #{$important}; + height: auto #{$important}; + overflow: visible #{$important}; + clip: auto #{$important}; + white-space: normal #{$important}; +} + +/// Vertically centers the element inside of its first non-static parent, +/// @link http://www.sitepoint.com/centering-with-sass/ Centering With Sass +@mixin vertical-center { + position: absolute; + top: 50%; + transform: translateY(-50%); +} + +/// Horizontally centers the element inside of its first non-static parent, +/// @link http://www.sitepoint.com/centering-with-sass/ Centering With Sass +@mixin horizontal-center { + position: absolute; + left: 50%; + transform: translateX(-50%); +} + +/// Absolutely centers the element inside of its first non-static parent, +/// @link http://www.sitepoint.com/centering-with-sass/ Centering With Sass +@mixin absolute-center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +/// Iterates through breakpoints defined in `$breakpoint-classes` and prints the CSS inside the mixin at each breakpoint's media query. Use this with the grid, or any other component that has responsive classes. +/// +/// @param {Boolean} $small [true] - If `false`, the mixin will skip the `small` breakpoint. Use this with components that don't prefix classes with `small-`, only `medium-` and up. +/// @param {Boolean} $auto-insert-breakpoints [true] - If `false`, the mixin will iterate over breakpoints without doing the media query itself. Useful for more complex media query generation as in the margin grid. +@mixin -zf-each-breakpoint( + $small: true, + $auto-insert-breakpoints: true +) { + @include -zf-each-breakpoint-in(auto, -zf-bool($small), -zf-bool($auto-insert-breakpoints)) { + @content + }; +} + +/// Iterates with `@content` through the given list of breakpoints `$breakpoints`. +/// +/// @access private +/// +/// @param {Keyword|List} $breakpoints [auto] - Breakpoints to iterates on. It can be a breakpoint name, list of breakpoints or `auto` for all breakpoints. +/// @param {Boolean|Null} $zero-breakpoint [null] - Whether the zero-breakpoint (often `small`) must be included. If `true`, it will always be added to the list if not already there. If `false`, it will always be removed. Does nothing by default. +/// @param {Boolean|Keyword} $media-queries [true] - Whether media-queries must be generated. If `for-lists`, only generate media-queries when `$breakpoints` is a list. +@mixin -zf-each-breakpoint-in( + $breakpoints: auto, + $zero-breakpoint: null, + $media-queries: true +) { + $-list: (); + $-breakpoints-is-a-list: true; + + // Retrieve the list of breakpoint(s) to iterate on. + @if $breakpoints == auto { + $-list: $breakpoint-classes; + } + @else if type-of($breakpoints) == 'list' { + $-list: $breakpoints; + } + @else if type-of($breakpoints) == 'string' { + $-list: ($breakpoints); + $-breakpoints-is-a-list: false; + } + @else { + @error 'Wrong syntax for "$breakpoints" in "-zf-each-breakpoint-in()". Got "#{$breakpoints}" (#{type-of($breakpoints)}). Expected a breakpoint name, a list of breakpoints or "auto"'; + } + + // Add or remove the zero breakpoint according to `$zero-breakpoint` + @if $zero-breakpoint == true { + $-list: join(($-zf-zero-breakpoint), sl-remove($-list, $-zf-zero-breakpoint)); + } + @else if $zero-breakpoint == false { + $-list: sl-remove($-list, $-zf-zero-breakpoint); + } + + // Iterate on breakpoint(s) + @each $bp in $-list { + $old-zf-size: null; + @if global-variable-exists(-zf-size) { + $old-zf-size: $-zf-size; + } + $-zf-size: $bp !global; + + @if ($media-queries == true + or ($media-queries == 'for-lists' and $-breakpoints-is-a-list)) { + @include breakpoint($bp) { + @content; + } + } + @else { + @content; + } + + $-zf-size: $old-zf-size !global; + } +} + +/// Generate the `@content` passed to the mixin with a value `$-zf-bp-value` related to a breakpoint, depending on the `$name` parameter: +/// - For a single value, `$-zf-bp-value` is this value. +/// - For a breakpoint name, `$-zf-bp-value` is the corresponding breakpoint value in `$map`. +/// - For "auto", `$-zf-bp-value` is the corresponding breakpoint value in `$map` and is passed to `@content`, which is made responsive for each breakpoint of `$map`. +/// @param {Number|Array|Keyword} $name [auto] - Single value, breakpoint name, or list of breakpoint names to use. "auto" by default. +/// @param {Number|Map} $map - Map of breakpoints and values or single value to use. +@mixin -zf-breakpoint-value( + $name: auto, + $map: null +) { + @if $name == auto and type-of($map) == 'map' { + // "auto" + @each $k, $v in $map { + @include breakpoint($k) { + @include -zf-breakpoint-value($v, $map) { + @content; + } + } + } + } + @else { + // breakpoint name + @if type-of($name) == 'string' { + $bp-value: -zf-get-bp-val($map, $name); + @if $bp-value != null { + $name: $bp-value; + } + } + + // breakpoint value + $-zf-bp-value: $name !global; + @content; + } +} diff --git a/src/foundation/util/_selector.scss b/src/foundation/util/_selector.scss new file mode 100644 index 0000000..2c79c04 --- /dev/null +++ b/src/foundation/util/_selector.scss @@ -0,0 +1,41 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group functions +//// + +/// Generates a selector with every text input type. You can also filter the list to only output a subset of those selectors. +/// +/// @param {List|Keyword} $types [()] - A list of text input types to use. Leave blank to use all of them. +/// @param {Keyword} $modifier [''] - A modifier to be applied to each text input type (e.g. a class or a pseudo-class). Leave blank to ignore. +@function text-inputs($types: (), $modifier: '') { + $return: (); + + $all-types: + text + password + date + datetime + datetime-local + month + week + email + number + search + tel + time + url + color; + + @if not has-value($types) { + $types: $all-types; + } + + @each $type in $types { + $return: append($return, unquote('[type=\'#{$type}\']#{$modifier}'), comma); + } + + @return $return; +} diff --git a/src/foundation/util/_typography.scss b/src/foundation/util/_typography.scss new file mode 100644 index 0000000..adff086 --- /dev/null +++ b/src/foundation/util/_typography.scss @@ -0,0 +1,26 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group functions +//// + +$-zf-font-stack: ( + 'georgia': (Georgia, "URW Bookman L", serif), + 'helvetica': (Helvetica, Arial, "Nimbus Sans L", sans-serif), + 'lucida-grande': ("Lucida Grande", "Lucida Sans Unicode", "Bitstream Vera Sans", sans-serif), + 'monospace': ("Courier New", Courier, "Nimbus Sans L", monospace), + 'system': (-apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Helvetica Neue", Helvetica, Arial, sans-serif), + 'verdana': (Verdana, Geneva, "DejaVu Sans", sans-serif), +); + +/// Return a font stack list from a map. Equivalent to `map-safe-get($name, $-zf-font-stack)`. +/// +/// @param {String} $stack - Name of the font stack. +/// @param {Map} $map [$-zf-font-stack] - Map of font stacks to retrieve a list from. +/// +/// @returns {List} Found font stack. +@function font-stack($stack, $map: $-zf-font-stack) { + @return map-safe-get($map, $stack); +} diff --git a/src/foundation/util/_unit.scss b/src/foundation/util/_unit.scss new file mode 100644 index 0000000..9496226 --- /dev/null +++ b/src/foundation/util/_unit.scss @@ -0,0 +1,152 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group functions +//// + +$global-font-size: 100% !default; + +/// Removes the unit (e.g. px, em, rem) from a value, returning the number only. +/// +/// @param {Number} $num - Number to strip unit from. +/// +/// @returns {Number} The same number, sans unit. +@function strip-unit($num) { + @return $num / ($num * 0 + 1); +} + +/// Converts one or more pixel values into matching rem values. +/// +/// @param {Number|List} $values - One or more values to convert. Be sure to separate them with spaces and not commas. If you need to convert a comma-separated list, wrap the list in parentheses. +/// @param {Number} $base [null] - The base value to use when calculating the `rem`. If you're using Foundation out of the box, this is 16px. If this parameter is `null`, the function will reference the `$global-font-size` variable as the base. +/// +/// @returns {List} A list of converted values. +@function rem-calc($values, $base: null) { + $rem-values: (); + $count: length($values); + + // If no base is defined, defer to the global font size + @if $base == null { + $base: $global-font-size; + } + + // If the base font size is a %, then multiply it by 16px + // This is because 100% font size = 16px in most all browsers + @if unit($base) == '%' { + $base: ($base / 100%) * 16px; + } + + // Using rem as base allows correct scaling + @if unit($base) == 'rem' { + $base: strip-unit($base) * 16px; + } + + @if $count == 1 { + @return -zf-to-rem($values, $base); + } + + @for $i from 1 through $count { + $rem-values: append($rem-values, -zf-to-rem(nth($values, $i), $base)); + } + + @return $rem-values; +} + +// Converts a unitless, pixel, or rem value to em, for use in breakpoints. +@function -zf-bp-to-em($value) { + // Pixel and unitless values are converted to rems + @if unit($value) == 'px' or unitless($value) { + $value: rem-calc($value, $base: 16px); + } + + // Then the value is converted to ems + @return strip-unit($value) * 1em; +} + +/// Converts a pixel value to matching rem value. *Any* value passed, regardless of unit, is assumed to be a pixel value. By default, the base pixel value used to calculate the rem value is taken from the `$global-font-size` variable. +/// @access private +/// +/// @param {Number} $value - Pixel value to convert. +/// @param {Number} $base [null] - Base for pixel conversion. +/// +/// @returns {Number} A number in rems, calculated based on the given value and the base pixel value. rem values are passed through as is. +@function -zf-to-rem($value, $base: null) { + // Check if the value is a number + @if type-of($value) != 'number' { + @warn inspect($value) + ' was passed to rem-calc(), which is not a number.'; + @return $value; + } + + // Transform em into rem if someone hands over 'em's + @if unit($value) == 'em' { + $value: strip-unit($value) * 1rem; + } + + // Calculate rem if units for $value is not rem or em + @if unit($value) != 'rem' { + $value: strip-unit($value) / strip-unit($base) * 1rem; + } + + // Turn 0rem into 0 + @if $value == 0rem { + $value: 0; + } + + @return $value; +} + +/// Converts a pixel, percentage, rem or em value to a unitless value based on a given font size. Ideal for working out unitless line heights. +/// +/// @param {Number} $value - Value to convert to a unitless line height +/// @param {Number} $base - The font size to use to work out the line height - defaults to $global-font-size +/// +/// @return {Number} - Unitless number +@function unitless-calc($value, $base: null) { + + // If no base is defined, defer to the global font size + @if $base == null { + $base: $global-font-size; + } + + // First, lets convert our $base to pixels + + // If the base font size is a %, then multiply it by 16px + @if unit($base) == '%' { + $base: ($base / 100%) * 16px; + } + + @if unit($base) == 'rem' { + $base: strip-unit($base) * 16px; + } + + @if unit($base) == 'em' { + $base: strip-unit($base) * 16px; + } + + // Now let's convert our value to pixels too + @if unit($value) == '%' { + $value: ($value / 100%) * $base; + } + + @if unit($value) == 'rem' { + $value: strip-unit($value) * $base; + } + + @if unit($value) == 'em' { + $value: strip-unit($value) * $base; + } + + // 'px' + @if unit($value) == 'px' { + @return strip-unit($value) / strip-unit($base); + } + + // assume that line-heights greater than 10 are meant to be absolute in 'px' + @if unitless($value) and ($value > 10) { + @return $value / strip-unit($base); + } + + @return $value; +} diff --git a/src/foundation/util/_util.scss b/src/foundation/util/_util.scss new file mode 100644 index 0000000..ddcb59e --- /dev/null +++ b/src/foundation/util/_util.scss @@ -0,0 +1,14 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +@import 'math'; +@import 'unit'; +@import 'value'; +@import 'direction'; +@import 'color'; +@import 'selector'; +@import 'flex'; +@import 'breakpoint'; +@import 'mixins'; +@import 'typography'; diff --git a/src/foundation/util/_value.scss b/src/foundation/util/_value.scss new file mode 100644 index 0000000..03053fd --- /dev/null +++ b/src/foundation/util/_value.scss @@ -0,0 +1,200 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group functions +//// + +/// Determine if a value is not falsey, in CSS terms. Falsey values are `null`, `none`, `0` with any unit, or an empty list. +/// +/// @param {Mixed} $val - Value to check. +/// +/// @returns {Boolean} `true` if `$val` is not falsey. +@function has-value($val) { + @if $val == null or $val == none { + @return false; + } + @if type-of($val) == 'number' and strip-unit($val) == 0 { + @return false; + } + @if type-of($val) == 'list' and length($val) == 0 { + @return false; + } + @return true; +} + +/// Determine a top/right/bottom/right value on a padding, margin, etc. property, no matter how many values were passed in. Use this function if you need to know the specific side of a value, but don't know if the value is using a shorthand format. +/// +/// @param {List|Number} $val - Value to analyze. Should be a shorthand sizing property, e.g. "1em 2em 1em" +/// @param {Keyword} $side - Side to return. Should be `top`, `right`, `bottom`, or `left`. +/// +/// @returns {Number} A single value based on `$val` and `$side`. +@function get-side($val, $side) { + $length: length($val); + + @if $length == 1 { + @return $val; + } + @if $length == 2 { + @return map-get(( + top: nth($val, 1), + bottom: nth($val, 1), + left: nth($val, 2), + right: nth($val, 2), + ), $side); + } + @if $length == 3 { + @return map-get(( + top: nth($val, 1), + left: nth($val, 2), + right: nth($val, 2), + bottom: nth($val, 3), + ), $side); + } + @if $length == 4 { + @return map-get(( + top: nth($val, 1), + right: nth($val, 2), + bottom: nth($val, 3), + left: nth($val, 4), + ), $side); + } +} + +/// Given border $val, find a specific element of the border, which is $elem. The possible values for $elem are width, style, and color. +/// +/// @param {List} $val - Border value to find a value in. +/// @param {Keyword} $elem - Border component to extract. +/// +/// @returns {Mixed} If the value exists, returns the value. If the value is not in the border definition, the function will return a 0px width, solid style, or black border. +@function get-border-value($val, $elem) { + // Find the width, style, or color and return it + @each $v in $val { + $type: type-of($v); + @if $elem == width and $type == 'number' { + @return $v; + } + @if $elem == style and $type == 'string' { + @return $v; + } + @if $elem == color and $type == 'color' { + @return $v; + } + } + + // Defaults + $defaults: ( + width: 0, + style: solid, + color: #000, + ); + + @return map-get($defaults, $elem); +} + +/// Finds a value in a nested map. +/// @link https://css-tricks.com/snippets/sass/deep-getset-maps/ Deep Get/Set in Maps +/// +/// @param {Map} $map - Map to pull a value from. +/// @param {String} $keys... - Keys to use when looking for a value. +/// @returns {Mixed} The value found in the map. +@function map-deep-get($map, $keys...) { + @each $key in $keys { + $map: map-get($map, $key); + } + @return $map; +} + +/// Casts a map into a list. +/// @link http://hugogiraudel.com/2014/04/28/casting-map-into-list/ +/// +/// @param {Map} $map - Map to pull a value from. +/// +/// @returns {List} Depending on the flag, returns either $keys or $values or both. +@function map-to-list($map, $keep: 'both') { + $keep: if(index('keys' 'values', $keep), $keep, 'both'); + + @if type-of($map) == 'map' { + $keys: (); + $values: (); + + @each $key, $val in $map { + $keys: append($keys, $key); + $values: append($values, $val); + } + + @if $keep == 'keys' { + @return $keys; + } + @else if $keep == 'values' { + @return $values; + } + @else { + @return zip($keys, $values); + } + } + + @return if(type-of($map) != 'list', ($value,), $map); + +} + +/// Return a join of the two given strings `$str1` and `$str2`. +/// If the two strings are not empty, they are separated by `$delimiter`. +/// +/// @param {String} $str1 [null] - First string to join. +/// @param {String} $str1 [null] - Second string to join. +/// @param {String} $delimiter [null] - Delimieter between `$str1` and `$str2`. +/// +/// @returns {String} Join of `$str1`, `$delimiter` and `$str2`. +@function zf-str-join( + $str1: null, + $str2: null, + $delimiter: null +) { + $ret: ''; + + @if $str1 and str-length($str1) > 0 { + $ret: $ret + $str1; + + @if $delimiter and str-length($delimiter) > 0 and $str2 and str-length($str2) > 0 { + $ret: $ret + $delimiter; + } + } + @if $str2 and str-length($str2) > 0 { + $ret: $ret + $str2; + } + + @return $ret; +} + +/// Safely return a value from a map. +/// +/// @param {Map} $map - Map to retrieve a value from. +/// @param {String} $key - Name of the map key. +/// +/// @returns {List} Found value. +@function map-safe-get($map, $key) { + @if (type-of($map) == 'map' or (type-of($map) == 'list' and length($map) == 0)) { + @if (map-has-key($map, $key)) { + @return map-get($map, $key); + } + @else { + @error 'Key: `#{$key}` is not available in `#{$map}`'; + } + } + @else { + @error '`#{$map}` is not a valid map'; + } +} + +/// Convert the given `$val` to a Boolean. Empty values are considered as false. +//// +/// @access private +/// +/// @param {*} $val - Value to convert. +/// +/// @returns {Boolean} Converted Boolean value. +@function -zf-bool($val) { + @return $val != false and has-value($val); +} diff --git a/src/foundation/vendor/normalize.scss b/src/foundation/vendor/normalize.scss new file mode 100644 index 0000000..1023303 --- /dev/null +++ b/src/foundation/vendor/normalize.scss @@ -0,0 +1,281 @@ +@mixin foundation-normalize() { + /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */ + + // Document + // ========================================================================== + + // 1. Correct the line height in all browsers. + // 2. Prevent adjustments of font size after orientation changes in iOS. + + html { + line-height: 1.15; // 1 + -webkit-text-size-adjust: 100%; // 2 + } + + // Sections + // ========================================================================== + + // Remove the margin in all browsers. + + body { + margin: 0; + } + + // Correct the font size and margin on `h1` elements within `section` and + // `article` contexts in Chrome, Firefox, and Safari. + + h1 { + font-size: 2em; + margin: 0.67em 0; + } + + // Grouping content + // ========================================================================== + + // 1. Add the correct box sizing in Firefox. + // 2. Show the overflow in Edge and IE. + + hr { + box-sizing: content-box; // 1 + height: 0; // 1 + overflow: visible; // 2 + } + + // 1. Correct the inheritance and scaling of font size in all browsers. + // 2. Correct the odd `em` font sizing in all browsers. + + pre { + font-family: monospace, monospace; // 1 + font-size: 1em; // 2 + } + + // Text-level semantics + // ========================================================================== + + // Remove the gray background on active links in IE 10. + + a { + background-color: transparent; + } + + // 1. Remove the bottom border in Chrome 57- + // 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + + abbr[title] { + border-bottom: none; // 1 + text-decoration: underline; // 2 + text-decoration: underline dotted; // 2 + } + + // Add the correct font weight in Chrome, Edge, and Safari. + + b, + strong { + font-weight: bolder; + } + + // 1. Correct the inheritance and scaling of font size in all browsers. + // 2. Correct the odd `em` font sizing in all browsers. + + code, + kbd, + samp { + font-family: monospace, monospace; // 1 + font-size: 1em; // 2 + } + + // Add the correct font size in all browsers. + + small { + font-size: 80%; + } + + // Prevent `sub` and `sup` elements from affecting the line height in + // all browsers. + + sub, + sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + + sub { + bottom: -0.25em; + } + + sup { + top: -0.5em; + } + + // Embedded content + // ========================================================================== + + // Remove the border on images inside links in IE 10. + + img { + border-style: none; + } + + // Forms + // ========================================================================== + + // 1. Change the font styles in all browsers. + // 2. Remove the margin in Firefox and Safari. + + button, + input, + optgroup, + select, + textarea { + font-family: inherit; // 1 + font-size: 100%; // 1 + line-height: 1.15; // 1 + margin: 0; // 2 + } + + // Show the overflow in IE. + // 1. Show the overflow in Edge. + + button, + input { // 1 + overflow: visible; + } + + // Remove the inheritance of text transform in Edge, Firefox, and IE. + // 1. Remove the inheritance of text transform in Firefox. + + button, + select { // 1 + text-transform: none; + } + + // Correct the inability to style clickable types in iOS and Safari. + + button, + [type="button"], + [type="reset"], + [type="submit"] { + -webkit-appearance: button; + } + + // Remove the inner border and padding in Firefox. + + button::-moz-focus-inner, + [type="button"]::-moz-focus-inner, + [type="reset"]::-moz-focus-inner, + [type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; + } + + // Restore the focus styles unset by the previous rule. + + button:-moz-focusring, + [type="button"]:-moz-focusring, + [type="reset"]:-moz-focusring, + [type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; + } + + // Correct the padding in Firefox. + + fieldset { + padding: 0.35em 0.75em 0.625em; + } + + // 1. Correct the text wrapping in Edge and IE. + // 2. Correct the color inheritance from `fieldset` elements in IE. + // 3. Remove the padding so developers are not caught out when they zero out + // `fieldset` elements in all browsers. + + legend { + box-sizing: border-box; // 1 + color: inherit; // 2 + display: table; // 1 + max-width: 100%; // 1 + padding: 0; // 3 + white-space: normal; // 1 + } + + // Add the correct vertical alignment in Chrome, Firefox, and Opera. + + progress { + vertical-align: baseline; + } + + // Remove the default vertical scrollbar in IE 10+. + + textarea { + overflow: auto; + } + + // 1. Add the correct box sizing in IE 10. + // 2. Remove the padding in IE 10. + + [type="checkbox"], + [type="radio"] { + box-sizing: border-box; // 1 + padding: 0; // 2 + } + + // Correct the cursor style of increment and decrement buttons in Chrome. + + [type="number"]::-webkit-inner-spin-button, + [type="number"]::-webkit-outer-spin-button { + height: auto; + } + + // 1. Correct the odd appearance in Chrome and Safari. + // 2. Correct the outline style in Safari. + + [type="search"] { + -webkit-appearance: textfield; // 1 + outline-offset: -2px; // 2 + } + + // Remove the inner padding in Chrome and Safari on macOS. + + [type="search"]::-webkit-search-decoration { + -webkit-appearance: none; + } + + // 1. Correct the inability to style clickable types in iOS and Safari. + // 2. Change font properties to `inherit` in Safari. + + ::-webkit-file-upload-button { + -webkit-appearance: button; // 1 + font: inherit; // 2 + } + + // Interactive + // ========================================================================== + + // Add the correct display in Edge, IE 10+, and Firefox. + + details { + display: block; + } + + // Add the correct display in all browsers. + + summary { + display: list-item; + } + + // Misc + // ========================================================================== + + // Add the correct display in IE 10+. + + template { + display: none; + } + + // Add the correct display in IE 10. + + [hidden] { + display: none; + } +} diff --git a/src/foundation/xy-grid/_cell.scss b/src/foundation/xy-grid/_cell.scss new file mode 100644 index 0000000..b851b6a --- /dev/null +++ b/src/foundation/xy-grid/_cell.scss @@ -0,0 +1,272 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group xy-grid +//// + +/// Returns the appropriate CSS flex value for a cell base. +/// +/// @param {Keyword} $size [full] - The size of your cell. Accepts `full`, `auto`, `shrink`, `grow`, or any other value representing a cell size (it will be treated as `shrink`). +/// +/// @returns {List} The cell flex property value. +@function xy-cell-base($size: full) { + @if ($size == 'auto') { + @return 1 1 0px; + } + @else if ($size == 'grow') { + @return 1 0 auto; + } + @else if ($size == 'shrink' or $size == 'full' or zf-is-fraction($size, $allow-no-denominator: true)) { + @return 0 0 auto; + } + @return null; +} + +/// Calculate the size of a cell gutters. +/// +/// @param {Number|Map} $gutters [$grid-margin-gutters] - Map or single value for gutters. +/// @param {String} $breakpoint [null] - The name of the breakpoint size in your gutters map to get the size from. If `auto`, returns the responsive gutters map `$gutters`. If using with the `breakpoint()` mixin this will be set automatically unless manually entered. +/// +/// @returns {Number|Map} The cell gutter size or the responsive gutters map. +@function xy-cell-gutters( + $gutters: $grid-margin-gutters, + $breakpoint: null +) { + // For `auto`, returns the responsive map `$gutters`. + @if ($breakpoint == 'auto') { + @return $gutters; + } + + // Use the contextual breakpoint by default. + $breakpoint: -zf-current-breakpoint($breakpoint); + + @if ($breakpoint) { + @return -zf-get-bp-val($gutters, $breakpoint); + } + @else { + @return -zf-get-bp-val($gutters, $-zf-zero-breakpoint) or 0; + } +} + +/// Returns the percentage size of a cell. +/// +/// @param {Number|List} $size [$grid-columns] - Size to make the cell. You can pass a value in multiple formats, such as `6`, `50%`, `1 of 2` or `1/3`. +/// +/// @returns {Number} Size of the cell (in percent). +@function xy-cell-size( + $size: $grid-columns +) { + @return fraction-to-percentage($size, $denominator: $grid-columns); +} + +/// Returns the appropriate CSS value for a cell size. +/// +/// Gutters-related arguments are required for cells with margin gutters (by default) as the gutter is included in the width. +/// +/// @param {Keyword|Number} $size [full] - The size of your cell. Can be `full`, `auto`, `shrink` or any fraction like `6`, `50%`, `1 of 2` or `1/2`. +/// @param {Number|Map} $gutters [$grid-margin-gutters] - Map or single value for gutters. +/// @param {Keyword} $gutter-type [margin] - Type of gutter to output. Accepts `margin`, `padding` or `none`. +/// @param {String} $breakpoint [null] - The name of the breakpoint size in your gutters map to get the size from. If `auto`, returns a map of sizes adapted to responsive gutters. If using with the `breakpoint()` mixin this will be set automatically unless manually entered. +/// +/// @returns {Number|String|Map} The cell sizing property value, or a responsive map of them. +@function xy-cell-size-css( + $size: full, + $gutters: $grid-margin-gutters, + $gutter-type: margin, + $breakpoint: null +) { + $margin-gutter: 0; + + @if ($size == 'auto' or $size == 'shrink') { + @return auto; + } + + // For cells with margin gutters, the gutter is included in the width. + @if ($gutter-type == 'margin') { + $margin-gutter: xy-cell-gutters($gutters, $breakpoint); + @if ($margin-gutter == null) { + @error 'xy-cell-size: no gutters were found in `$gutters` for "$breakpoint: #{$breakpoint}"'; + } + } + + // Calculate the cell size (number) + $size-raw: if($size == 'full', 100%, xy-cell-size($size)); + + // Calculate the cell CSS size including gutters (string) + // If the cell has responsive margin gutters, return a responsive map of sizes. + @if type-of($margin-gutter) == 'map' { + $responsive-css-sizes: (); + + @each $bp, $mg in $margin-gutter { + $size-css: if($mg == 0, $size-raw, calc(#{$size-raw} - #{rem-calc($mg)})); + $responsive-css-sizes: map-merge($responsive-css-sizes, ($bp: $size-css)); + } + + @return $responsive-css-sizes; + } + // Otherwise, return a single CSS size. + @else { + $css-size: if($margin-gutter == 0, $size-raw, calc(#{$size-raw} - #{rem-calc($margin-gutter)})); + @return $css-size; + } +} + +/// Sets base flex properties for cells. +/// +/// @param {Keyword} $size [full] - The size of your cell. Accepts `full`, `auto`, `shrink`, `grow`, or any other value representing a cell size (it will be treated as `shrink`). +@mixin xy-cell-base($size: full) { + $base: xy-cell-base($size); + + flex: #{$base}; + + // Set base styles for "full" only + @if($size == 'full') { + min-height: 0px; + min-width: 0px; + } +} + +/// Resets a cells width (or height if vertical is true) as well as strips its gutters. +/// +/// @param {Boolean} $vertical [false] - Set to true to output vertical (height) styles rather than widths. +@mixin xy-cell-reset($vertical: true) { + $direction: if($vertical == true, height, width); + #{$direction}: auto; + max-#{$direction}: none; +} + +/// Sets sizing properties for cells. +/// +/// Gutters-related arguments are required for cells with margin gutters (by default) as the gutter is included in the width. +/// +/// @param {Keyword|Number} $size [full] - The size of your cell. Can be `full` (100% width), `auto` (use all available space), `shrink` (use only the required space) or any fraction (`6`, `50%`, `1 of 2` or `1/2`...). +/// @param {Number|Map} $gutters [$grid-margin-gutters] - Map or single value for gutters. +/// @param {Keyword} $gutter-type [margin] - Type of gutter to output. Accepts `margin`, `padding` or `none`. +/// @param {String} $breakpoint [null] - The name of the breakpoint size in your gutters map to get the size from. If `auto`, generates sizes adapted for responsive gutters. If using with the `breakpoint()` mixin this will be set automatically unless manually entered. +/// @param {Boolean} $vertical [false] - Set to true to output vertical (height) styles rather than widths. +@mixin xy-cell-size( + $size: full, + $gutters: $grid-margin-gutters, + $gutter-type: margin, + $breakpoint: null, + $vertical: false +) { + $sizes: xy-cell-size-css($size, $gutters, $gutter-type, $breakpoint); + $direction: if($vertical == true, height, width); + + @if (type-of($sizes) == 'map') { + @include -zf-breakpoint-value(auto, $sizes) { + #{$direction}: $-zf-bp-value; + } + } + @else { + #{$direction}: $sizes; + } +} + +/// Sets gutters properties for cells. +/// +/// @param {Number|Map} $gutters [$grid-margin-gutters] - Map or single value for gutters. +/// @param {Keyword} $gutter-type [margin] - Type of gutter to output. Accepts `margin`, `padding` or `none`. +/// @param {List} $gutter-position [null] - The position to apply gutters to. Accepts `top`, `bottom`, `left`, `right` in any combination. By default `right left` for horizontal cells and `top bottom` for vertical cells. +/// @param {String} $breakpoint [null] - The name of the breakpoint size in your gutters map to get the size from. If `auto`, generates responsive gutters. If using with the `breakpoint()` mixin this will be set automatically unless manually entered. +/// @param {Boolean} $vertical [false] - Direction of the gutters to output. See `$gutter-position`. +@mixin xy-cell-gutters( + $gutters: $grid-margin-gutters, + $gutter-type: margin, + $gutter-position: null, + $breakpoint: null, + $vertical: false +) { + // Get the default gutter position according to cell direction + @if($gutter-position == null) { + $gutter-position: if($vertical == true, top bottom, left right); + } + + // Get the gutter width for this breakpoint + $gutter-width: xy-cell-gutters($gutters, $breakpoint); + @if ($gutter-width == null) { + @error 'xy-cell-gutters: no gutters were found in `$gutters` for "$breakpoint: #{$breakpoint}"'; + } + + @if ($gutter-type and $gutter-type != none) { + @include xy-gutters($gutter-width, $gutter-type, $gutter-position); + } +} + +/// Creates a cell for your grid. +/// +/// @param {Keyword|Number} $size [full] - The size of your cell. Can be `full` (100% width), `auto` (use all available space), `shrink` (use only the required space) or any fraction (`6`, `50%`, `1 of 2` or `1/2`...). +/// @param {Boolean} $gutter-output [null] - [DEPRECATED] Whether or not to output gutters. +/// @param {Number|Map} $gutters [$grid-margin-gutters] - Map or single value for gutters. +/// @param {Keyword} $gutter-type [margin] - Type of gutter to output. Accepts `margin`, `padding` or `none`. +/// @param {List} $gutter-position [null] - The position to apply gutters to. Accepts `top`, `bottom`, `left`, `right` in any combination. By default `right left` for horizontal cells and `top bottom` for vertical cells. +/// @param {String} $breakpoint [null] - The name of the breakpoint size in your gutters map to get the size from. If `auto`, generates responsive gutters. If using with the `breakpoint()` mixin this will be set automatically unless manually entered. +/// @param {Boolean} $vertical [false] - Set to true to output vertical (height) styles rather than widths. +/// @param {List} $output [(base size gutters)] - Cell parts to output. You will need to generate others parts of the cell seperately, it may not work properly otherwise. +@mixin xy-cell( + $size: full, + $gutter-output: null, + $gutters: $grid-margin-gutters, + $gutter-type: margin, + $gutter-position: null, + $breakpoint: null, + $vertical: false, + $output: (base size gutters) +) { + // Default for $gutter-output + @if ($gutter-output != null) { + @warn 'xy-cell: $gutter-output is deprecated and will be removed. See migration notes at https://git.io/foundation-6-6-0'; + @if ($gutter-output == false) { + $output: sl-remove($output, gutters); + } + } + + @if (index($output, base)) { + @include xy-cell-base($size); + } + @if (index($output, size)) { + @include xy-cell-size($size, $gutters, $gutter-type, $breakpoint, $vertical); + } + @if (index($output, gutters)) { + @include xy-cell-gutters($gutters, $gutter-type, $gutter-position, $breakpoint, $vertical); + } +} + +/// Creates a single breakpoint sized grid. Used to generate our grid classes. +/// +/// `xy-cell-static()` is deprecated and will be removed. +/// Use `xy-cell()` instead with `$output: (size gutters)` to not generate the cell base. +/// See migration notes at https://git.io/foundation-6-6-0 +/// +/// @deprecated v6.6.0 +/// +/// @param {Keyword|Number} $size [full] - The size of your cell. Can be `full` (100% width), `auto` (use all available space), `shrink` (use only the required space) or any fraction (`6`, `50%`, `1 of 2` or `1/2`...). +/// @param {Boolean} $gutter-output [true] - Whether or not to output gutters. Always `true` for margin gutters. +/// @param {Number|Map} $gutters [$grid-margin-gutters] - Map or single value for gutters. +/// @param {Keyword} $gutter-type [margin] - Map or single value for gutters. +/// @param {String} $breakpoint [null] - The name of the breakpoint size in your gutters map to get the size from. If using with the `breakpoint()` mixin this will be set automatically unless manually entered. +/// @param {Boolean} $vertical [false] - Set to true to output vertical (height) styles rather than widths. +@mixin xy-cell-static( + $size: full, + $gutter-output: true, + $gutters: $grid-margin-gutters, + $gutter-type: margin, + $breakpoint: $-zf-zero-breakpoint, + $vertical: false +) { + @warn 'xy-cell-static() mixin is deprecated and will be removed. Use "xy-cell()" instead. See migration notes at https://git.io/foundation-6-6-0'; + + $gutter: -zf-get-bp-val($gutters, $breakpoint); + $gutter-position: if($vertical == true, top bottom, left right); + + $-gutter-output: if($gutter-type == 'margin', true, $gutter-output); + $-gutter-margin: if($gutter-type == 'margin', $gutter, 0); + + @include -xy-cell-properties($size, $-gutter-margin, $vertical); + @if ($-gutter-output) { + @include xy-gutters($gutter, $gutter-type, $gutter-position); + } +} diff --git a/src/foundation/xy-grid/_classes.scss b/src/foundation/xy-grid/_classes.scss new file mode 100644 index 0000000..11d176e --- /dev/null +++ b/src/foundation/xy-grid/_classes.scss @@ -0,0 +1,493 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group xy-grid +//// + +// Margin Grid classes +@mixin xy-base-grid-classes { + + // Grid Container + .grid-container { + @include xy-grid-container; + + &.fluid { + @include xy-grid-container(100%); + } + + &.full { + @include xy-grid-container(100%, 0); + } + } + + // Base grid styles + .grid-x { + @include xy-grid; + } + + .cell { + @include xy-cell(full, $gutter-type: none); + + &.auto { + @include xy-cell-base(auto); + } + + &.shrink { + @include xy-cell-base(shrink); + } + + } + .grid-x { + > .auto { + @include xy-cell-size(auto, $gutter-type: none); + } + + > .shrink { + @include xy-cell-size(shrink, $gutter-type: none); + } + } + + // Auto width + @include -zf-each-breakpoint() { + // This is a bit of a hack/workaround, see these issues & PRs for the backstory: + // https://github.com/zurb/foundation-sites/issues/10244 + // https://github.com/zurb/foundation-sites/pull/10222 and + // https://github.com/zurb/foundation-sites/pull/10164 + .grid-x { + $str: "> .#{$-zf-size}-shrink, > .#{$-zf-size}-full"; + @for $i from 1 through $grid-columns { + $str: $str + ", > .#{$-zf-size}-#{$i}" + } + #{$str} { + flex-basis: auto; + } + } + } + + @include -zf-each-breakpoint() { + // Responsive "auto" modifier + @if not($-zf-size == $-zf-zero-breakpoint) { + .grid-x > .#{$-zf-size}-auto { + @include xy-cell(auto, $gutter-type: none); + } + } + + %-xy-cell-base-shrink-horizontal-#{$-zf-size} { + @include xy-cell-base(shrink); + } + + // Responsive "shrink" modifier + @if not($-zf-size == $-zf-zero-breakpoint) { + .grid-x > .#{$-zf-size}-shrink { + @extend %-xy-cell-base-shrink-horizontal-#{$-zf-size}; + @include xy-cell-size(shrink, $gutter-type: none); + } + } + + // Responsive width modifiers + @for $i from 1 through $grid-columns { + // Sizing (percentage) + .grid-x > .#{$-zf-size}-#{$i} { + @extend %-xy-cell-base-shrink-horizontal-#{$-zf-size}; + @include xy-cell-size($i, $gutter-type: none); + } + } + } + + // Reset width when using `.grid-margin-x` not on `.grid-x` + .grid-margin-x:not(.grid-x) > .cell { + width: auto; + } + + // Reset height when using `.grid-margin-y` not on `.grid-y` + .grid-margin-y:not(.grid-y) > .cell { + height: auto; + } +} + +@mixin -xy-breakpoint-cell-classes($class-breakpoint, $gutter-breakpoint, $vertical) { + $prefix: if($class-breakpoint == $-zf-zero-breakpoint, '', '#{$class-breakpoint}-'); + > .#{$prefix}auto { + @include xy-cell-size(auto, $vertical: $vertical); + } + + > .#{$prefix}shrink { + @include xy-cell-size(shrink, $vertical: $vertical); + } + + @for $i from 1 through $grid-columns { + // Sizing (percentage) + $classname: if($vertical, '.#{$class-breakpoint}-#{$i}', '.#{$class-breakpoint}-#{$i}'); + + > #{$classname} { + @include xy-cell-size($i, $vertical: $vertical); + } + } +} + +// Margin Grid classes +@mixin xy-margin-grid-classes( + $gutter-position: left right, + $vertical: false, + $wrapping-selector: '.grid-margin-x' +){ + #{$wrapping-selector} { + @include xy-gutters($negative: true, $gutter-position: $gutter-position); + + // Base cell styles + > .cell { + @include xy-cell($vertical: $vertical, $output: (size gutters)); + } + + // base styles need to all be before the auto and shrink styles + @include -zf-each-breakpoint() { + @if(type-of($grid-margin-gutters) == 'map' and map-has-key($grid-margin-gutters, $-zf-size) and $-zf-size != $-zf-zero-breakpoint) { + > .cell { + @include xy-cell($vertical: $vertical, $output: (size gutters)); + } + } + } + + @include -zf-each-breakpoint() { + + // This is purely for responsive gutters - the margin grid has to go back and adjust widths (or heights) + // for all prior breakpoints. + // As their gutter is defined with their width/height, even breakpoint without a new margin must be + // generated to not having their width/height overrided by re-adjusted smaller breakpoints. + @if(type-of($grid-margin-gutters) == 'map' and map-has-key($grid-margin-gutters, $-zf-size)) { + @each $bp in -zf-breakpoints-less-than($-zf-size) { + @include -xy-breakpoint-cell-classes($bp, $-zf-size, $vertical); + } + } + + @include -xy-breakpoint-cell-classes($-zf-size, $-zf-size, $vertical); + } + } +} + +// Padding Grid classes +@mixin xy-padding-grid-classes { + .grid-padding-x { + + // Negative margin for nested grids + .grid-padding-x { + @include xy-gutters($negative: true); + } + + // Negative margin for grids within `grid-container/grid-container.fluid` + // This allows margin and padding grids to line up with eachother + .grid-container:not(.full) > & { + @include xy-gutters($negative: true); + } + + // Base cell styles + > .cell { + @include xy-gutters($gutters: $grid-padding-gutters, $gutter-type: padding); + } + } +} + +// Block Grid classes +@mixin xy-block-grid-classes($margin-grid: true, $padding-grid: true) { + @if $padding-grid { + @include -zf-each-breakpoint { + @for $i from 1 through $xy-block-grid-max { + .#{$-zf-size}-up-#{$i} { + @include xy-grid-layout($n: $i, $selector: '.cell', $gutter-type: padding, $output: (size)); + } + } + } + } + + @if $margin-grid { + @include -zf-each-breakpoint { + @for $i from 1 through $xy-block-grid-max { + // This is purely for responsive gutters - the margin grid has to go back and adjust widths (or heights) + // for prior breakpoints based on the responsive gutter. + @if(type-of($grid-margin-gutters) == 'map' and map-has-key($grid-margin-gutters, $-zf-size)) { + @each $bp in -zf-breakpoints-less-than($-zf-size) { + @if(map-has-key($grid-margin-gutters, $bp)) { + .grid-margin-x.#{$bp}-up-#{$i} { + @include xy-grid-layout($n: $i, $selector: '.cell', $gutter-type: margin, $output: (size)); + } + } + } + } + } + @for $i from 1 through $xy-block-grid-max { + .grid-margin-x.#{$-zf-size}-up-#{$i} { + @include xy-grid-layout($n: $i, $selector: '.cell', $gutter-type: margin, $output: (size)); + } + } + } + } +} + +// Collapse classes +@mixin xy-collapse-grid-classes($margin-grid: true, $padding-grid: true) { + @each $bp in $breakpoint-classes { + @if $margin-grid { + .#{$bp}-margin-collapse { + @include xy-grid-collapse($gutter-type: margin, $min-breakpoint: $bp); + } + } + + @if $padding-grid { + .#{$bp}-padding-collapse { + @include xy-grid-collapse($gutter-type: padding, $min-breakpoint: $bp); + } + } + } +} + +// Offset classes +@mixin xy-offset-cell-classes { + @include -zf-each-breakpoint { + @for $i from 1 through $grid-columns { + // Offsets + $o: $i - 1; + + .#{$-zf-size}-offset-#{$o} { + @include xy-cell-offset($o, $gutters: $grid-padding-gutters, $gutter-type: padding); + } + + .grid-margin-x > .#{$-zf-size}-offset-#{$o} { + @include xy-cell-offset($o); + } + } + } +} + +// Vertical Grid classes +@mixin xy-vertical-grid-classes( + $margin-grid: true, + $padding-grid: true +) { + + @include -zf-each-breakpoint() { + @if not($-zf-size == $-zf-zero-breakpoint) { + } + } + + .grid-y { + @include xy-grid(vertical, false); + + + > .cell { + @include xy-cell-reset(); + } + + > .auto { + @include xy-cell-size(auto, $gutter-type: none, $vertical: true); + } + + > .shrink { + @include xy-cell-size(shrink, $gutter-type: none, $vertical: true); + } + + + @include -zf-each-breakpoint() { + // This is a bit of a hack/workaround, see these issues and PRs for the backstory: + // https://github.com/zurb/foundation-sites/issues/10244 + // https://github.com/zurb/foundation-sites/pull/10222 and + // https://github.com/zurb/foundation-sites/pull/10164 + $str: "> .#{$-zf-size}-shrink, > .#{$-zf-size}-full"; + @for $i from 1 through $grid-columns { + $str: $str + ", > .#{$-zf-size}-#{$i}" + } + #{$str} { + flex-basis: auto; + } + } + + @include -zf-each-breakpoint() { + // Responsive "auto" modifier + @if not($-zf-size == $-zf-zero-breakpoint) { + > .#{$-zf-size}-auto { + @include xy-cell(auto, $gutter-type: none, $vertical: true); + } + } + + %-xy-cell-base-shrink-vertical-#{$-zf-size} { + @include xy-cell-base(shrink); + } + + // Responsive "shrink" modifier + @if not($-zf-size == $-zf-zero-breakpoint) { + > .#{$-zf-size}-shrink { + @extend %-xy-cell-base-shrink-vertical-#{$-zf-size}; + @include xy-cell-size(shrink, $gutter-type: none, $vertical: true); + } + } + + // Responsive width modifiers + @for $i from 1 through $grid-columns { + // Sizing (percentage) + > .#{$-zf-size}-#{$i} { + @extend %-xy-cell-base-shrink-vertical-#{$-zf-size}; + @include xy-cell-size($i, $gutter-type: none, $vertical: true); + } + } + + } + } + + @if $padding-grid { + .grid-padding-y { + // Negative margin for nested grids + .grid-padding-y { + @include xy-gutters($negative: true, $gutter-position: top bottom); + } + + // Base cell styles + > .cell { + @include xy-gutters($gutters: $grid-padding-gutters, $gutter-type: padding, $gutter-position: top bottom); + } + } + } + + @if $margin-grid { + @include xy-margin-grid-classes(top bottom, true, '.grid-margin-y'); + } + +} + +@mixin xy-frame-grid-classes($vertical-grid: true, $margin-grid: true) { + // Framed grid styles + .grid-frame { + @include xy-grid-frame; + } + + .cell .grid-frame { + width: 100%; // Same as include with $nested, but with less css + } + + .cell-block { + @include xy-cell-block(); + } + + .cell-block-y { + @include xy-cell-block(true); + } + + + .cell-block-container { + @include xy-cell-block-container(); + } + + + @include -zf-each-breakpoint(false) { + + .#{$-zf-size}-grid-frame { + @include xy-grid-frame; + } + + .cell .#{$-zf-size}-grid-frame { + width: 100%; // Same as include with $nested, but with less css + } + + .#{$-zf-size}-cell-block { + @include xy-cell-block(); + } + + .#{$-zf-size}-cell-block-container { + @include xy-cell-block-container(); + } + + .#{$-zf-size}-cell-block-y { + @include xy-cell-block(true); + } + } + + @if $vertical-grid { + .grid-y { + &.grid-frame { + width: auto; + @include xy-grid-frame(true); + } + + @include -zf-each-breakpoint(false) { + &.#{$-zf-size}-grid-frame { + width: auto; + @include xy-grid-frame(true); + } + + } + } + .cell { + .grid-y.grid-frame { + height: 100%; // Same as include with $nested, but with less css + } + @include -zf-each-breakpoint(false) { + .grid-y.#{$-zf-size}-grid-frame { + height: 100%; // Same as include with $nested, but with less css + } + } + } + } + @if $margin-grid { + @include xy-margin-grid-classes(top bottom, true, '.grid-margin-y'); + .grid-frame.grid-margin-y { + @include xy-grid-frame(true, false, $grid-margin-gutters, $include-base: false); + } + @include -zf-each-breakpoint(false) { + .grid-margin-y.#{$-zf-size}-grid-frame { + @include xy-grid-frame(true, false, $grid-margin-gutters, $-zf-size, false); + } + } + } +} + +// Final classes +@mixin foundation-xy-grid-classes( + $base-grid: true, + $margin-grid: true, + $padding-grid: true, + $block-grid: true, + $collapse: true, + $offset: true, + $vertical-grid: true, + $frame-grid: true +) { + + // Base grid styles + @if($base-grid) { + @include xy-base-grid-classes(); + } + + // Margin grid + @if($margin-grid) { + @include xy-margin-grid-classes(); + } + + // Padding grid + @if($padding-grid) { + @include xy-padding-grid-classes(); + } + + // Block grid + @if($block-grid) { + @include xy-block-grid-classes($margin-grid, $padding-grid); + } + + // Collapse gutters + @if($collapse) { + @include xy-collapse-grid-classes($margin-grid, $padding-grid); + } + + // Offset gutters + @if($offset) { + @include xy-offset-cell-classes(); + } + + // Vertical grid + @if($vertical-grid) { + @include xy-vertical-grid-classes($margin-grid, $padding-grid); + } + + @if ($frame-grid) { + @include xy-frame-grid-classes($vertical-grid, $margin-grid) + } +} diff --git a/src/foundation/xy-grid/_collapse.scss b/src/foundation/xy-grid/_collapse.scss new file mode 100644 index 0000000..76b692f --- /dev/null +++ b/src/foundation/xy-grid/_collapse.scss @@ -0,0 +1,75 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group xy-grid +//// + +/// Collapses the grid a cells within it. +/// +/// @param {String} $selector [.cell] - The child element to remove the gutter from. +/// @param {Keyword} $gutter-type [margin] - The type of gutter to remove. +/// @param {List} $gutter-position [right left] - The positions to remove gutters from. Accepts `top`, `bottom`, `left`, `right` in any combination. +/// @param {Keyword} $min-breakpoint [$-zf-zero-breakpoint] - Minimum breakpoint in `$breakpoint-classes` for which to collapse the gutter. +@mixin xy-grid-collapse( + $selector: '.cell', + $gutter-type: margin, + $gutter-position: right left, + $min-breakpoint: $-zf-zero-breakpoint +) { + // First, lets negate any margins on the top level + @if ($gutter-type == 'margin') { + + @include breakpoint($min-breakpoint) { + @each $value in $gutter-position { + margin-#{$value}: 0; + } + + > #{$selector} { + @each $value in $gutter-position { + margin-#{$value}: 0; + } + } + } + + $excluded-bps: -zf-breakpoints-less-than($min-breakpoint); + + // Output new widths to not include gutters + @each $bp in $breakpoint-classes { + @if(sl-contain($excluded-bps, $bp)) { + @include breakpoint($min-breakpoint) { + @for $i from 1 through $grid-columns { + // Sizing (percentage) + > .#{$bp}-#{$i} { + @include xy-cell-size($i, $gutter-type: none); + } + } + } + } @else { + @include breakpoint($bp) { + @for $i from 1 through $grid-columns { + // Sizing (percentage) + > .#{$bp}-#{$i} { + @include xy-cell-size($i, $gutter-type: none); + } + } + } + } + } + } + @else { + + @include breakpoint($min-breakpoint) { + @each $value in $gutter-position { + margin-#{$value}: 0; + } + + > #{$selector} { + @each $value in $gutter-position { + padding-#{$value}: 0; + } + } + } + } +} diff --git a/src/foundation/xy-grid/_frame.scss b/src/foundation/xy-grid/_frame.scss new file mode 100644 index 0000000..9bd1d11 --- /dev/null +++ b/src/foundation/xy-grid/_frame.scss @@ -0,0 +1,86 @@ +/// Modifies a grid to give it "frame" behavior (no overflow, no wrap, stretch behavior) +/// +/// @param {Boolean} $vertical [false] - Is grid vertical or horizontal. Should match grid. +/// @param {Boolean} $nested [false] - Is grid nested or not. If nested is true this sets the frame to 100% height, otherwise will be 100vh. +/// @param {Number|Map} $gutters [null] - Map or single value for gutters. +/// @param {String} $breakpoint [null] - The name of the breakpoint size in your gutters map to get the size from. +/// @param {Boolean} $include-base [true] - Include the base styles that don't vary per breakpoint. +@mixin xy-grid-frame( + $vertical: false, + $nested: false, + $gutters: null, + $breakpoint: null, + $include-base: true +) { + + @if $include-base { + overflow: hidden; + position: relative; + flex-wrap: nowrap; + align-items: stretch; + } + + @if $breakpoint == null and type-of($gutters) == 'map' { + @include -zf-each-breakpoint() { + @include xy-grid-frame($vertical, $nested, $gutters, $-zf-size, false); + } + } @else { + // Get our gutters if applicable + $gutter: -zf-get-bp-val($gutters, $breakpoint); + + // If we have a gutter, add it to the width/height + @if $gutter { + @if $vertical == true { + $unit: if($nested == true, 100%, 100vh); + $gutter: rem-calc($gutter); + height: calc(#{$unit} + #{$gutter}); + } @else { + $unit: if($nested == true, 100%, 100vw); + $gutter: rem-calc($gutter); + width: calc(#{$unit} + #{$gutter}); + } + } + @else { + @if $vertical == true { + height: if($nested == true, 100%, 100vh); + } @else { + width: if($nested == true, 100%, 100vw); + } + } + } +} + +/// Modifies a cell to give it "block" behavior (overflow auto, inertial scrolling) +/// +/// @param {Boolean} $vertical [false] - Is grid vertical or horizontal. Should match grid. +@mixin xy-cell-block( + $vertical: false +) { + $property: if($vertical == true, 'overflow-y', 'overflow-x'); + + @if $vertical == true { + overflow-y: auto; + max-height: 100%; + min-height: 100%; + } @else { + overflow-x: auto; + max-width: 100%; + } + + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; +} + +/// Container for inside a grid frame containing multiple blocks. Typically used +/// as a modifier for a `.cell` to allow the cell to pass along flex sizing +/// constraints / from parents to children. +@mixin xy-cell-block-container() { + display: flex; + flex-direction: column; + max-height: 100%; + + > .grid-x { + max-height: 100%; + flex-wrap: nowrap; + } +} diff --git a/src/foundation/xy-grid/_grid.scss b/src/foundation/xy-grid/_grid.scss new file mode 100644 index 0000000..722d0b2 --- /dev/null +++ b/src/foundation/xy-grid/_grid.scss @@ -0,0 +1,37 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group xy-grid +//// + +/// Creates a max width container, designed to house your grid content. +/// +/// @param {Number} $width [$grid-container] - a width to limit the container to. +/// @param {Number} $padding [$grid-container-padding] - paddings of the container. +@mixin xy-grid-container( + $width: $grid-container, + $padding: $grid-container-padding +) { + @include xy-gutters($gutters: $padding, $gutter-type: padding); + + max-width: $width; + margin-left: auto; + margin-right: auto; +} + +/// Creates a container for your flex cells. +/// +/// @param {Keyword} $direction [horizontal] - Either horizontal or vertical direction of cells within. +/// @param {Boolean} $wrap [true] - If the cells within should wrap or not. +@mixin xy-grid( + $direction: horizontal, + $wrap: true +) { + $direction: if($direction == 'horizontal', row, column); + $wrap: if($wrap, wrap, nowrap); + + display: flex; + flex-flow: $direction $wrap; +} diff --git a/src/foundation/xy-grid/_gutters.scss b/src/foundation/xy-grid/_gutters.scss new file mode 100644 index 0000000..19e1db8 --- /dev/null +++ b/src/foundation/xy-grid/_gutters.scss @@ -0,0 +1,45 @@ +// Foundation for Sites by ZURB +// foundation.zurb.com +// Licensed under MIT Open Source + +//// +/// @group xy-grid +//// + +/// Create gutters for a cell/c |