48 |
53 |
58 |
63 |
--------------------------------------------------------------------------------
/preview/src/component.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Component"
3 | layout: "layout/base.html"
4 | ---
5 |
6 |
Hello, this is a danger type alert message
7 |
--------------------------------------------------------------------------------
/preview/src/font/montserrat-v25-latin-ext_latin-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/conedevelopment/sprucecss/0d57b4ae03be67e8b9db47b3c1292bcbf585548f/preview/src/font/montserrat-v25-latin-ext_latin-700.woff2
--------------------------------------------------------------------------------
/preview/src/font/montserrat-v25-latin-ext_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/conedevelopment/sprucecss/0d57b4ae03be67e8b9db47b3c1292bcbf585548f/preview/src/font/montserrat-v25-latin-ext_latin-regular.woff2
--------------------------------------------------------------------------------
/preview/src/function.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Function"
3 | layout: "layout/base.html"
4 | ---
5 |
6 |
spacer
7 |
18 |
--------------------------------------------------------------------------------
/preview/src/index.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Home"
3 | layout: "layout/base.html"
4 | ---
5 |
--------------------------------------------------------------------------------
/preview/src/js/reading-direction.js:
--------------------------------------------------------------------------------
1 | document.getElementById('reading-direction').addEventListener('input', (e) => {
2 | document.documentElement.setAttribute('dir', e.target.value);
3 | localStorage.setItem('spruce-reading-direction', e.target.value, 31556926, '/');
4 | }, false);
5 |
--------------------------------------------------------------------------------
/preview/src/js/theme-mode.js:
--------------------------------------------------------------------------------
1 | document.getElementById('theme-mode').addEventListener('input', (e) => {
2 | document.documentElement.dataset.themeMode = e.target.value;
3 | localStorage.setItem('spruce-theme-mode', e.target.value, 31556926, '/');
4 | }, false);
5 |
--------------------------------------------------------------------------------
/preview/src/media.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Media"
3 | layout: "layout/base.html"
4 | ---
5 |
6 |
7 |
8 |
9 |
10 |

11 |
12 |
13 | A caption for the above image.
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/preview/src/mixin.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Mixin"
3 | layout: "layout/base.html"
4 | ---
5 |
6 |
short-ring
7 |
8 |
9 |
10 |
11 |
scrollbar
12 |
16 |
a11y-card-link
17 |
18 |
text-ellipsis
19 |
20 |
Multiline ellipsis test Pellentesque quis sapien quis ex tincidunt ornare sit amet eu neque. Fusce ut tempor nisl, vitae semper dui. Fusce sagittis felis orci, ut ultrices justo congue a. Aenean a auctor nisi, eu fringilla erat. Cras id lacinia lorem. Nunc porttitor lorem tortor, sit amet faucibus urna vestibulum ac. Sed facilisis tristique consequat. Fusce condimentum sed enim eu varius. Vestibulum scelerisque sapien vulputate maximus commodo. Fusce dictum erat nec felis convallis elementum. Suspendisse blandit nibh varius, varius lacus quis, commodo orci. Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
21 |
Multiline ellipsis test Pellentesque quis sapien quis ex tincidunt ornare sit amet eu neque. Fusce ut tempor nisl, vitae semper dui. Fusce sagittis felis orci, ut ultrices justo congue a. Aenean a auctor nisi, eu fringilla erat. Cras id lacinia lorem. Nunc porttitor lorem tortor, sit amet faucibus urna vestibulum ac. Sed facilisis tristique consequat. Fusce condimentum sed enim eu varius. Vestibulum scelerisque sapien vulputate maximus commodo. Fusce dictum erat nec felis convallis elementum. Suspendisse blandit nibh varius, varius lacus quis, commodo orci. Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
22 |
23 |
24 |
selection
25 |
26 |
Sed facilisis tristique consequat. Fusce condimentum sed enim eu varius. Vestibulum scelerisque sapien vulputate maximus commodo. Fusce dictum erat nec felis convallis elementum. Suspendisse blandit nibh varius, varius lacus quis, commodo orci. Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
27 |
SELECTION Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
28 |
29 |
clear-list
30 |
31 | - Cleared UL list
32 | - List item two
33 | - List item three
34 | - List item four
35 |
36 |
spacer-clamp
37 |
Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
38 |
word-wrap
http://www.reallylong.link/rll/eHljLISQs4/VYnSQH4F8X3sNpWLpziElYnqUVCq8WmbR06lHV7vCttOFydbFBKzBovFUYDMl_SGFCRd3eWTUpIggT5TLgATzFQELX_YUMb4U6ah7UG/7sNSn6XjPWAke0FUy26kfo3ckKU0WtJbyeWNBoliQ4iI7ZIu0lSMD4E_AKRrWT_d27gw3/j/GA/xdi9_3xB3Lq2DcUndrHNPm0_iTVNbvxmodOkGqq3rsQwNU3G5mfWVAgKTTs64NlZ6NA11mQDTLLZQ_X6SCqZh3d8wnG2d2ZwrZ/3OU1hdOiWNKnry0Ml8_X/Oo3EZRHLGpv_mehdAfy4KOzDtsYYdeBfDTMj1QBwvVmh8r9sOqthOgtIE15muoY7dPw_SHWVSo8XoH68tdZr2nqm4NiOAjrchNnaS9LqpLD5Zd09_Tpj0hwgfQFAvlA0_0TQzI9xsV1bxB_zY9Y8a0DeP/_sijH9kCQV0rdKT8Fu6QcJ_MPELWSEmdvVqyBlLyjgWHcyAKzJNuU1CnLf425IOTLpjmAUVvCElISwCCdw1_4Klv4riPll1dfvZjU5QaBx3Vq/EZKK8Z247sL5vuap9pOW04CrkQzsLZF2RRLyZ98C5FWXJP9uD41SfTWR8f9ybpA1YaibXaQgTHgtHaFW/kbbmR3zl3LRhV0HZD9/vY4QyRoi4CHnZpF2wq/nDRLtY3Jy7AiBjUQGMi80UTmzucFY/2sV5VcvLObY7UrbVOlQax/dCjxP0NPAQMLJukneUmvf0C3uzoXm07DxynvusbvVejeexdxU9f5Hdqg1V8yKxYpD9NAOB5dPRBdSoT1uhO1IcVIziwdU0S5Ybmf7fqF06JjxIQQXetOf6w6d4P3BZER1FfD/WA7zj7p/KrqxK6ZMFu_npgHrWTI2qF7bn_nAzti9DyGCFaIyqKSmPMQVMyyF/VWjjF2bauMKliqzInbC9sE3pAf82Mgx570z3ThYHK/FRKT7YxrUWURUb1c2JF3vNs28t/G6c63XX0LTSloOpE1Ci1WNHdwzVKHVa1oz5MaW_1lW_Vb/km231/qu0rlV5hpQCH_0QqkJw7/p1uHBoWQq5TUxeH5RyfBwNI/8XYAa1ybPIGhtXQrnv7VYyG2P4pKkIXMjJkUHz4yAGSMT0C8ZShZHAG_xPmhhwpzXZaNsh4C44u5h/6sfjDD2a7YcfyxsGFd4bqfUqsJcwF7g1GbZeBeA68AsIGcA6ZOa_f1Rnzff858u/2X6yhCn53DSgHwQZIei1xo2OSI6Ia8UjgzW10hMr_fbkOuO64nqKTv9Rzk1cktR1lNnBCBZUaU2I0uUlsp4ELG1073iKab_kkPx0jfxNc6F9abeCp3zrTxu6Foo_bplsKdRUZe25wI_hVpc7PUzIxe/Agaf9/WQwO7azadwWDkJJfAHNruhwH6yVfS5JVR_4DT7CcjdowIV2_bNFmJjCljqn__vBn04FsT6nhCBpFCKV5vmGJtXI935staZS1eSp230RFl3hlCEkUxovrAvXKyPVv1OrndCeylLR8mGkUc/rTnl04YgMlqqflSOFdVIcqqH1JvgrhMFtW/myZ/R06O3dKMxppl2Az8NkU/OmYpcOZ4YrcCsFzpsVu2ybi6sjm_AYgC01zERrEoJ9ATX0BpxSdyjmF6XWZKP3iFcRsgv7otfGqzx1mMSVPBddS/jknevdQNezn3/7Qy/sUVKnS1sPEh0bNrxdJ4r1ovAM2Q1esGHxd7u67TA7iTJty3K5Zbh/poh7000k0ZjTLVhmP1PF2f0m3JjIsRoYJkUsjJNSBrnjflSJJbrjoKzFOdR2Og43phCRKDSRIBkZS6ZQULcYD2ptuQjppoTbcRuQhxsrhrEayABlU0KptmBX_tRrtj0zxzrpTxsiM0n_pzSFS0M4Os5EVkp9ZwqVKC6i7U8dafrpUAgk8VK_4pyQ3MFxk1eu2l_w/aS0yM96
39 |
--------------------------------------------------------------------------------
/preview/src/scss/component/_alert.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:map';
3 | @use 'spruce' as *;
4 |
5 | .alert {
6 | align-items: center;
7 | border: 1px solid;
8 | border-left: 0.4rem solid;
9 | border-radius: config('border-radius-sm', $display);
10 | display: flex;
11 | gap: spacer('m');
12 | justify-content: space-between;
13 | line-height: config('line-height-md', $typography);
14 | padding: 0.65em 1em;
15 |
16 | @each $name, $value in map.get($colors, 'alert') {
17 | @at-root .alert--#{$name} {
18 | background-color: color.scale($value, $lightness: 95%);
19 | color: color.scale($value, $lightness: -30%);
20 | }
21 |
22 | @at-root .alert--#{$name} .alert__close {
23 | background-color: color.scale($value, $lightness: -30%);
24 | color: color.scale($value, $lightness: 90%);
25 | }
26 | }
27 |
28 | @each $name, $value in map.get($colors, 'alert') {
29 | @at-root [data-theme-mode='dark'] .alert--#{$name} {
30 | background-color: transparent;
31 | border-color: color.scale($value, $lightness: -30%);
32 | color: color('text');
33 | }
34 | }
35 |
36 | &__caption {
37 | @include layout-stack('xxs');
38 | }
39 |
40 | &__close {
41 | --dimension: 1.5rem;
42 | @include clear-btn;
43 | @include transition;
44 | align-items: center;
45 | block-size: var(--dimension);
46 | border-radius: config('border-radius-sm', $display);
47 | display: flex;
48 | flex-shrink: 0;
49 | inline-size: var(--dimension);
50 | justify-content: center;
51 |
52 | &:hover,
53 | &:focus {
54 | opacity: 0.75;
55 | }
56 |
57 | svg {
58 | --dimension: 0.5rem;
59 | block-size: var(--dimension);
60 | inline-size: var(--dimension);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/preview/src/scss/component/_color.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'spruce' as *;
3 |
4 | .colors {
5 | @include layout-grid('s:m', $minimum: 15rem);
6 | }
7 |
8 | .color-item {
9 | align-items: start;
10 | display: flex;
11 | gap: spacer(m);
12 |
13 | &--bordered &__color {
14 | box-shadow: 0 0 0 1px color('border') inset;
15 | }
16 |
17 | &__color {
18 | border-radius: config('border-radius-lg', $display);
19 | flex-shrink: 0;
20 | block-size: 3rem;
21 | inline-size: 3rem;
22 | }
23 |
24 | &__caption {
25 | display: flex;
26 | flex-direction: column;
27 | line-height: config('line-height-md', $typography);
28 | }
29 |
30 | &__name {
31 | color: color('heading');
32 | font-weight: 700;
33 | }
34 |
35 | &__value {
36 | font-weight: 300;
37 | }
38 | }
39 |
40 | @each $name, $value in map.get($colors, 'base') {
41 | .color-item--base-#{$name} .color-item__color {
42 | background-color: color($name, 'base');
43 | }
44 | }
45 |
46 | @each $name, $value in map.get($colors, 'selection') {
47 | .color-item--selection-#{$name} .color-item__color {
48 | background-color: color($name, 'selection');
49 | }
50 | }
51 |
52 | @each $name, $value in map.get($colors, 'alert') {
53 | .color-item--alert-#{$name} .color-item__color {
54 | background-color: color($name, 'alert');
55 | }
56 | }
57 |
58 | @each $name, $value in map.get($colors, 'btn') {
59 | .color-item--btn-#{$name} .color-item__color {
60 | background-color: color($name, 'btn');
61 | }
62 | }
63 |
64 | @each $name, $value in map.get($colors, 'form') {
65 | .color-item--form-#{$name} .color-item__color {
66 | background-color: color($name, 'form');
67 | }
68 | }
69 |
70 | @each $name, $value in map.get($colors, 'table') {
71 | .color-item--table-#{$name} .color-item__color {
72 | background-color: color($name, 'table');
73 | }
74 | }
75 |
76 | @each $name, $value in map.get($colors, 'scrollbar') {
77 | .color-item--scrollbar-#{$name} .color-item__color {
78 | background-color: color($name, 'scrollbar');
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/preview/src/scss/component/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'color';
2 | @forward 'navigation';
3 | @forward 'alert';
4 | @forward 'preview';
5 |
--------------------------------------------------------------------------------
/preview/src/scss/component/_navigation.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | .navigation-menu {
4 | @include clear-list;
5 | @include layout-stack('xs');
6 |
7 | a {
8 | color: color('text');
9 | text-decoration: none;
10 |
11 | &[aria-current='page'] {
12 | color: color('primary');
13 | font-weight: 700;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/preview/src/scss/component/_preview.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | .preview-group {
4 | align-items: start;
5 | display: flex;
6 | flex-wrap: wrap;
7 | gap: spacer('s');
8 | }
9 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_config.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'dark-colors' as dark;
3 |
4 | $color-tertiary: hsl(337deg 55% 43%);
5 | $spacer: 1.25rem;
6 |
7 | @use 'spruce' with (
8 | $colors: (
9 | 'btn': (
10 | 'custom-background': hsl(0deg 0% 100%),
11 | 'custom-background-hover': hsl(332deg 49% 29%),
12 | 'custom-foreground': hsl(0deg 0% 0%),
13 | 'custom-foreground-hover': hsl(0deg 0% 100%),
14 | 'dark-background': hsl(0deg 0% 0%),
15 | 'dark-background-hover': hsl(0deg 0% 20%),
16 | 'dark-foreground': hsl(0deg 0% 100%),
17 | 'dark-outline-border': hsl(260deg 4% 70%),
18 | 'dark-outline-foreground': hsl(0deg 0% 0%),
19 | 'dark-outline-foreground-hover': hsl(0deg 0% 100%),
20 | 'dark-outline-background-hover': hsl(0deg 0% 0%),
21 | 'dark-outline-focus-ring': hsl(295deg 100% 50%),
22 | 'tertiary-background': $color-tertiary,
23 | 'tertiary-foreground': hsl(0deg 0% 100%),
24 | 'tertiary-shadow-focus': color.adjust($color-tertiary, $alpha: -0.75),
25 | ),
26 | form: (
27 | 'check-background': red,
28 | 'check-focus-ring': olive,
29 | 'check-foreground': aqua,
30 | 'switch-background': pink,
31 | 'switch-focus-ring': blue,
32 | 'switch-foreground': yellow,
33 | ),
34 | ),
35 | $dark-colors: dark.$colors,
36 | $form-select: (
37 | 'padding-inline-end': 4rem,
38 | ),
39 | $settings: (
40 | 'css-custom-properties': true,
41 | 'color-fallback': false,
42 | 'html-smooth-scrolling': true,
43 | 'hyphens': true,
44 | 'prefix': 'spruce',
45 | 'print': true,
46 | 'scaler': 25,
47 | 'utilities': (
48 | 'typography': true,
49 | ),
50 | ),
51 | $btn-lg: (
52 | 'text-transform': uppercase,
53 | ),
54 | $btn-sm: (
55 | 'icon-size': 1.25rem,
56 | ),
57 | $form-fieldset: (
58 | 'layout-gap': 1rem,
59 | 'legend-font-family': serif,
60 | ),
61 | $form-label: (
62 | 'text-transform': uppercase,
63 | ),
64 | $form-range: (
65 | 'thumb-block-size': 2rem,
66 | 'thumb-inline-size': 2rem,
67 | ),
68 | $generators: (
69 | 'content': (
70 | 'normalize': true,
71 | ),
72 | 'form': (
73 | 'btn': true,
74 | ),
75 | ),
76 | $layout: (
77 | 'container-inline-size': 70rem,
78 | ),
79 | $typography: (
80 | 'letter-spacing-heading': 0.05em,
81 | ),
82 | );
83 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_dark-colors.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 |
3 | $color-black: hsl(206deg 100% 7%);
4 | $color-danger: hsl(0deg 71% 51%);
5 | $color-gray: hsl(0deg 0% 97%);
6 | $color-gray-dark: hsl(0deg 0% 100% / 8%);
7 | $color-primary: hsl(261deg 54% 70%);
8 | $color-secondary: hsl(227deg 92% 55%);
9 | $color-success: hsl(150deg 100% 33%);
10 | $color-white: hsl(0deg 0% 95%);
11 |
12 | $colors: (
13 | 'base': (
14 | 'background': $color-black,
15 | 'blockquote-border': $color-primary,
16 | 'border': $color-gray-dark,
17 | 'code-background': hsl(207deg 64% 18%),
18 | 'code-foreground': $color-white,
19 | 'heading': $color-white,
20 | 'link': $color-primary,
21 | 'link-hover': color.scale($color-primary, $lightness: 20%),
22 | 'mark-background': hsl(50deg 100% 80%),
23 | 'mark-foreground': $color-black,
24 | 'marker': $color-primary,
25 | 'primary': $color-primary,
26 | 'secondary': $color-secondary,
27 | 'strong': $color-white,
28 | 'text': $color-gray,
29 | ),
30 | 'btn': (
31 | 'primary-background': hsl(261deg 52% 59%),
32 | 'primary-background-hover': hsl(261deg 52% 65%),
33 | 'primary-foreground': $color-white,
34 | 'primary-shadow': color.adjust($color-primary, $lightness: -25%),
35 | 'secondary-background': $color-secondary,
36 | 'secondary-background-hover': color.adjust($color-secondary, $lightness: 5%),
37 | 'secondary-foreground': $color-white,
38 | 'secondary-shadow': color.adjust($color-secondary, $lightness: -20%),
39 | ),
40 | 'form': (
41 | 'background': color.scale($color-black, $lightness: 5%),
42 | 'background-disabled': $color-black,
43 | 'border': $color-gray-dark,
44 | 'border-disabled': $color-gray-dark,
45 | 'border-focus': $color-primary,
46 | 'check-background': $color-primary,
47 | 'check-focus-ring': $color-primary,
48 | 'check-foreground': $color-black,
49 | 'group-label-background': color.scale($color-black, $lightness: 2.5%),
50 | 'group-label-foreground': $color-gray,
51 | 'invalid': $color-danger,
52 | 'invalid-focus-ring': color.adjust($color-danger, $alpha: -0.75),
53 | 'label': $color-white,
54 | 'legend': $color-white,
55 | 'placeholder': hsl(0deg 0% 60%),
56 | 'range-thumb-background': $color-primary,
57 | 'range-thumb-focus-ring': $color-primary,
58 | 'range-track-background': $color-gray-dark,
59 | 'ring-focus': color.adjust($color-primary, $alpha: -0.75),
60 | 'select-foreground': hsl(0deg 0% 100%),
61 | 'text': $color-gray,
62 | 'valid': $color-success,
63 | 'valid-focus-ring': color.adjust($color-success, $alpha: -0.75),
64 | ),
65 | 'selection': (
66 | 'background': $color-primary,
67 | 'foreground': $color-white,
68 | ),
69 | 'scrollbar': (
70 | 'thumb-background': hsl(0deg 0% 100% / 15%),
71 | 'thumb-background-hover': hsl(0deg 0% 100% / 25%),
72 | 'track-background': hsl(0deg 0% 100% / 5%),
73 | ),
74 | 'table': (
75 | 'border': $color-gray-dark,
76 | 'caption': $color-gray,
77 | 'heading': $color-white,
78 | 'hover': hsl(0deg 0% 100% / 5%),
79 | 'text': $color-gray,
80 | 'stripe': hsl(0deg 0% 100% / 2.5%),
81 | ),
82 | );
83 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_dark-mode.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | @include generate-color-variables(
4 | $dark-colors,
5 | ':root[data-theme-mode="dark"]'
6 | );
7 |
8 | [data-theme-mode='dark'] {
9 | color-scheme: dark;
10 |
11 | select.form-control:not([multiple]):not([size]) {
12 | @include field-icon(
13 | config('select', $form-icon, false),
14 | color('select-foreground', 'form', true, $dark-colors)
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_font.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | @include font-face(
4 | 'Montserrat',
5 | '../font/montserrat-v25-latin-ext_latin-regular.woff2'
6 | );
7 |
8 | @include font-face(
9 | 'Montserrat',
10 | '../font/montserrat-v25-latin-ext_latin-700.woff2',
11 | 700
12 | );
13 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'config';
2 | @forward 'font';
3 | @forward 'styles';
4 | @forward 'dark-mode';
5 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_styles.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | @include generate-styles;
4 |
--------------------------------------------------------------------------------
/preview/src/scss/layout/_container.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | .container {
4 | --inline-size: #{config('container-inline-size', $layout, false)};
5 | --gap: #{spacer-clamp('m', 'l')};
6 |
7 | @include layout-center(
8 | var(--gap),
9 | var(--inline-size)
10 | );
11 |
12 | &--narrow {
13 | --inline-size: 50rem;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/preview/src/scss/layout/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'container';
2 | @forward 'main';
3 |
--------------------------------------------------------------------------------
/preview/src/scss/layout/_main.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | .l-main {
4 | padding-block: spacer-clamp('l', 'xl');
5 |
6 | &__inner {
7 | @include layout-sidebar('l:xl', 12rem);
8 | }
9 |
10 | &__sidebar {
11 | &-inner {
12 | inset-block-start: spacer('m');
13 | position: sticky;
14 | @include layout-stack('m');
15 | }
16 | }
17 |
18 | &__body {
19 | @include layout-stack('s');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/preview/src/scss/main.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:math';
3 | @use 'sass:map';
4 |
5 | @forward 'config';
6 | @forward 'layout';
7 | @forward 'component';
8 |
9 | @use 'spruce' as *;
10 |
11 | :root {
12 | @include set-css-variable((
13 | --section-gap: spacer-clamp('xl', 'xxl')
14 | ));
15 | }
16 |
17 | @include generate-form-check(
18 | '.wpcf7-list-item label',
19 | '.wpcf7-list-item input',
20 | '.wpcf7-list-item .wpcf7-list-item-label'
21 | );
22 |
23 | @include generate-table(
24 | 'table',
25 | false,
26 | false
27 | );
28 |
29 | .btn--dark {
30 | @include btn-variant('dark');
31 | }
32 |
33 | .btn--outline-dark {
34 | @include btn-variant-outline('dark');
35 | }
36 |
37 | .btn--custom {
38 | @include btn-variant('custom');
39 | box-shadow: -3px 5px color('custom-foreground', 'btn');
40 | border: 3px solid color('custom-foreground', 'btn');
41 | border-radius: 0;
42 | font-family: config('font-family-cursive', $typography);
43 | }
44 |
45 | .btn--tertiary {
46 | @include btn-variant('tertiary');
47 | }
48 |
49 | .clear-btn {
50 | @include clear-btn;
51 | }
52 |
53 | .section-title {
54 | font-size: font-size('h4');
55 | margin-block: 0 spacer('m');
56 |
57 | * + & {
58 | margin-block-start: spacer('l');
59 | }
60 | }
61 |
62 | .ellipsis-1 {
63 | @include text-ellipsis(1);
64 | }
65 |
66 | .ellipsis-2 {
67 | @include text-ellipsis(2);
68 | }
69 |
70 | .selection-1 {
71 | @include selection('secondary', $is-direct: true);
72 | @include transition;
73 | }
74 |
75 | .selection-2 {
76 | @include selection(#000, $is-direct: true);
77 | }
78 |
79 | .ellipsis-btn {
80 | @include text-ellipsis(1);
81 | max-inline-size: 10ch;
82 | }
83 |
84 | .scrollbar {
85 | max-block-size: 15rem;
86 | overflow: auto;
87 | padding-inline-end: spacer('m');
88 | @include scrollbar;
89 | @include layout-stack;
90 | }
91 |
92 | .custom-heading-size {
93 | font-size: responsive-font-size(4rem, 30, 4vw);
94 | font-family: 'Montserrat', sans-serif;
95 | }
96 |
97 | .custom-link {
98 | @include transition(2s, background-color, linear);
99 |
100 | &:hover {
101 | background-color: aqua;
102 | }
103 | }
104 |
105 | .cleared-list {
106 | @include clear-list;
107 | }
108 |
109 | .card {
110 | @include a11y-card-link('.card__link', true);
111 | border: 1px solid color('border');
112 | border-radius: config('border-radius-lg', $display);
113 | padding: spacer('m');
114 |
115 | &__link {
116 | color: color-value('heading');
117 | font-size: font-size('h3');
118 | font-weight: 700;
119 | text-decoration: none;
120 | }
121 | }
122 |
123 | .break-long-url {
124 | @include word-wrap;
125 | }
126 |
127 | .btn-group {
128 | display: flex;
129 | flex-wrap: wrap;
130 | gap: spacer('s');
131 | }
132 |
133 | .form-group {
134 | &--height-test {
135 | align-items: center;
136 | display: flex;
137 | gap: spacer('xs');
138 | }
139 | }
140 |
141 | .form-group--stacked\:md {
142 | @include form-group-stacked('md');
143 | }
144 |
145 | .ring-wrapper {
146 | align-items: center;
147 | display: flex;
148 | flex-wrap: wrap;
149 | gap: 1rem;
150 | }
151 |
152 | .ring-one,
153 | .ring-two {
154 | --size: 3rem;
155 | @include clear-btn;
156 | block-size: var(--size);
157 | inline-size: var(--size);
158 | }
159 |
160 | .ring-one {
161 | background-color: pink;
162 |
163 | &:focus-visible {
164 | @include short-ring('input');
165 | }
166 | }
167 |
168 | .ring-two {
169 | background-color: rebeccapurple;
170 |
171 | &:focus-visible {
172 | @include short-ring('button', 'secondary');
173 | }
174 | }
175 |
176 | .spacer-wrapper {
177 | align-items: center;
178 | display: flex;
179 | gap: spacer('m');
180 | }
181 |
182 | .spacer {
183 | background-color: red;
184 |
185 | div {
186 | background-color: pink;
187 | block-size: 2rem;
188 | inline-size: 5rem;
189 | }
190 |
191 | &--one {
192 | div {
193 | margin: spacer('m');
194 | }
195 | }
196 |
197 | &--two {
198 | div {
199 | margin: spacer('s');
200 | }
201 | }
202 |
203 | &--three {
204 | div {
205 | margin: spacer('xxl:xl');
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/preview/src/table.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Table"
3 | layout: "layout/base.html"
4 | ---
5 |
Regular
6 |
7 |
8 | This is a regular sized table.
9 |
10 |
11 | Person |
12 | Number |
13 | Third Column |
14 |
15 |
16 |
17 |
18 | Someone Lastname |
19 | 900 |
20 | Nullam quis risus eget urna mollis ornare vel eu leo. |
21 |
22 |
23 | Person Name |
24 | 1200 |
25 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla. |
26 |
27 |
28 | Another Person |
29 | 1500 |
30 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit. |
31 |
32 |
33 | Last One |
34 | 2800 |
35 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum. |
36 |
37 |
38 |
39 |
40 |
Rounded
41 |
42 |
43 | This is a regular sized, rounded, borderless table.
44 |
45 |
46 | Person |
47 | Number |
48 | Third Column |
49 |
50 |
51 |
52 |
53 | Someone Lastname |
54 | 900 |
55 | Nullam quis risus eget urna mollis ornare vel eu leo. |
56 |
57 |
58 | Person Name |
59 | 1200 |
60 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla. |
61 |
62 |
63 | Another Person |
64 | 1500 |
65 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit. |
66 |
67 |
68 | Last One |
69 | 2800 |
70 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum. |
71 |
72 |
73 |
74 |
75 |
Small
76 |
77 |
78 | This is a small sized table.
79 |
80 |
81 | Person |
82 | Number |
83 | Third Column |
84 |
85 |
86 |
87 |
88 | Someone Lastname |
89 | 900 |
90 | Nullam quis risus eget urna mollis ornare vel eu leo. |
91 |
92 |
93 | Person Name |
94 | 1200 |
95 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla. |
96 |
97 |
98 | Another Person |
99 | 1500 |
100 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit. |
101 |
102 |
103 | Last One |
104 | 2800 |
105 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum. |
106 |
107 |
108 |
109 |
110 |
In Line
111 |
112 |
113 |
114 |
115 | Person |
116 | Number |
117 | Third Column |
118 |
119 |
120 |
121 |
122 | Someone Lastname |
123 | 900 |
124 | Nullam quis risus eget urna mollis ornare vel eu leo. |
125 |
126 |
127 | Person Name |
128 | 1200 |
129 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla. |
130 |
131 |
132 | Another Person |
133 | 1500 |
134 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit. |
135 |
136 |
137 | Last One |
138 | 2800 |
139 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum. |
140 |
141 |
142 |
143 |
144 |
Classless
145 |
146 |
147 |
148 |
149 | Person |
150 | Number |
151 | Third Column |
152 |
153 |
154 |
155 |
156 | Someone Lastname |
157 | 900 |
158 | Nullam quis risus eget urna mollis ornare vel eu leo. |
159 |
160 |
161 | Person Name |
162 | 1200 |
163 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla. |
164 |
165 |
166 | Another Person |
167 | 1500 |
168 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit. |
169 |
170 |
171 | Last One |
172 | 2800 |
173 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum. |
174 |
175 |
176 |
177 |
178 |
--------------------------------------------------------------------------------
/preview/src/typography.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Typography"
3 | layout: "layout/base.html"
4 | ---
5 |
Morbi dui augue, consequat non pulvinar ac, consequat nec massa. Nulla nec purus vitae enim eleifend laoreet quis vitae nunc. Fusce lacinia nunc eget arcu pulvinar finibus. Nulla et egestas augue. Nulla at nunc vel massa ullamcorper posuere. Donec cursus venenatis dui sed aliquam. Curabitur ultrices, odio ac aliquam mollis, urna felis gravida dolor, id mattis ante mauris eu dui.
This is an anchor link
6 |
Custom heading size
7 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In aliquam nibh in facilisis vestibulum. Pellentesque bibendum lorem risus, ut viverra lectus blandit sit amet. Ut rhoncus a dui ac euismod.
8 |
The quick brown fox jumps over the lazy dog
9 |
The quick brown fox jumps over the lazy dog
10 |
The quick brown fox jumps over the lazy dog
11 |
The quick brown fox jumps over the lazy dog
12 |
The quick brown fox jumps over the lazy dog
13 |
The quick brown fox jumps over the lazy dog
14 |
Curabitur posuere placerat odio, a suscipit velit consectetur nec. Proin tincidunt gravida risus eu commodo. Vivamus tempus enim ac metus finibus vestibulum. Donec ac sagittis quam, in ullamcorper dolor. Vestibulum rhoncus tempor lacus in commodo. Morbi finibus sapien sed tortor interdum, vitae ornare mi accumsan. Vestibulum rutrum facilisis tincidunt.
15 |
16 |
Phasellus vel tortor mi. Vivamus bibendum erat CSS lacus, quis tincidunt urna dictum non. Fusce vel ex feugiat, faucibus lectus sit amet, accumsan lacus. Quisque cursus leo nunc, ut maximus arcu suscipit ut.
17 | Nulla laoreet felis mauris, quis Ctrl + S urna aliquet ac. Ut ultricies eros pharetra, elementum urna non, mollis eros. Proin viverra, ipsum a laoreet laoreet, nunc erat pulvinar quam, vel vehicula enim nibh non velit.
18 |
19 |
20 | Details
21 | Nulla et egestas augue. Nulla at nunc vel massa ullamcorper posuere. Donec cursus venenatis dui sed aliquam. Curabitur ultrices, odio ac aliquam mollis, urna felis gravida dolor, id mattis ante mauris eu dui.
22 |
23 |
Custom link, hey
24 |
1 Infinite Loop, CA 95014
United States
25 |
26 | “Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.”
27 | — Albert Einstein,
28 | Quote Investigator
29 |
30 |
31 |
32 | “Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.”
33 |
34 |
35 | - Definition List Title
36 | - Definition list division.
37 | - Kitchen Sink
38 | - Used in expressions to describe work in which all conceivable (and some inconceivable) sources have been mined. In this case, a bunch of markup.
39 | - aside
40 | - Defines content aside from the page content
41 | - blockquote
42 | - Defines a section that is quoted from another source
43 |
44 |
45 | - Unordered List item one
46 |
47 | - Nested list item
48 |
49 | - Level 3, item one
50 | - Level 3, item two
51 | - Level 3, item three
52 | - Level 3, item four
53 |
54 |
55 | - List item two
56 | - List item three
57 | - List item four
58 |
59 |
60 | - List item two
61 | - List item three
62 | - List item four
63 |
64 |
65 |
66 | - List item one
67 |
68 | - List item one
69 |
70 | - List item one
71 | - List item two
72 | - List item three
73 | - List item four
74 |
75 |
76 | - List item two
77 | - List item three
78 | - List item four
79 |
80 |
81 | - List item two
82 | - List item three
83 | - List item four
84 |
85 |
Heading level #1
86 |
This is a hidden paragraph.
87 |
--------------------------------------------------------------------------------
/scss/config/_breakpoint.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $breakpoints: () !default;
4 | $breakpoints: map.merge(
5 | (
6 | 'xs': 32em,
7 | 'sm': 48em,
8 | 'md': 64em,
9 | 'lg': 80em,
10 | 'xl': 90em,
11 | 'xxl': 110em,
12 | ),
13 | $breakpoints
14 | );
15 |
--------------------------------------------------------------------------------
/scss/config/_button.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'display' as *;
3 | @use 'typography' as *;
4 | @use 'spacer' as *;
5 |
6 | $btn: () !default;
7 | $btn: map.merge(
8 | (
9 | 'border-radius': map.get($display, 'border-radius-sm'),
10 | 'border-width': 1px,
11 | 'focus-ring-box-shadow-type': outside,
12 | 'focus-ring-offset': 2px,
13 | 'focus-ring-size': 2px,
14 | 'focus-ring-type': outline,
15 | 'font-family': null,
16 | 'font-size': map.get($typography, 'font-size-base'),
17 | 'font-style': null,
18 | 'font-weight': 500,
19 | 'gap': map.get($spacers, 'xs'),
20 | 'icon-padding': 0.75em,
21 | 'icon-size': 1em,
22 | 'padding': 0.75em 1em,
23 | 'shadow-size': 0.25rem,
24 | 'text-transform': null,
25 | ),
26 | $btn
27 | );
28 |
29 | $btn-lg: () !default;
30 | $btn-lg: map.merge(
31 | (
32 | 'font-size': 1.15rem,
33 | 'gap': map.get($spacers, 'xs'),
34 | 'icon-padding': 0.9em,
35 | 'padding': 0.9em 1.15em,
36 | ),
37 | $btn-lg
38 | );
39 |
40 | $btn-sm: () !default;
41 | $btn-sm: map.merge(
42 | (
43 | 'font-size': 0.8rem,
44 | 'gap': map.get($spacers, 'xxs'),
45 | 'icon-padding': 0.5em,
46 | 'icon-size': 0.8rem,
47 | 'padding': 0.5em 0.75em,
48 | ),
49 | $btn-sm
50 | );
51 |
--------------------------------------------------------------------------------
/scss/config/_color.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:map';
3 |
4 | $color-black: hsl(205deg 100% 2%) !default;
5 | $color-danger: hsl(0deg 71% 51%) !default;
6 | $color-gray: hsl(208deg 9% 42%) !default;
7 | $color-gray-light: hsl(215deg 63% 93%) !default;
8 | $color-primary: hsl(262deg 71% 49%) !default;
9 | $color-secondary: hsl(227deg 92% 55%) !default;
10 | $color-success: hsl(150deg 100% 33%) !default;
11 | $color-white: hsl(0deg 0% 100%) !default;
12 |
13 | $colors: () !default;
14 | $colors: map.deep-merge(
15 | (
16 | 'alert': (
17 | 'danger': $color-danger,
18 | 'info': hsl(195deg 100% 42%),
19 | 'success': $color-success,
20 | 'warning': hsl(48deg 89% 55%),
21 | ),
22 | 'base': (
23 | 'background': $color-white,
24 | 'blockquote-border': $color-primary,
25 | 'border': $color-gray-light,
26 | 'code-background': color.change($color-primary, $lightness: 97%),
27 | 'code-foreground': $color-black,
28 | 'heading': $color-black,
29 | 'link': $color-primary,
30 | 'link-hover': color.scale($color-primary, $lightness: -20%),
31 | 'mark-background': hsl(50deg 100% 80%),
32 | 'mark-foreground': $color-black,
33 | 'marker': $color-primary,
34 | 'primary': $color-primary,
35 | 'secondary': $color-secondary,
36 | 'strong': $color-black,
37 | 'text': $color-gray,
38 | ),
39 | 'btn': (
40 | 'primary-background': $color-primary,
41 | 'primary-background-hover': color.adjust($color-primary, $lightness: -10%),
42 | 'primary-foreground': $color-white,
43 | 'primary-shadow': color.adjust($color-primary, $lightness: 35%),
44 | 'secondary-background': $color-secondary,
45 | 'secondary-background-hover': color.adjust($color-secondary, $lightness: -10%),
46 | 'secondary-foreground': $color-white,
47 | 'secondary-shadow': color.adjust($color-secondary, $lightness: 35%),
48 | ),
49 | 'form': (
50 | 'background': $color-white,
51 | 'background-disabled': hsl(0deg 0% 95%),
52 | 'border': hsl(260deg 4% 70%),
53 | 'border-disabled': $color-gray-light,
54 | 'border-focus': $color-primary,
55 | 'check-background': $color-primary,
56 | 'check-focus-ring': $color-primary,
57 | 'check-foreground': $color-white,
58 | 'group-label-background': hsl(210deg 60% 98%),
59 | 'group-label-foreground': $color-gray,
60 | 'invalid': $color-danger,
61 | 'invalid-focus-ring': color.adjust($color-danger, $alpha: -0.75),
62 | 'label': $color-black,
63 | 'legend': $color-black,
64 | 'placeholder': hsl(208deg 7% 40%),
65 | 'range-thumb-background': $color-primary,
66 | 'range-thumb-focus-ring': $color-primary,
67 | 'range-track-background': $color-gray-light,
68 | 'ring-focus': color.adjust($color-primary, $alpha: -0.75),
69 | 'select-foreground': $color-black,
70 | 'switch-background': $color-primary,
71 | 'switch-focus-ring': $color-primary,
72 | 'switch-foreground': $color-white,
73 | 'text': $color-gray,
74 | 'valid': $color-success,
75 | 'valid-focus-ring': color.adjust($color-success, $alpha: -0.75),
76 | ),
77 | 'selection': (
78 | 'foreground': $color-white,
79 | 'background': $color-primary,
80 | ),
81 | 'scrollbar': (
82 | 'thumb-background': hsl(0deg 0% 0% / 15%),
83 | 'thumb-background-hover': hsl(0deg 0% 0% / 25%),
84 | 'track-background': hsl(0deg 0% 0% / 5%),
85 | ),
86 | 'table': (
87 | 'border': $color-gray-light,
88 | 'caption': $color-gray,
89 | 'heading': $color-black,
90 | 'hover': hsl(0deg 0% 0% / 5%),
91 | 'stripe': hsl(0deg 0% 0% / 2.5%),
92 | 'text': $color-gray,
93 | ),
94 | ),
95 | $colors
96 | );
97 |
98 | $dark-colors: () !default;
99 | $dark-colors: map.deep-merge(
100 | (),
101 | $dark-colors
102 | );
103 |
--------------------------------------------------------------------------------
/scss/config/_display.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $display: () !default;
4 | $display: map.merge(
5 | (
6 | 'border-radius-lg': 0.925rem,
7 | 'border-radius-sm': 0.425rem,
8 | ),
9 | $display
10 | );
11 |
--------------------------------------------------------------------------------
/scss/config/_escaping-characters.scss:
--------------------------------------------------------------------------------
1 | /// Characters to escape using SVG as data:image.
2 | $escaping-characters: (
3 | ('<', '%3c'),
4 | ('>', '%3e'),
5 | ('#', '%23'),
6 | ('(', '%28'),
7 | (')', '%29'),
8 | ) !default;
9 |
--------------------------------------------------------------------------------
/scss/config/_generator.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $generators: () !default;
4 | $generators: map.deep-merge(
5 | (
6 | 'content': (
7 | 'accessibility': true,
8 | 'default': true,
9 | 'display': true,
10 | 'divider': true,
11 | 'layout': true,
12 | 'media': true,
13 | 'normalize': true,
14 | 'print': true,
15 | 'root': true,
16 | 'table': true,
17 | 'typography': true,
18 | 'utilities': true,
19 | ),
20 | 'form': (
21 | 'btn': true,
22 | 'file-btn': true,
23 | 'form-check': true,
24 | 'form-control': true,
25 | 'form-description': true,
26 | 'form-feedback': true,
27 | 'form-fieldset': true,
28 | 'form-group-label': true,
29 | 'form-group': true,
30 | 'form-label': true,
31 | 'form-range': true,
32 | 'form-row': true,
33 | 'form-switch': true,
34 | ),
35 | ),
36 | $generators
37 | );
38 |
--------------------------------------------------------------------------------
/scss/config/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'color';
2 | @forward 'setting';
3 | @forward 'spacer';
4 | @forward 'display';
5 | @forward 'typography';
6 | @forward 'button';
7 | @forward 'form';
8 | @forward 'table';
9 | @forward 'layout';
10 | @forward 'transition';
11 | @forward 'breakpoint';
12 | @forward 'print';
13 | @forward 'escaping-characters';
14 | @forward 'generator';
15 |
--------------------------------------------------------------------------------
/scss/config/_layout.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $layout: () !default;
4 | $layout: map.merge(
5 | (
6 | 'container-inline-size': 84rem,
7 | ),
8 | $layout
9 | );
10 |
--------------------------------------------------------------------------------
/scss/config/_print.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $print: () !default;
4 | $print: map.merge(
5 | (
6 | 'page-margin': 2cm,
7 | 'hidden-elements': 'header, footer, aside, nav, form, iframe, [class^="aspect-ratio"]',
8 | ),
9 | $print
10 | );
11 |
--------------------------------------------------------------------------------
/scss/config/_setting.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $settings: () !default;
4 | $settings: map.deep-merge(
5 | (
6 | 'color-fallback': false,
7 | 'css-custom-properties': false,
8 | 'html-smooth-scrolling': true,
9 | 'hyphens': true,
10 | 'optimal-font-size': '2vw + 1rem',
11 | 'optimal-spacer-size': '5vw',
12 | 'prefix': 'spruce',
13 | 'print': false,
14 | 'scaler': 15,
15 | 'utilities': (
16 | 'display': true,
17 | 'typography': true,
18 | ),
19 | ),
20 | $settings
21 | );
22 |
23 | // We use this value to prefix our CSS variables. The only difference to the default prefix value that we add the '-' suffix.
24 | $internal-prefix: if(map.get($settings, prefix), map.get($settings, prefix) + '-', '');
25 |
--------------------------------------------------------------------------------
/scss/config/_spacer.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $spacer: 1rem !default;
4 |
5 | $spacers: () !default;
6 | $spacers: map.merge(
7 | (
8 | 'xxs': $spacer * 0.25,
9 | 'xs': $spacer * 0.5,
10 | 's': $spacer,
11 | 'm': $spacer * 1.5,
12 | 'l': $spacer * 3,
13 | 'xl': $spacer * 4.5,
14 | 'xxl': $spacer * 7,
15 | 'xxxl': $spacer * 10,
16 | ),
17 | $spacers
18 | );
19 |
--------------------------------------------------------------------------------
/scss/config/_table.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'typography' as *;
3 | @use 'spacer' as *;
4 |
5 | $table: () !default;
6 | $table: map.merge(
7 | (
8 | 'caption-font-size': null,
9 | 'caption-font-style': null,
10 | 'caption-font-weight': null,
11 | 'line-height': map.get($typography, 'line-height-md'),
12 | 'padding': map.get($spacers, 's'),
13 | 'responsive-inline-size': 40rem,
14 | 'stripe': odd,
15 | ),
16 | $table
17 | );
18 |
19 | $table-sm: () !default;
20 | $table-sm: map.merge(
21 | (
22 | 'padding': map.get($spacers, 'xs'),
23 | ),
24 | $table-sm
25 | );
26 |
--------------------------------------------------------------------------------
/scss/config/_transition.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $transition: () !default;
4 | $transition: map.merge(
5 | (
6 | 'duration': 0.15s,
7 | 'timing-function': ease-in-out,
8 | ),
9 | $transition
10 | );
11 |
--------------------------------------------------------------------------------
/scss/config/_typography.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'sass:math';
3 | @use 'display' as *;
4 |
5 | $typography: () !default;
6 | $typography: map.merge(
7 | (
8 | 'border-radius': map.get($display, 'border-radius-sm'),
9 | 'font-family-base': #{Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif},
10 | 'font-family-cursive': #{ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace},
11 | 'font-family-heading': #{Avenir, 'Avenir Next LT Pro', Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif},
12 | 'font-size-base': 1rem,
13 | 'font-size-lead': clamp(1.15rem, 2vw, 1.35rem),
14 | 'font-size-lg': 1.125rem,
15 | 'font-size-ratio': 1.25,
16 | 'font-size-sm': 0.875rem,
17 | 'font-weight-base': null,
18 | 'font-weight-heading': 700,
19 | 'inline-padding': 0.1em 0.3em,
20 | 'letter-spacing-heading': null,
21 | 'line-height-base': 1.8,
22 | 'line-height-heading': calc(2px + 2ex + 2px),
23 | 'line-height-lg': 1.8,
24 | 'line-height-md': 1.5,
25 | 'line-height-sm': 1.2,
26 | ),
27 | $typography
28 | );
29 |
30 | $font-sizes: () !default;
31 | $font-sizes: map.merge(
32 | (
33 | 'h1': math.pow(map.get($typography, 'font-size-ratio'), 4) * map.get($typography, 'font-size-base'),
34 | 'h2': math.pow(map.get($typography, 'font-size-ratio'), 3) * map.get($typography, 'font-size-base'),
35 | 'h3': math.pow(map.get($typography, 'font-size-ratio'), 2) * map.get($typography, 'font-size-base'),
36 | 'h4': math.pow(map.get($typography, 'font-size-ratio'), 1) * map.get($typography, 'font-size-base'),
37 | 'h5': map.get($typography, 'font-size-base'),
38 | 'h6': map.get($typography, 'font-size-base'),
39 | ),
40 | $font-sizes
41 | );
42 |
--------------------------------------------------------------------------------
/scss/config/form/_check.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'label' as *;
3 | @use '../display' as *;
4 | @use '../typography' as *;
5 |
6 | $form-check: () !default;
7 | $form-check: map.merge(
8 | (
9 | 'border-radius': map.get($display, border-radius-sm),
10 | 'border-width': 1px,
11 | 'focus-ring-box-shadow-type': outside,
12 | 'focus-ring-offset': 2px,
13 | 'focus-ring-size': 2px,
14 | 'focus-ring-type': outline,
15 | 'font-size': 1.125rem,
16 | 'font-weight': map.get($form-label, 'font-weight'),
17 | 'line-height': map.get($typography, 'line-height-md'),
18 | 'margin-block': 0.1em,
19 | 'vertical-alignment': center,
20 | ),
21 | $form-check
22 | );
23 |
24 | $form-check-lg: () !default;
25 | $form-check-lg: map.merge(
26 | (
27 | 'font-size': map.get($typography, 'size-lg'),
28 | ),
29 | $form-check-lg
30 | );
31 |
32 | $form-check-sm: () !default;
33 | $form-check-sm: map.merge(
34 | (
35 | 'font-size': map.get($typography, 'font-size-base'),
36 | ),
37 | $form-check-sm
38 | );
39 |
--------------------------------------------------------------------------------
/scss/config/form/_control.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../display' as *;
3 | @use '../typography' as *;
4 |
5 | $form-control: () !default;
6 | $form-control: map.merge(
7 | (
8 | 'border-radius': map.get($display, 'border-radius-sm'),
9 | 'border-width': 1px,
10 | 'focus-ring-box-shadow-type': outside,
11 | 'focus-ring-offset': 2px,
12 | 'focus-ring-size': 0.25rem,
13 | 'focus-ring-type': box-shadow,
14 | 'font-family': null,
15 | 'font-size': map.get($typography, 'font-size-base'),
16 | 'font-weight': null,
17 | 'line-height': 1.5,
18 | 'padding': 0.5em 0.75em,
19 | 'textarea-block-size': 6rem,
20 | ),
21 | $form-control
22 | );
23 |
24 | $form-control-lg: () !default;
25 | $form-control-lg: map.merge(
26 | (
27 | 'font-size': map.get($typography, 'size-lg'),
28 | 'padding': 0.65em 1em,
29 | ),
30 | $form-control-lg
31 | );
32 |
33 | $form-control-sm: () !default;
34 | $form-control-sm: map.merge(
35 | (
36 | 'border-radius': 0.35em,
37 | 'font-size': map.get($typography, 'size-sm'),
38 | 'padding': 0.25em 0.75em,
39 | ),
40 | $form-control-sm
41 | );
42 |
43 | $form-control-color: () !default;
44 | $form-control-color: map.merge(
45 | (
46 | 'aspect-ratio': 1,
47 | 'block-size': 100%,
48 | 'inline-size': 2.625rem,
49 | 'padding': 0.5em,
50 | ),
51 | $form-control-color
52 | );
53 |
54 | $form-control-color-lg: () !default;
55 | $form-control-color-lg: map.merge(
56 | (
57 | 'aspect-ratio': 1,
58 | 'block-size': 100%,
59 | 'inline-size': 3.204rem,
60 | 'padding': 0.5em,
61 | ),
62 | $form-control-color-lg
63 | );
64 |
65 | $form-control-color-sm: () !default;
66 | $form-control-color-sm: map.merge(
67 | (
68 | 'aspect-ratio': 1,
69 | 'block-size': 100%,
70 | 'inline-size': 1.925rem,
71 | 'padding': 0.25em,
72 | ),
73 | $form-control-color-sm
74 | );
75 |
--------------------------------------------------------------------------------
/scss/config/form/_description.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-description: () !default;
4 | $form-description: map.merge(
5 | (
6 | 'font-size': 1em,
7 | 'font-style': null,
8 | 'font-weight': 400,
9 | ),
10 | $form-description
11 | );
12 |
--------------------------------------------------------------------------------
/scss/config/form/_fieldset.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../spacer' as *;
3 | @use '../typography' as *;
4 |
5 | $form-fieldset: () !default;
6 | $form-fieldset: map.merge(
7 | (
8 | 'layout-gap': map.get($spacers, 's'),
9 | 'legend-font-family': null,
10 | 'legend-font-size': clamp(#{map.get($font-sizes, 'h5')}, 5vw, #{map.get($font-sizes, 'h4')}),
11 | 'legend-font-weight': 700,
12 | ),
13 | $form-fieldset
14 | );
15 |
--------------------------------------------------------------------------------
/scss/config/form/_file.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-file: () !default;
4 | $form-file: map.merge(
5 | (
6 | 'background': 'primary',
7 | ),
8 | $form-file
9 | );
10 |
--------------------------------------------------------------------------------
/scss/config/form/_group.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../spacer' as *;
3 |
4 | $form-group: () !default;
5 | $form-group: map.merge(
6 | (
7 | 'gap': map.get($spacers, 'xs'),
8 | ),
9 | $form-group
10 | );
11 |
12 | $form-group-check: () !default;
13 | $form-group-check: map.merge(
14 | (
15 | 'gap': map.get($spacers, 's'),
16 | ),
17 | $form-group-check
18 | );
19 |
20 | $form-group-row: () !default;
21 | $form-group-row: map.merge(
22 | (
23 | 'container-inline-size': 38rem,
24 | 'gap': map.get($spacers, 'xxs') map.get($spacers, 's'),
25 | 'label-inline-size': 10rem,
26 | 'vertical-alignment': center,
27 | ),
28 | $form-group-row
29 | );
30 |
--------------------------------------------------------------------------------
/scss/config/form/_icon.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-icon: () !default;
4 | $form-icon: map.merge(
5 | (
6 | 'checkbox-indeterminate': '
',
7 | 'checkbox': '
',
8 | 'invalid': '
',
9 | 'radio': '
',
10 | 'select': '
',
11 | 'switch': '
',
12 | 'valid': '
',
13 | ),
14 | $form-icon
15 | );
16 |
--------------------------------------------------------------------------------
/scss/config/form/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'label';
2 | @forward 'control';
3 | @forward 'description';
4 | @forward 'fieldset';
5 | @forward 'file';
6 | @forward 'icon';
7 | @forward 'range';
8 | @forward 'group';
9 | @forward 'row';
10 | @forward 'select';
11 | @forward 'switch';
12 | @forward 'check';
13 |
--------------------------------------------------------------------------------
/scss/config/form/_label.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-label: () !default;
4 | $form-label: map.merge(
5 | (
6 | 'font-family': null,
7 | 'font-size': null,
8 | 'font-style': null,
9 | 'font-weight': null,
10 | 'text-align': start,
11 | 'text-transform': null,
12 | ),
13 | $form-label
14 | );
15 |
--------------------------------------------------------------------------------
/scss/config/form/_range.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-range: () !default;
4 | $form-range: map.merge(
5 | (
6 | 'focus-ring-box-shadow-type': outside,
7 | 'focus-ring-offset': 2px,
8 | 'focus-ring-size': 2px,
9 | 'focus-ring-type': outline,
10 | 'thumb-block-size': 1rem,
11 | 'thumb-border-radius': 0.5rem,
12 | 'thumb-inline-size': 1rem,
13 | 'track-block-size': 0.25rem,
14 | 'track-border-radius': 0.15rem,
15 | ),
16 | $form-range
17 | );
18 |
--------------------------------------------------------------------------------
/scss/config/form/_row.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-row: () !default;
4 | $form-row: map.merge(
5 | (
6 | 'inline-size': 20ch,
7 | ),
8 | $form-row
9 | );
10 |
--------------------------------------------------------------------------------
/scss/config/form/_select.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-select: () !default;
4 | $form-select: map.merge(
5 | (
6 | 'icon-inline-size': 1.25em,
7 | 'icon-right-offset': 0.5em,
8 | 'padding-inline-end': calc(0.75em + 1.25em),
9 | ),
10 | $form-select
11 | );
12 |
--------------------------------------------------------------------------------
/scss/config/form/_switch.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../typography' as *;
3 | @use 'label' as *;
4 |
5 | $form-switch: () !default;
6 | $form-switch: map.merge(
7 | (
8 | 'border-width': 1px,
9 | 'font-size': 1.125rem,
10 | 'font-weight': map.get($form-label, 'font-weight'),
11 | 'line-height': map.get($typography, 'line-height-md'),
12 | 'margin-block': 0.15em,
13 | 'vertical-alignment': center,
14 | ),
15 | $form-switch
16 | );
17 |
18 | $form-switch-lg: () !default;
19 | $form-switch-lg: map.merge(
20 | (
21 | 'font-size': map.get($typography, 'font-size-lead'),
22 | ),
23 | $form-switch-lg
24 | );
25 |
26 |
27 | $form-switch-sm: () !default;
28 | $form-switch-sm: map.merge(
29 | (
30 | 'font-size': map.get($typography, 'font-size-base'),
31 | ),
32 | $form-switch-sm
33 | );
34 |
--------------------------------------------------------------------------------
/scss/element/_accessibility.scss:
--------------------------------------------------------------------------------
1 | @use '../mixin' as *;
2 |
3 | @mixin generate-accessibility {
4 | .sr-only {
5 | @include visually-hidden;
6 | }
7 |
8 | [tabindex='-1']:focus {
9 | outline: none !important;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/scss/element/_default.scss:
--------------------------------------------------------------------------------
1 | @use '../config' as *;
2 | @use '../function' as *;
3 |
4 | @mixin generate-default {
5 | ::selection {
6 | background-color: color('background', 'selection');
7 | color: color('foreground', 'selection');
8 | text-shadow: none;
9 | }
10 |
11 | html {
12 | box-sizing: border-box;
13 |
14 | @if setting('html-smooth-scrolling') {
15 | @media (prefers-reduced-motion: no-preference) {
16 | scroll-behavior: smooth;
17 | }
18 | }
19 | }
20 |
21 | *,
22 | ::before,
23 | ::after {
24 | box-sizing: inherit;
25 | }
26 |
27 | body {
28 | background: color('background');
29 | color: color('text');
30 | }
31 |
32 | a {
33 | color: color('link');
34 | text-decoration: underline;
35 | transition-duration: config('duration', $transition);
36 | transition-property: color;
37 | transition-timing-function: config('timing-function', $transition);
38 |
39 | &:hover {
40 | color: color('link-hover');
41 | }
42 | }
43 |
44 | button {
45 | color: inherit;
46 | }
47 |
48 | // Turn off double-tap on mobile to zoom
49 | a,
50 | button {
51 | touch-action: manipulation;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/scss/element/_divider.scss:
--------------------------------------------------------------------------------
1 | @use '../function' as *;
2 |
3 | @mixin generate-divider {
4 | hr {
5 | border: 0;
6 | border-block-start: 1px solid color('border');
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/scss/element/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'root';
2 | @forward 'accessibility';
3 | @forward 'default';
4 | @forward 'divider';
5 | @forward 'media';
6 | @forward 'table';
7 | @forward 'typography';
8 | @forward 'utilities';
9 |
--------------------------------------------------------------------------------
/scss/element/_media.scss:
--------------------------------------------------------------------------------
1 | @use '../function' as *;
2 | @use '../mixin' as *;
3 |
4 | @mixin generate-media {
5 | img {
6 | block-size: auto;
7 | display: block;
8 | max-inline-size: 100%;
9 | user-select: none;
10 | }
11 |
12 | iframe {
13 | block-size: 100%;
14 | display: block;
15 | inline-size: 100%;
16 | }
17 |
18 | figure {
19 | margin-inline: 0;
20 |
21 | figcaption {
22 | margin-block-start: spacer('xs');
23 | text-align: center;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/scss/element/_root.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 | @use '../config' as *;
5 |
6 | @mixin generate-root {
7 | @include generate-color-variables;
8 |
9 | :root {
10 | @if map.get($generators, 'content', 'typography') {
11 | @include generate-variables($typography);
12 | }
13 |
14 | @if map.get($generators, 'content', 'display') {
15 | @include generate-variables($display);
16 | }
17 |
18 | @if map.get($generators, 'content', 'layout') {
19 | @include generate-variables($layout);
20 | }
21 |
22 | @if map.get($generators, 'content', 'print') {
23 | @include generate-variables($print);
24 | }
25 |
26 | @if setting('css-custom-properties') {
27 | @media (prefers-reduced-motion: reduce) {
28 | --#{$internal-prefix}duration: 0;
29 | }
30 |
31 | @media (prefers-reduced-motion: no-preference) {
32 | --#{$internal-prefix}duration: #{config('duration', $transition, false)};
33 | --#{$internal-prefix}timing-function: #{config('timing-function', $transition, false)};
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/scss/element/_table.scss:
--------------------------------------------------------------------------------
1 | @use '../config' as *;
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 |
5 | @mixin generate-table(
6 | $selector: '.table',
7 | $has-variations: true,
8 | $has-responsive-table: true
9 | ) {
10 | @if ($has-responsive-table) {
11 | .table-responsive {
12 | --inline-size: #{config('responsive-inline-size', $table, false)};
13 | -webkit-overflow-scrolling: touch;
14 | overflow-x: auto;
15 |
16 | table {
17 | min-inline-size: var(--inline-size);
18 | }
19 | }
20 | }
21 |
22 | #{$selector} {
23 | @include generate-variables($table, ('stripe'));
24 |
25 | border-collapse: collapse;
26 | color: color('text', 'table');
27 | inline-size: 100%;
28 |
29 | caption {
30 | color: color(caption, table);
31 | font-size: config('caption-font-size', $table);
32 | font-style: config('caption-font-style', $table);
33 | font-weight: config('caption-font-weight', $table);
34 | margin-block-end: spacer('s');
35 | }
36 |
37 | th,
38 | td {
39 | border-block-end: 1px solid color('border', 'table');
40 | line-height: config('line-height', $table);
41 | padding: config('padding', $table);
42 | }
43 |
44 | th {
45 | color: color('heading', 'table');
46 | text-align: inherit;
47 | text-align: -webkit-match-parent;
48 | }
49 |
50 | @if ($has-variations) {
51 | &--striped {
52 | > tbody > tr:nth-child(#{config('stripe', $table, false)}) {
53 | background-color: color('stripe', 'table');
54 | }
55 | }
56 |
57 | &--hover {
58 | > tbody > tr:hover {
59 | background: color('hover', 'table');
60 | }
61 | }
62 |
63 | &--clear-border {
64 | th,
65 | td {
66 | border: 0;
67 | }
68 | }
69 |
70 | &--in-line {
71 | th:first-child,
72 | td:first-child {
73 | padding-inline-start: 0;
74 | }
75 |
76 | th:last-child,
77 | td:last-child {
78 | padding-inline-end: 0;
79 | }
80 | }
81 |
82 | &--sm {
83 | @include generate-variables($table-sm);
84 |
85 | th,
86 | td {
87 | padding: config('padding', $table-sm);
88 | }
89 | }
90 |
91 | &--rounded {
92 | th,
93 | td {
94 | &:first-child {
95 | border-end-start-radius: config('border-radius-sm', $display);
96 | border-start-start-radius: config('border-radius-sm', $display);
97 | }
98 |
99 | &:last-child {
100 | border-end-end-radius: config('border-radius-sm', $display);
101 | border-start-end-radius: config('border-radius-sm', $display);
102 | }
103 | }
104 | }
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/scss/element/_typography.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 | @use '../config' as *;
5 |
6 | @mixin generate-typography {
7 | html {
8 | -webkit-tap-highlight-color: hsl(0deg 0% 0% / 0%);
9 | }
10 |
11 | body {
12 | font-family: config('font-family-base', $typography);
13 | font-size: config('font-size-base', $typography);
14 | font-weight: config('font-weight-base', $typography);
15 | line-height: config('line-height-base', $typography);
16 | }
17 |
18 | @if setting('hyphens') {
19 | p,
20 | li,
21 | h1,
22 | h2,
23 | h3,
24 | h4,
25 | h5,
26 | h6 {
27 | hyphens: auto;
28 | overflow-wrap: break-word;
29 | }
30 | }
31 |
32 | h1,
33 | h2,
34 | h3,
35 | h4,
36 | h5,
37 | h6 {
38 | color: color('heading');
39 | font-family: config('font-family-heading', $typography);
40 | font-weight: config('font-weight-heading', $typography);
41 | letter-spacing: config('letter-spacing-heading', $typography);
42 | line-height: config('line-height-heading', $typography);
43 | }
44 |
45 | h1 {
46 | font-size: font-size('h1');
47 | }
48 |
49 | h2 {
50 | font-size: font-size('h2');
51 | }
52 |
53 | h3 {
54 | font-size: font-size('h3');
55 | }
56 |
57 | h4 {
58 | font-size: font-size('h4');
59 | }
60 |
61 | h5 {
62 | font-size: font-size('h5');
63 | }
64 |
65 | h6 {
66 | font-size: font-size('h6');
67 | }
68 |
69 | ul,
70 | ol {
71 | list-style-position: inside;
72 | @include layout-stack('xxs');
73 |
74 | li {
75 | list-style-position: outside;
76 |
77 | &::marker {
78 | color: color('marker');
79 | }
80 | }
81 | }
82 |
83 | li > ul,
84 | li > ol {
85 | margin-block-start: spacer('xxs');
86 | }
87 |
88 | dl {
89 | dt {
90 | color: color('heading');
91 | font-weight: bold;
92 | }
93 |
94 | dd {
95 | margin: 0;
96 | }
97 |
98 | dd + dt {
99 | margin-block-start: spacer('s');
100 | }
101 | }
102 |
103 | .quote {
104 | border-inline-start: 0.5rem solid color('blockquote-border');
105 | padding-inline-start: spacer('m');
106 | @include layout-stack('xs');
107 |
108 | blockquote {
109 | border-inline-start: 0;
110 | padding-inline-start: 0;
111 | }
112 |
113 | figcaption {
114 | text-align: start;
115 | }
116 | }
117 |
118 | blockquote {
119 | border-inline-start: 0.5rem solid color('blockquote-border');
120 | margin-inline-start: 0;
121 | padding-inline-start: spacer('m');
122 | @include layout-stack('xs');
123 | }
124 |
125 | abbr[title] {
126 | border-block-end: 1px dotted;
127 | cursor: help;
128 | text-decoration: none;
129 | }
130 |
131 | mark {
132 | background-color: color('mark-background');
133 | border-radius: config('border-radius', $typography);
134 | color: color('mark-foreground');
135 | padding: config('inline-padding', $typography);
136 | }
137 |
138 | code,
139 | kbd,
140 | samp {
141 | background-color: color('code-background');
142 | border-radius: config('border-radius', $typography);
143 | color: color('code-foreground');
144 | padding: config('inline-padding', $typography);
145 | }
146 |
147 | strong {
148 | color: color('strong');
149 | }
150 |
151 | .lead {
152 | font-size: config('font-size-lead', $typography);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/scss/element/_utilities.scss:
--------------------------------------------------------------------------------
1 | @use '../function' as *;
2 |
3 | @mixin generate-utilities {
4 | @if setting('display', 'utilities') == true {
5 | .hidden,
6 | [hidden] {
7 | display: none !important;
8 | }
9 | }
10 |
11 | @if setting('typography', 'utilities') == true {
12 | .h1 {
13 | font-size: font-size('h1');
14 | }
15 |
16 | .h2 {
17 | font-size: font-size('h2');
18 | }
19 |
20 | .h3 {
21 | font-size: font-size('h3');
22 | }
23 |
24 | .h4 {
25 | font-size: font-size('h4');
26 | }
27 |
28 | .h5 {
29 | font-size: font-size('h5');
30 | }
31 |
32 | .h6 {
33 | font-size: font-size('h6');
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/scss/form/_button.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 | @use '../config' as *;
5 |
6 | @mixin generate-btn(
7 | $selector,
8 | $pseudo-selector: null,
9 | $has-icons: true,
10 | $has-sizes: true,
11 | ) {
12 | #{$selector}#{$pseudo-selector} {
13 | @include generate-variables($btn, ('focus-'));
14 |
15 | align-items: center;
16 | border-radius: config('border-radius', $btn);
17 | border-style: solid;
18 | border-width: config('border-width', $btn);
19 | cursor: pointer;
20 | display: inline-flex;
21 | font-family: config('font-family', $btn);
22 | font-size: config('font-size', $btn);
23 | font-style: config('font-style', $btn);
24 | font-weight: config('font-weight', $btn);
25 | gap: config('gap', $btn);
26 | justify-content: center;
27 | line-height: 1;
28 | padding: config('padding', $btn);
29 | text-align: start;
30 | text-decoration: none;
31 | text-transform: config('text-transform', $btn);
32 | transition-duration: config('duration', $transition);
33 | transition-property: background-color, border-color, box-shadow, color;
34 | transition-timing-function: config('timing-function', $transition);
35 | }
36 |
37 | #{$selector}:focus {
38 | outline-color: transparent;
39 | outline-style: solid;
40 | }
41 |
42 | #{$selector}:disabled {
43 | opacity: 0.5;
44 | pointer-events: none;
45 | }
46 |
47 | @if ($has-icons) {
48 | #{$selector}--icon {
49 | padding: config('icon-padding', $btn);
50 |
51 | {$selector}--sm {
52 | padding: config('icon-padding', $btn-sm);
53 | }
54 |
55 | {$selector}--lg {
56 | padding: config('icon-padding', $btn-lg);
57 | }
58 | }
59 |
60 | #{$selector}__icon {
61 | block-size: config('icon-size', $btn);
62 | flex-shrink: 0;
63 | inline-size: config('icon-size', $btn);
64 | pointer-events: none;
65 |
66 | &--sm {
67 | block-size: config('icon-size', $btn-sm);
68 | inline-size: config('icon-size', $btn-sm);
69 | }
70 | }
71 | }
72 |
73 | @if ($has-sizes) {
74 | // Sizes
75 | #{$selector}--sm#{$pseudo-selector} {
76 | @include generate-variables($btn-sm);
77 |
78 | font-size: config('font-size', $btn-sm);
79 | gap: config('gap', $btn-sm);
80 | padding: config('padding', $btn-sm);
81 | }
82 |
83 | #{$selector}--lg#{$pseudo-selector} {
84 | @include generate-variables($btn-lg);
85 |
86 | @if not map.get($settings, 'css-custom-properties') {
87 | gap: config('gap', $btn-lg);
88 | padding: config('padding', $btn-lg);
89 |
90 | @include breakpoint(md) {
91 | font-size: config('font-size', $btn-lg);
92 | }
93 | }
94 | }
95 |
96 | #{$selector}--block#{$pseudo-selector} {
97 | inline-size: 100%;
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/scss/form/_check.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../function' as *;
4 | @use '../mixin' as *;
5 |
6 | // Create custom checkbox and radio
7 | @mixin generate-form-check(
8 | $parent,
9 | $input,
10 | $label,
11 | $has-sizes: false
12 | ) {
13 | #{$parent} {
14 | @include generate-variables($form-check, ('focus-'));
15 |
16 | align-items: config('vertical-alignment', $form-check);
17 | display: inline-flex;
18 | gap: spacer('xs');
19 | }
20 |
21 | #{$parent}--vertical-center {
22 | align-items: center;
23 | }
24 |
25 | #{$parent}--vertical-start {
26 | align-items: flex-start;
27 | }
28 |
29 | @if ($has-sizes) {
30 | #{$parent}--sm {
31 | @include generate-variables($form-control-sm);
32 |
33 | #{$input} {
34 | font-size: config('font-size', $form-check-sm);
35 | }
36 | }
37 |
38 | #{$parent}--lg {
39 | @include generate-variables($form-control-lg);
40 |
41 | #{$input} {
42 | font-size: config('font-size', $form-check-lg);
43 | }
44 | }
45 | }
46 |
47 | @at-root {
48 | #{$input} {
49 | appearance: none;
50 | background-color: color('background', 'form');
51 | background-position: center;
52 | background-repeat: no-repeat;
53 | background-size: contain;
54 | block-size: 1em;
55 | border: config('border-width', $form-check) solid color('border', 'form');
56 | flex-shrink: 0;
57 | font-size: config('font-size', $form-check);
58 | font-weight: config('font-weight', $form-check);
59 | inline-size: 1em;
60 | line-height: 1;
61 | margin-block: config('margin-block', $form-check);
62 | transition-duration: config('duration', $transition);
63 | transition-property: border, box-shadow;
64 | transition-timing-function: config('timing-function', $transition);
65 |
66 | &[type='radio'] {
67 | border-radius: 50%;
68 | }
69 |
70 | &[type='checkbox'] {
71 | border-radius: config('border-radius', $form-check);
72 | }
73 |
74 | &:focus-visible {
75 | @include focus-ring(
76 | $type: config('focus-ring-type', $form-check, false),
77 | $border-color: color('check-background', 'form'),
78 | $ring-color: color('check-focus-ring', 'form'),
79 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-check, false),
80 | $ring-size: config('focus-ring-size', $form-check, false),
81 | $ring-offset: config('focus-ring-offset', $form-check, false)
82 | );
83 | }
84 |
85 | &:checked {
86 | background-color: color('check-background', 'form');
87 | border-color: color('check-background', 'form');
88 |
89 | &[type='radio'] {
90 | @include field-icon(config('radio', $form-icon, false), color('check-foreground', 'form', true));
91 | }
92 |
93 | &[type='checkbox'] {
94 | @include field-icon(config('checkbox', $form-icon, false), color('check-foreground', 'form', true));
95 | }
96 | }
97 |
98 | &:indeterminate {
99 | &[type='checkbox'] {
100 | @include field-icon(config('checkbox-indeterminate', $form-icon, false), color('check-foreground', 'form', true));
101 | background-color: color('check-background', 'form');
102 | border-color: color('check-background', 'form');
103 | }
104 | }
105 |
106 | &:disabled,
107 | &.disabled {
108 | @include field-disabled(
109 | $background: color('background-disabled', 'form'),
110 | $border: color('border-disabled', 'form')
111 | );
112 |
113 | + #{$label} {
114 | opacity: 0.5;
115 | }
116 | }
117 | }
118 |
119 | #{$label} {
120 | font-weight: config('font-weight', $form-check);
121 | line-height: config('line-height', $form-check);
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/scss/form/_control.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 | @use '../config' as *;
5 |
6 | @mixin generate-form-control(
7 | $selector,
8 | $has-states: false,
9 | $has-sizes: false,
10 | $has-select: true
11 | ) {
12 | #{$selector} {
13 | --webkit-date-line-height: 1.375;
14 | @include generate-variables($form-control, ('focus-'));
15 |
16 | appearance: none;
17 | background-color: color('background', 'form');
18 | border: config('border-width', $form-control) solid color('border', 'form');
19 | border-radius: config('border-radius', $form-control);
20 | box-sizing: border-box;
21 | color: color('text', 'form');
22 | display: block;
23 | font-family: config('font-family', $form-control);
24 | font-size: config('font-size', $form-control);
25 | font-weight: config('font-weight', $form-control);
26 | inline-size: 100%;
27 | line-height: config('line-height', $form-control);
28 | padding: config('padding', $form-control);
29 | transition-duration: config('duration', $transition);
30 | transition-property: border, box-shadow;
31 | transition-timing-function: config('timing-function', $transition);
32 |
33 | &::placeholder {
34 | color: color('placeholder', 'form');
35 | };
36 |
37 | &::-webkit-datetime-edit {
38 | line-height: var(--webkit-date-line-height);
39 | }
40 |
41 | &:focus {
42 | @include focus-ring(
43 | $type: config('focus-ring-type', $form-control, false),
44 | $border-color: color('border-focus', 'form'),
45 | $ring-color: color('ring-focus', 'form'),
46 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-control, false),
47 | $ring-size: config('focus-ring-size', $form-control, false),
48 | $ring-offset: config('focus-ring-offset', $form-control, false)
49 | );
50 | }
51 |
52 | &[type='color'] {
53 | @include generate-variables($form-control-color);
54 | aspect-ratio: config('aspect-ratio', $form-control-color);
55 | block-size: config('block-size', $form-control-color);
56 | inline-size: config('inline-size', $form-control-color);
57 | padding: config('padding', $form-control-color);
58 |
59 | &::-webkit-color-swatch-wrapper {
60 | padding: 0;
61 | }
62 |
63 | &::-moz-color-swatch {
64 | border: 0;
65 | border-radius: config('border-radius', $form-control);
66 | }
67 |
68 | &::-webkit-color-swatch {
69 | border: 0;
70 | border-radius: config('border-radius', $form-control);
71 | }
72 | }
73 |
74 | &[disabled],
75 | &[disabled='true'] {
76 | @include field-disabled(
77 | $background: color('background-disabled', 'form'),
78 | $border: color('border-disabled', 'form')
79 | );
80 | }
81 |
82 | @at-root {
83 | textarea#{$selector} {
84 | block-size: config('textarea-block-size', $form-control);
85 | min-block-size: config('textarea-block-size', $form-control);
86 | resize: vertical;
87 | }
88 | }
89 |
90 | @if ($has-states) {
91 | &--valid,
92 | &--invalid {
93 | background-position: center right config('icon-right-offset', $form-select, false);
94 | background-repeat: no-repeat;
95 | background-size: config('icon-inline-size', $form-select, false) auto;
96 | padding-inline-end: config('padding-inline-end', $form-select, false);
97 |
98 | html[dir='rtl'] & {
99 | background-position: center left config('icon-right-offset', $form-select, false);
100 | }
101 | }
102 |
103 | &--valid {
104 | @include field-icon(config('valid', $form-icon, false), color('success', 'alert', true));
105 | border-color: color('success', 'alert');
106 |
107 | &:focus {
108 | @include focus-ring(
109 | $type: config('focus-ring-type', $form-control, false),
110 | $border-color: color('valid', 'form'),
111 | $ring-color: color('valid-focus-ring', 'form', false),
112 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-control),
113 | $ring-size: config('focus-ring-size', $form-control, false),
114 | $ring-offset: config('focus-ring-offset', $form-control, false)
115 | );
116 | }
117 | }
118 |
119 | &--invalid {
120 | @include field-icon(config('invalid', $form-icon, false), color('danger', 'alert', true));
121 | border-color: color('danger', 'alert');
122 |
123 | &:focus {
124 | @include focus-ring(
125 | $type: config('focus-ring-type', $form-control, false),
126 | $border-color: color('invalid', 'form'),
127 | $ring-color: color('invalid-focus-ring', 'form'),
128 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-control, false),
129 | $ring-size: config('focus-ring-size', $form-control, false),
130 | $ring-offset: config('focus-ring-offset', $form-control, false)
131 | );
132 | }
133 | }
134 | }
135 |
136 | @if ($has-sizes) {
137 | &--sm {
138 | --webkit-date-line-height: 1.36;
139 | @include generate-variables($form-control-sm);
140 |
141 | &[type='color'] {
142 | @include generate-variables($form-control-color-sm);
143 | }
144 |
145 | @if not map.get($settings, 'css-custom-properties') {
146 | font-size: config('font-size', $form-control-sm);
147 | padding: config('padding', $form-control-sm);
148 |
149 | &[type='color'] {
150 | aspect-ratio: config('aspect-ratio', $form-control-color-sm);
151 | block-size: config('block-size', $form-control-color-sm);
152 | inline-size: config('inline-size', $form-control-color-sm);
153 | padding: config('padding', $form-control-color-sm);
154 | }
155 | }
156 | }
157 |
158 | &--lg {
159 | --webkit-date-line-height: 1.387;
160 | @include generate-variables($form-control-lg);
161 |
162 | &[type='color'] {
163 | @include generate-variables($form-control-color-lg);
164 | }
165 |
166 | @if not map.get($settings, 'css-custom-properties') {
167 | font-size: config('font-size', $form-control-lg);
168 | padding: config('padding', $form-control-lg);
169 |
170 | &[type='color'] {
171 | aspect-ratio: config('aspect-ratio', $form-control-color-lg);
172 | height: config('block-size', $form-control-color-lg);
173 | inline-size: config('inline-size', $form-control-color-lg);
174 | padding: config('padding', $form-control-color-lg);
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
181 | @if ($has-select) {
182 | select#{$selector} {
183 | &:not([multiple]):not([size]) {
184 | @include field-icon(config('select', $form-icon, false), color('select-foreground', 'form', true));
185 | background-position: center right config('icon-right-offset', $form-select, false);
186 | background-repeat: no-repeat;
187 | background-size: config('icon-inline-size', $form-select, false) auto;
188 | padding-inline-end: config('padding-inline-end', $form-select, false);
189 |
190 | html[dir='rtl'] & {
191 | background-position: center left config('icon-right-offset', $form-select, false);
192 | }
193 | }
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/scss/form/_description.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 | @use '../config' as *;
5 |
6 | @mixin generate-form-description {
7 | .form-description {
8 | @include generate-variables($form-description);
9 |
10 | color: color('text', 'form');
11 | display: block;
12 | font-size: config('font-size', $form-description);
13 | font-style: config('font-style', $form-description);
14 | font-weight: config('font-weight', $form-description);
15 | line-height: config('line-height-md', $typography);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/scss/form/_fieldset.scss:
--------------------------------------------------------------------------------
1 | @use '../function' as *;
2 | @use '../mixin' as *;
3 | @use '../config' as *;
4 |
5 | @mixin generate-form-fieldset {
6 | fieldset {
7 | border: 0;
8 | margin: 0;
9 | padding: 0;
10 |
11 | @include generate-variables($form-fieldset);
12 | @include layout-stack(config('layout-gap', $form-fieldset));
13 |
14 | + fieldset {
15 | margin-block-start: spacer('l');
16 | }
17 | }
18 |
19 | legend {
20 | color: color('legend', 'form');
21 | font-family: config('legend-font-family', $form-fieldset);
22 | font-size: config('legend-font-size', $form-fieldset);
23 | font-weight: config('legend-font-weight', $form-fieldset);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/scss/form/_file.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 | @use '../config' as *;
5 | @use 'button' as *;
6 |
7 | @mixin generate-file-btn(
8 | $selector,
9 | $pseudo-selector: null,
10 | $has-icons: true,
11 | $has-sizes: true,
12 | ) {
13 | @include generate-btn($selector, $pseudo-selector, $has-icons, $has-sizes);
14 |
15 | #{$selector} {
16 | display: block;
17 |
18 | &:focus {
19 | outline: revert;
20 | }
21 |
22 | &:focus-within#{$pseudo-selector} {
23 | background-color: color(config('background', $form-file, false) + '-background-hover', btn);
24 | }
25 |
26 | {$pseudo-selector} {
27 | margin-inline-end: spacer('s');
28 | @include btn-variant(config('background', $form-file, false), false);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/scss/form/_group-label.scss:
--------------------------------------------------------------------------------
1 | @use '../function' as *;
2 | @use '../config' as *;
3 | @use '../mixin' as *;
4 |
5 | @mixin generate-form-group-label {
6 | .form-group-label {
7 | @include generate-variables($form-control, $include: ('border-width', 'border-radius'));
8 | align-items: center;
9 | background-color: color('group-label-background', 'form');
10 | border: config('border-width', $form-control) solid color('border', 'form');
11 | border-radius: config('border-radius', $form-control);
12 | color: color('group-label-foreground', 'form');
13 | display: flex;
14 | padding-inline: spacer('s');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/scss/form/_group.scss:
--------------------------------------------------------------------------------
1 | @use '../function' as *;
2 | @use '../mixin' as *;
3 | @use '../config' as *;
4 |
5 | @mixin generate-form-group {
6 | .form-group {
7 | @include generate-variables($form-group);
8 |
9 | display: flex;
10 | flex-direction: column;
11 | gap: config('gap', $form-group);
12 |
13 | &--horizontal-check {
14 | @include generate-variables($form-group-check);
15 | display: flex;
16 | flex-direction: row;
17 | flex-wrap: wrap;
18 | gap: config('gap', $form-group-check);
19 | }
20 |
21 | &--vertical-check {
22 | @include generate-variables($form-group-check);
23 | align-items: start;
24 | flex-direction: column;
25 | gap: config('gap', $form-group-check);
26 | }
27 |
28 | &--row {
29 | @include generate-variables($form-group-row);
30 |
31 | align-items: config('vertical-alignment', $form-group-row);
32 | display: grid;
33 | gap: config('gap', $form-group-row);
34 | grid-template-columns: minmax(0, 1fr);
35 |
36 | &\:vertical-center {
37 | align-items: center;
38 | }
39 |
40 | &\:vertical-start {
41 | align-items: flex-start;
42 | }
43 |
44 | @container form-group-container (inline-size > #{config('container-inline-size', $form-group-row, false)}) {
45 | grid-template-columns: minmax(0, #{config('label-inline-size', $form-group-row)}) minmax(0, 1fr);
46 | }
47 |
48 | .form-description,
49 | .field-feedback {
50 | @container form-group-container (inline-size > #{config('container-inline-size', $form-group-row, false)}) {
51 | grid-column-start: 2;
52 | }
53 | }
54 | }
55 |
56 | &--stacked {
57 | display: flex;
58 |
59 | > * {
60 | + * {
61 | border-radius: 0;
62 | margin-inline-start: -1px;
63 | }
64 |
65 | // stylelint-disable
66 | &:first-child {
67 | border-start-end-radius: 0;
68 | border-start-start-radius: config('border-radius', $form-control);
69 | border-end-end-radius: 0;
70 | border-end-start-radius: config('border-radius', $form-control);
71 | }
72 |
73 | &:last-child {
74 | border-start-end-radius: config('border-radius', $form-control);
75 | border-start-start-radius: 0;
76 | border-end-end-radius: config('border-radius', $form-control);
77 | border-end-start-radius: 0;
78 | }
79 |
80 | &:only-child {
81 | border-radius: config('border-radius', $form-control);
82 | }
83 | // stylelint-enable
84 |
85 | &:focus {
86 | z-index: 2;
87 | }
88 | }
89 | }
90 |
91 | &-container {
92 | container: form-group-container / inline-size;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/scss/form/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'button';
2 | @forward 'fieldset';
3 | @forward 'label';
4 | @forward 'control';
5 | @forward 'description';
6 | @forward 'group-label';
7 | @forward 'group';
8 | @forward 'row';
9 | @forward 'check';
10 | @forward 'switch';
11 | @forward 'file';
12 | @forward 'range';
13 | @forward 'validation';
14 |
--------------------------------------------------------------------------------
/scss/form/_label.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../function' as *;
4 |
5 | @mixin generate-form-label {
6 | .form-label {
7 | color: color('label', 'form');
8 | font-family: map.get($form-label, 'font-family');
9 | font-size: map.get($form-label, 'font-size');
10 | font-style: map.get($form-label, 'font-style');
11 | font-weight: map.get($form-label, 'font-weight');
12 | line-height: map.get($typography, 'line-height-md');
13 | text-align: map.get($form-label, 'text-align');
14 | text-transform: map.get($form-label, 'text-transform');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/scss/form/_range.scss:
--------------------------------------------------------------------------------
1 | @use '../config' as *;
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 |
5 | @mixin generate-form-range {
6 | .form-range {
7 | @include generate-variables($form-range);
8 | appearance: none;
9 | margin-block-start: calc(#{config('thumb-block-size', $form-range)} / 2 - #{config('track-block-size', $form-range)} / 2);
10 |
11 | &:focus-visible {
12 | outline: none;
13 |
14 | &::-webkit-slider-thumb {
15 | @include focus-ring(
16 | $type: config('focus-ring-type', $form-range, false),
17 | $border-color: null,
18 | $ring-color: color('range-thumb-focus-ring', 'form'),
19 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-range, false),
20 | $ring-size: config('focus-ring-size', $form-range, false),
21 | $ring-offset: config('focus-ring-offset', $form-range, false)
22 | );
23 | }
24 |
25 | &::-moz-range-thumb {
26 | @include focus-ring(
27 | $type: config('focus-ring-type', $form-range, false),
28 | $border-color: null,
29 | $ring-color: color('range-thumb-focus-ring', 'form'),
30 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-range, false),
31 | $ring-size: config('focus-ring-size', $form-range, false),
32 | $ring-offset: config('focus-ring-offset', $form-range, false)
33 | );
34 | }
35 | }
36 |
37 | &::-webkit-slider-runnable-track {
38 | background-color: color('range-track-background', 'form');
39 | block-size: config('track-block-size', $form-range);
40 | border-radius: config('track-border-radius', $form-range);
41 | }
42 |
43 | &::-moz-range-track {
44 | background-color: color('range-track-background', 'form');
45 | block-size: config('track-block-size', $form-range);
46 | border-radius: config('track-border-radius', $form-range);
47 | }
48 |
49 | &::-webkit-slider-thumb {
50 | appearance: none;
51 | background-color: color('range-thumb-background', 'form');
52 | block-size: config('thumb-block-size', $form-range);
53 | border-radius: config('thumb-border-radius', $form-range);
54 | inline-size: config('thumb-inline-size', $form-range);
55 | margin-block-start: calc(#{config('track-block-size', $form-range)} / 2 - #{config('thumb-block-size', $form-range)} / 2);
56 | }
57 |
58 | &::-moz-range-thumb {
59 | background-color: color('range-thumb-background', 'form');
60 | block-size: config('thumb-block-size', $form-range);
61 | border: 0; /*Removes extra border that FF applies*/
62 | border-radius: config('thumb-border-radius', $form-range);
63 | inline-size: config('thumb-inline-size', $form-range);
64 | }
65 |
66 | &:disabled {
67 | cursor: not-allowed;
68 | opacity: 0.5;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/scss/form/_row.scss:
--------------------------------------------------------------------------------
1 | @use '../function' as *;
2 | @use '../mixin' as *;
3 | @use '../config' as *;
4 |
5 | @mixin generate-form-row {
6 | .form-row {
7 | &--mixed {
8 | --inline-size: #{config('inline-size', $form-row, false)};
9 | @include layout-flex('s');
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/scss/form/_switch.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../function' as *;
4 | @use '../mixin' as *;
5 |
6 | @mixin generate-form-switch(
7 | $parent,
8 | $input,
9 | $label,
10 | $has-sizes: false
11 | ) {
12 | #{$parent} {
13 | @include generate-variables($form-switch, ('focus-'));
14 | align-items: config('vertical-alignment', $form-switch);
15 | display: inline-flex;
16 | gap: spacer('xs');
17 |
18 | &--block {
19 | inline-size: 100%;
20 | justify-content: space-between;
21 | }
22 | }
23 |
24 | #{$parent}--vertical-center {
25 | align-items: center;
26 | }
27 |
28 | #{$parent}--vertical-start {
29 | align-items: flex-start;
30 | }
31 |
32 | @if ($has-sizes) {
33 | #{$parent}--sm {
34 | @include generate-variables($form-switch-sm);
35 |
36 | @if not map.get($settings, 'css-custom-properties') {
37 | #{$input} {
38 | font-size: config('font-size', $form-switch-sm);
39 | }
40 | }
41 | }
42 |
43 | #{$parent}--lg {
44 | @include generate-variables($form-switch-lg);
45 |
46 | @if not map.get($settings, 'css-custom-properties') {
47 | #{$input} {
48 | font-size: config('font-size', $form-switch-lg);
49 | }
50 | }
51 | }
52 | }
53 |
54 | @at-root {
55 | #{$input} {
56 | @include field-icon(config('switch', $form-icon, false), color('border', 'form', 'true'));
57 | appearance: none;
58 | background-color: color('background', 'form');
59 | background-position: left center;
60 | background-repeat: no-repeat;
61 | background-size: contain;
62 | block-size: 1em;
63 | border: config('border-width', $form-switch) solid color('border', 'form');
64 | border-radius: 2em;
65 | flex-shrink: 0;
66 | font-size: config('font-size', $form-switch);
67 | inline-size: 2em;
68 | line-height: 1;
69 | margin-block: config('margin-block', $form-switch);
70 | transition-duration: config('duration', $transition);
71 | transition-property: background-position, border, box-shadow;
72 | transition-timing-function: config('timing-function', $transition);
73 |
74 | &:focus-visible {
75 | @include focus-ring(
76 | $type: config('focus-ring-type', $form-check, false),
77 | $border-color: color('switch-background', 'form'),
78 | $ring-color: color('switch-focus-ring', 'form'),
79 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-check, false),
80 | $ring-size: config('focus-ring-size', $form-check, false),
81 | $ring-offset: config('focus-ring-offset', $form-check, false)
82 | );
83 | }
84 |
85 | &:checked {
86 | @include field-icon(config('switch', $form-icon, false), color('switch-foreground', 'form', 'true'));
87 | background-color: color('switch-background', 'form');
88 | background-position: right center;
89 | border-color: color('switch-background', 'form');
90 | }
91 |
92 | &:disabled {
93 | @include field-disabled(
94 | $background: color('background-disabled', 'form'),
95 | $border: color('border-disabled', 'form')
96 | );
97 |
98 | + #{$label} {
99 | opacity: 0.5;
100 | }
101 | }
102 | }
103 |
104 | [dir='rtl'] #{$input} {
105 | background-position: right center;
106 |
107 | &:checked {
108 | background-position: left center;
109 | }
110 | }
111 |
112 | #{$label} {
113 | font-weight: config('font-weight', $form-switch);
114 | line-height: config('line-height', $form-switch);
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/scss/form/_validation.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../function' as *;
4 |
5 | @mixin generate-form-feedback {
6 | .field-feedback {
7 | display: block;
8 | line-height: map.get($typography, 'line-height-md');
9 |
10 | &--valid {
11 | color: color('success', 'alert');
12 | }
13 |
14 | &--invalid {
15 | color: color('danger', 'alert');
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/scss/function/_color.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:map';
3 | @use 'sass:math';
4 | @use 'setting' as *;
5 | @use '../config' as *;
6 |
7 | /// Get any color value from $colors (or any) Sass map.
8 | /// @param {string} $key - The key name of the color.
9 | /// @param {string} $type - The type of the color group (base, dark, etc.).
10 | /// @param {boolean} $only-color - If true, return only the color value.
11 | /// @param {map} $map - The map to get the color from.
12 | /// @return {color} - The color value or the variable.
13 | @function color(
14 | $key,
15 | $type: 'base',
16 | $only-color: false,
17 | $map: $colors
18 | ) {
19 | @if not map.has-key($map, $type, $key) {
20 | @error 'The #{$key} key name doesn\'t exist under #{$type} at the specified map (default: $colors).';
21 | }
22 |
23 | @if map.get($map, $type, $key) == null {
24 | @return null;
25 | }
26 |
27 | @if $only-color {
28 | @return map.get($map, $type, $key);
29 | }
30 |
31 | @if map.get($settings, color-fallback) {
32 | @return var(--#{$internal-prefix}#{$type}-color-#{$key}, #{map.get($map, $type, $key)});
33 | }
34 |
35 | @return var(--#{$internal-prefix}#{$type}-color-#{$key});
36 | }
37 |
38 | /// Get any - just - color value from $colors (or any) Sass map.
39 | /// @param {string} $key - The key name of the color.
40 | /// @param {string} $type - The type of the color group (base, dark, etc.).
41 | /// @param {map} $map - The map to get the color from.
42 | /// @return {color} - The color value.
43 | @function color-value(
44 | $key,
45 | $type: 'base',
46 | $map: $colors
47 | ) {
48 | @if not map.has-key($map, $type, $key) {
49 | @error 'The #{$key} key name doesn\'t exist under #{$type} at the specified map (default: $colors).';
50 | }
51 |
52 | @if map.get($map, $type, $key) == null {
53 | @return null;
54 | }
55 |
56 | @return map.get($map, $type, $key);
57 | }
58 |
59 | /// Get a white or black contrast color for any color (on WCAG standards).
60 | /// Thanks for David Halford for this function: https://codepen.io/davidhalford/pen/ALrbEP
61 | /// @param {color} $color - The color to get the contrast color.
62 | /// @return {color} - The contrast color.
63 | @function color-contrast($color) {
64 | $color-brightness: math.round((color.channel($color, 'red', $space: rgb) * 299) + (color.channel($color, 'green', $space: rgb) * 587) + math.div(color.channel($color, 'blue', $space: rgb) * 114, 1000));
65 | $light-color: math.round((color.channel(hsl(0deg 0% 100%), 'red', $space: rgb) * 299) + (color.channel(hsl(0deg 0% 100%), 'green', $space: rgb) * 587) + math.div(color.channel(hsl(0deg 0% 100%), 'blue', $space: rgb) * 114, 1000));
66 |
67 | @if math.abs($color-brightness) < math.div($light-color, 2) {
68 | @return hsl(0 0% 100%);
69 | } @else {
70 | @return hsl(0 100% 0%);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/scss/function/_config.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'sass:string';
3 | @use 'setting' as *;
4 | @use '../config' as *;
5 |
6 | /// Get the value of a key from a map.
7 | /// @param {string} $key - The key name.
8 | /// @param {map} $map - The map to get the value from.
9 | /// @param {boolean} $custom-property - Whether to return the value as a CSS custom property.
10 | /// @return {string} - The value of the key.
11 | /// @throws {error} - If the key doesn't exist.
12 | @function config(
13 | $key,
14 | $map,
15 | $custom-property: true
16 | ) {
17 | @if not map.has-key($map, $key) {
18 | @error $key;
19 | @error 'The #{$key} key name doesn\'t exist under #{$map} at the specified map.';
20 | }
21 |
22 | @if map.get($map, $key) == null {
23 | @return null;
24 | }
25 |
26 | @if not $custom-property {
27 | @return map.get($map, $key);
28 | }
29 |
30 | @if map.get($settings, 'css-custom-properties') {
31 | @return var(--#{$internal-prefix}#{$key});
32 | }
33 |
34 | @return map.get($map, $key);
35 | }
36 |
--------------------------------------------------------------------------------
/scss/function/_css-variable.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:string';
2 | @use 'sass:map';
3 | @use '../config' as *;
4 |
5 | /// Add the prefix value to a CSS custom properties.
6 | /// @param {string} $var - The name of the CSS custom property.
7 | /// @return {string} - The CSS custom property with the prefix.
8 | /// @throws {error} - If the CSS custom property name is invalid.
9 | @function get-css-variable($var) {
10 | @if string.index($var, --) != 1 {
11 | @error 'It seems that this is not a valid CSS custom property name.';
12 | }
13 |
14 | @return var(string.insert($var, '#{$internal-prefix}', 3));
15 | }
16 |
--------------------------------------------------------------------------------
/scss/function/_font-size.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'sass:math';
3 | @use '../config' as *;
4 |
5 | /// Get the font size of a key from the $font-sizes map.
6 | /// @param {string} $key - The key name.
7 | /// @param {boolean} $fluid - Whether to return the fluid font size.
8 | /// @param {number} $scaler - The scaler value (15 = 15% smaller).
9 | /// @param {number} $optimal-size - The optimal font size.
10 | /// @return {string} - The font size of the key.
11 | /// @throws {error} - If the key doesn't exist.
12 | @function font-size(
13 | $key,
14 | $fluid: true,
15 | $scaler: map.get($settings, 'scaler'),
16 | $optimal-size: map.get($settings, 'optimal-font-size')
17 | ) {
18 | @if not map.has-key($font-sizes, $key) {
19 | @error 'The #{$key} key name doesn\'t exist at the $font-sizes map.';
20 | }
21 |
22 | @if $scaler < 0 or $scaler > 100 {
23 | @error 'The $scaler value must be between 0 and 100.';
24 | }
25 |
26 | @if $fluid {
27 | $scaled-size: map.get($font-sizes, $key) * math.div(100 - $scaler, 100);
28 |
29 | @if $scaled-size < map.get($typography, 'font-size-base') {
30 | @return map.get($font-sizes, $key);
31 | }
32 |
33 | @return clamp(#{$scaled-size}, #{$optimal-size}, #{map.get($font-sizes, $key)});
34 | }
35 |
36 | @return map.get($font-sizes, $key);
37 | }
38 |
39 | /// Generate responsive font-size value using clamp().
40 | /// @param {number} $size - The font size.
41 | /// @param {number} $scaler - The scaler value (15 = 15% smaller).
42 | /// @param {number} $optimal-size - The optimal font size.
43 | /// @return {string} - The responsive font-size value.
44 | @function responsive-font-size(
45 | $size,
46 | $scaler: map.get($settings, 'scaler'),
47 | $optimal-size: map.get($settings, 'optimal-font-size')
48 | ) {
49 | @if $scaler < 0 or $scaler > 100 {
50 | @error 'The $scaler value must be between 0 and 100.';
51 | }
52 |
53 | @return clamp(#{$size * math.div(100 - $scaler, 100)}, #{$optimal-size}, #{$size});
54 | }
55 |
--------------------------------------------------------------------------------
/scss/function/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'color';
2 | @forward 'config';
3 | @forward 'css-variable';
4 | @forward 'font-size';
5 | @forward 'utilities';
6 | @forward 'setting';
7 | @forward 'spacer';
8 |
--------------------------------------------------------------------------------
/scss/function/_setting.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 |
4 | /// Get spacer value from $settings map.
5 | /// @param {string} $key - The key name.
6 | /// @return {string} - The value of the key.
7 | /// @throws {error} - If the key doesn't exist.
8 | @function setting(
9 | $key,
10 | $group: null
11 | ) {
12 | @if $group {
13 | @if not map.has-key($settings, $group) {
14 | @error 'The #{$group} key name doesn\'t exist at the $settings map.';
15 | }
16 |
17 | @if not map.has-key(map.get($settings, $group), $key) {
18 | @error 'The #{$key} key name doesn\'t exist at the #{$group} map.';
19 | }
20 |
21 | @return map.get(map.get($settings, $group), $key);
22 | }
23 |
24 | @if not map.has-key($settings, $key) {
25 | @error 'The #{$key} key name doesn\'t exist at the $settings map.';
26 | }
27 |
28 | @return map.get($settings, $key);
29 | }
30 |
--------------------------------------------------------------------------------
/scss/function/_spacer.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'sass:string';
3 | @use 'sass:list';
4 | @use '../config' as *;
5 |
6 | /// Split a string into a list of values.
7 | /// @param {string} $value - The string to split.
8 | /// @param {string} $separator - The separator to split by.
9 | /// @return {list} - The list of values.
10 | @function split-values($value, $separator: ':') {
11 | $colon-index: string.index($value, $separator);
12 |
13 | @if $colon-index {
14 | $first: string.slice($value, 1, $colon-index - 1);
15 | $second: string.slice($value, $colon-index + 1);
16 |
17 | @return ($first, $second);
18 | }
19 |
20 | @return null;
21 | }
22 |
23 | /// Get spacer value from $spacers map.
24 | /// @param {string} $key - The key name.
25 | /// @return {string} - The value of the key.
26 | /// @throws {error} - If the key doesn't exist.
27 | @function spacer($key) {
28 | @if string.index($key, ':') {
29 | $list: null;
30 |
31 | @each $key in split-values($key) {
32 | @if not map.has-key($spacers, $key) {
33 | @error 'The #{$key} key name doesn\'t exist at the $spacers map.';
34 | }
35 |
36 | $list: list.append($list, map.get($spacers, $key));
37 | }
38 |
39 | @return $list;
40 | }
41 |
42 | @if not map.has-key($spacers, $key) {
43 | @error 'The #{$key} key name doesn\'t exist at the $spacers map.';
44 | }
45 |
46 | @return map.get($spacers, $key);
47 | }
48 |
49 | /// Get value returned in a clamp from $spacers maps.
50 | /// @param {string} $min - The minimum value.
51 | /// @param {string} $max - The maximum value.
52 | /// @param {string} $optimal - The optimal value.
53 | /// @return {string} - The value returned in a clamp.
54 | @function spacer-clamp(
55 | $min,
56 | $max,
57 | $optimal: map.get($settings, 'optimal-spacer-size')
58 | ) {
59 | @if map.has-key($spacers, $min) {
60 | $min: map.get($spacers, $min);
61 | }
62 |
63 | @if map.has-key($spacers, $max) {
64 | $max: map.get($spacers, $max);
65 | }
66 |
67 | @return clamp(#{$min}, #{$optimal}, #{$max});
68 | }
69 |
--------------------------------------------------------------------------------
/scss/function/_utilities.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:string';
2 | @use '../config' as *;
3 |
4 | // Replace `$search` with `$replace` in `$string`
5 | // @author Kitty Giraudel
6 | // @param {String} $string - Initial string
7 | // @param {String} $search - Substring to replace
8 | // @param {String} $replace ('') - New value
9 | // @return {String} - Updated string
10 | @function str-replace($string, $search, $replace: '') {
11 | $index: string.index($string, $search);
12 |
13 | @if $index {
14 | @return string.slice($string, 1, $index - 1) + $replace + str-replace(string.slice($string, $index + string.length($search)), $search, $replace);
15 | }
16 |
17 | @return $string;
18 | }
19 |
20 | /// Escape a string to be used as a data URI.
21 | /// @param {String} $string - The string to escape.
22 | /// @return {String} - The escaped string.
23 | /// @author Kevin Weber - https://codepen.io/kevinweber/pen/dXWoRw
24 | @function svg-escape($svg) {
25 | @each $char, $encoded in $escaping-characters {
26 | $svg: str-replace($svg, $char, $encoded);
27 | }
28 | @return 'data:image/svg+xml,' + $svg;
29 | }
30 |
--------------------------------------------------------------------------------
/scss/mixin/_breakpoint.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 |
4 | /// Return a media query for a breakpoint based on min-width.
5 | /// @param {string} $breakpoint - The breakpoint name.
6 | /// @param {string} $logic - The logic operator.
7 | /// @return {string} - The media query.
8 | /// @throws {error} - If the breakpoint doesn't exist.
9 | @mixin breakpoint(
10 | $breakpoint,
11 | $logic: false
12 | ) {
13 | @if map.has-key($breakpoints, $breakpoint) {
14 | $breakpoint: map.get($breakpoints, $breakpoint);
15 |
16 | @if $logic {
17 | @media #{$logic} and (min-width: $breakpoint) {
18 | @content;
19 | }
20 | } @else {
21 | @media (min-width: $breakpoint) {
22 | @content;
23 | }
24 | }
25 | } @else {
26 | @error 'Invalid breakpoint: #{$breakpoint}.';
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/scss/mixin/_button.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:map';
3 | @use '../function' as *;
4 | @use '../config' as *;
5 | @use 'form' as *;
6 |
7 | /// Generate a button focus ring.
8 | /// @param {string} $type - The type of the button for the color value.
9 | /// @param {boolean} $focus - If the focus ring should be generated.
10 | /// @return {string} - The generated focus ring.
11 | @mixin btn-focus-helper(
12 | $type: 'primary',
13 | $focus: true
14 | ) {
15 | @if $focus {
16 | &:focus-visible {
17 | $ring-color: null;
18 |
19 | @if map.has-key($colors, 'btn', $type + '-focus-ring') {
20 | $ring-color: color($type + '-focus-ring', 'btn');
21 | } @else {
22 | $ring-color: color($type + '-background', 'btn');
23 | }
24 |
25 | @include focus-ring(
26 | $type: map.get($btn, 'focus-ring-type'),
27 | $ring-color: $ring-color,
28 | $box-shadow-type: map.get($btn, 'focus-ring-box-shadow-type'),
29 | $ring-size: map.get($btn, 'focus-ring-size'),
30 | $ring-offset: map.get($btn, 'focus-ring-offset')
31 | );
32 | }
33 | }
34 | }
35 |
36 | /// Generate a button variant.
37 | /// @param {string} $type - The type of the button for the color value.
38 | /// @param {boolean} $focus - If the focus ring should be generated.
39 | /// @return {string} - The generated button variant.
40 | /// @throws {error} - If the color key doesn't exist.
41 | @mixin btn-variant(
42 | $type: 'primary',
43 | $focus: true
44 | ) {
45 | @if not map.has-key($colors, 'btn', $type + '-foreground') or not map.has-key($colors, 'btn', $type + '-background') {
46 | @error 'The #{$type + '-foreground'} or #{$type + '-background'} key name doesn\'t exist under btn at the $colors map.';
47 | }
48 |
49 | @include btn-focus-helper($type, $focus);
50 |
51 | background-color: color($type + '-background', 'btn');
52 | border-color: color($type + '-background', 'btn');
53 | color: color($type + '-foreground', 'btn');
54 |
55 | &:hover {
56 | @if map.has-key($colors, 'btn', $type + '-background-hover') {
57 | background-color: color($type + '-background-hover', 'btn');
58 | border-color: color($type + '-background-hover', 'btn');
59 | } @else {
60 | background-color: color.adjust(color($type + '-background', 'btn', true), $lightness: -10%);
61 | border-color: color.adjust(color($type + '-background', 'btn', true), $lightness: -10%);
62 | }
63 |
64 | @if map.has-key($colors, 'btn', $type + '-foreground-hover') {
65 | color: color($type + '-foreground-hover', 'btn');
66 | } @else {
67 | color: color($type + '-foreground', 'btn');
68 | }
69 | }
70 |
71 | @if map.has-key($colors, 'btn', $type + '-shadow') {
72 | &-shadow {
73 | box-shadow: 0 0.55em 1em -0.2em color($type + '-shadow', 'btn'), 0 0.15em 0.35em -0.185em color($type + '-shadow', 'btn');
74 | }
75 | }
76 | }
77 |
78 | /// Generate a button variant with outline.
79 | /// @param {string} $type - The type of the button for the color value.
80 | /// @param {boolean} $focus - If the focus ring should be generated.
81 | /// @return {string} - The generated button variant with outline.
82 | /// @throws {error} - If the color key doesn't exist.
83 | @mixin btn-variant-outline(
84 | $type: primary,
85 | $focus: true
86 | ) {
87 | @if not map.has-key($colors, 'btn', $type + '-foreground') or not map.has-key($colors, 'btn', $type + '-background') {
88 | @error 'The #{$type + '-foreground'} or #{$type + '-background'} key name doesn\'t exist under btn at the $colors map.';
89 | }
90 |
91 | @if map.has-key($colors, 'btn', $type + '-outline-focus-ring') {
92 | @include btn-focus-helper($type + '-outline', $focus);
93 | } @else {
94 | @include btn-focus-helper($type, $focus);
95 | }
96 |
97 | background-color: transparent;
98 |
99 | @if map.has-key($colors, 'btn', $type + '-outline-border') {
100 | border-color: color($type + '-outline-border', 'btn');
101 | } @else {
102 | border-color: color($type + '-background', 'btn');
103 | }
104 |
105 | @if map.has-key($colors, 'btn', $type + '-outline-foreground') {
106 | color: color($type + '-outline-foreground', 'btn');
107 | } @else {
108 | color: color($type + '-background', 'btn');
109 | }
110 |
111 | &:hover {
112 | @if map.has-key($colors, 'btn', $type + '-outline-background-hover') {
113 | background-color: color($type + '-outline-background-hover', 'btn');
114 | border-color: color($type + '-outline-background-hover', 'btn');
115 | } @else {
116 | background-color: color($type + '-background', 'btn');
117 | border-color: color($type + '-background', 'btn');
118 | }
119 |
120 | @if map.has-key($colors, 'btn', $type + '-outline-foreground-hover') {
121 | color: color($type + '-outline-foreground-hover', 'btn');
122 | } @else {
123 | color: color($type + '-foreground', 'btn');
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/scss/mixin/_color.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../config' as *;
4 |
5 | /// Generate color variables.
6 | /// @param {map} $colors - The colors map.
7 | /// @param {string} $selector - The selector.
8 | /// @return {string} - The generated color variables.
9 | @mixin generate-color-variables(
10 | $colors: $colors,
11 | $selector: ':root'
12 | ) {
13 | @each $key, $value in $colors {
14 | #{$selector} {
15 | @each $name, $color in $value {
16 | @if $color {
17 | --#{$internal-prefix}#{$key}-color-#{$name}: #{$color};
18 | }
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/scss/mixin/_css-variable.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:string';
2 | @use 'sass:map';
3 | @use '../config' as *;
4 |
5 | /// Declare CSS custom properties through Spruce to add the prefix.
6 | /// @param {map} $vars - The CSS custom properties.
7 | /// @return {null}
8 | /// @throws {error} - If the CSS custom property name is invalid.
9 | @mixin set-css-variable($vars) {
10 | @each $name, $value in $vars {
11 | @if string.index($name, --) != 1 {
12 | @error 'It seems that this is not a valid CSS custom property name.';
13 | }
14 |
15 | #{string.insert($name, '#{$internal-prefix}', 3)}: #{$value};
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/scss/mixin/_font-face.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:string';
2 |
3 | /// Generate font-face declaration.
4 | /// @param {string} $font-family - The font family name.
5 | /// @param {string} $src - The font source.
6 | /// @param {number} $font-weight - The font weight.
7 | /// @param {string} $font-style - The font style.
8 | /// @param {string} $font-display - The font display.
9 | /// @return {string} - The generated font-face declaration.
10 | /// @throws {error} - If the font format is not .woff2.
11 | @mixin font-face(
12 | $font-family: null,
13 | $src: null,
14 | $font-weight: 400,
15 | $font-style: normal,
16 | $font-display: swap
17 | ) {
18 | @if not string.index($src, '.woff2') {
19 | @error 'It seems that your font format is not .woff2, please use a that format.';
20 | }
21 |
22 | @font-face {
23 | font-display: $font-display;
24 | font-family: $font-family;
25 | font-style: $font-style;
26 | font-weight: $font-weight;
27 | src: url('#{$src}') format('woff2');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/scss/mixin/_form.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../config' as *;
4 | @use './variables' as *;
5 | @use './breakpoint' as *;
6 |
7 | /// Generate a from focus ring.
8 | /// @param {string} $type - The type of focus ring (box-shadow, outline).
9 | /// @param {string} $border-color - The border color.
10 | /// @param {string} $ring-color - The ring color.
11 | /// @param {string} $box-shadow-type - The box shadow type (outset, inset).
12 | /// @param {string} $ring-size - The ring width.
13 | /// @param {string} $ring-offset - The ring offset.
14 | /// @return {string} - The generated focus ring.
15 | @mixin focus-ring(
16 | $type: 'box-shadow',
17 | $border-color: null,
18 | $ring-color,
19 | $box-shadow-type: outset,
20 | $ring-size: 2px,
21 | $ring-offset: 2px
22 | ) {
23 | @if $type == 'box-shadow' {
24 | border-color: $border-color;
25 | @if $box-shadow-type == 'inset' {
26 | box-shadow: 0 0 0 $ring-size $ring-color inset;
27 | } @else {
28 | box-shadow: 0 0 0 $ring-size $ring-color;
29 | }
30 | outline: 2px solid transparent;
31 | }
32 |
33 | @if $type == 'outline' {
34 | outline: $ring-size solid $ring-color;
35 | outline-offset: $ring-offset;
36 | }
37 | }
38 |
39 | /// Style field disabled input states.
40 | /// @param {string} $background - The background color.
41 | /// @param {string} $border - The border color.
42 | /// @return {string} - The generated disabled input states.
43 | @mixin field-disabled(
44 | $background,
45 | $border
46 | ) {
47 | background-color: $background;
48 | border-color: $border;
49 | cursor: not-allowed;
50 | }
51 |
52 | /// Get custom icon background for input and select fields.
53 | /// @param {string} $icon - The icon (an SVG in string).
54 | /// @param {string} $color - The color.
55 | /// @return {string} - The generated icon background.
56 | @mixin field-icon(
57 | $icon,
58 | $color
59 | ) {
60 | background-image: url('#{svg-escape(str-replace($icon, "#COLOR#", $color))}');
61 | }
62 |
63 | /// Create a form group stacked layout with custom breakpoint.
64 | /// @param {string} $breakpoint - The breakpoint.
65 | /// @return {string} - The generated form group stacked layout.
66 | @mixin form-group-stacked(
67 | $breakpoint: 'sm',
68 | ) {
69 | @if not map.has-key($breakpoints, $breakpoint) {
70 | @error 'The #{$breakpoint} not exists in the breakpoints map.';
71 | }
72 |
73 | @include generate-variables($form-control, $include: ('border-radius'));
74 | display: flex;
75 | flex-direction: column;
76 |
77 | @include breakpoint($breakpoint) { flex-direction: row; }
78 |
79 | > * {
80 | + * {
81 | border-start-end-radius: 0;
82 | border-start-start-radius: 0;
83 | margin-block-start: -1px;
84 |
85 | @include breakpoint($breakpoint) {
86 | border-end-start-radius: 0;
87 | border-start-end-radius: config('border-radius', $form-control);
88 | margin-block-start: 0;
89 | margin-inline-start: -1px;
90 | }
91 | }
92 |
93 | &:not(:last-child) {
94 | border-end-end-radius: 0;
95 | border-end-start-radius: 0;
96 |
97 | @include breakpoint($breakpoint) {
98 | border-end-end-radius: 0;
99 | border-start-end-radius: 0;
100 | }
101 | }
102 |
103 | @include breakpoint($breakpoint) {
104 | &:first-child {
105 | border-end-start-radius: config('border-radius', $form-control);
106 | }
107 | }
108 |
109 | &:focus {
110 | z-index: 2;
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/scss/mixin/_generator.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../element' as *;
4 | @use '../plugin' as *;
5 | @use '../form' as *;
6 | @use '../print' as *;
7 | @use 'button' as *;
8 |
9 | /// Generate all the styles.
10 | @mixin generate-styles {
11 | @if map.get($generators, 'content', 'normalize') {
12 | @include generate-normalize;
13 | }
14 |
15 | @if map.get($generators, 'content', 'root') {
16 | @include generate-root;
17 | }
18 |
19 | @if map.get($generators, 'content', 'accessibility') {
20 | @include generate-accessibility;
21 | }
22 |
23 | @if map.get($generators, 'content', 'default') {
24 | @include generate-default;
25 | }
26 |
27 | @if map.get($generators, 'content', 'divider') {
28 | @include generate-divider;
29 | }
30 |
31 | @if map.get($generators, 'content', 'media') {
32 | @include generate-media;
33 | }
34 |
35 | @if map.get($generators, 'content', 'table') {
36 | @include generate-table;
37 | }
38 |
39 | @if map.get($generators, 'content', 'typography') {
40 | @include generate-typography;
41 | }
42 |
43 | @if map.get($generators, 'content', 'utilities') {
44 | @include generate-utilities;
45 | }
46 |
47 | @if map.get($generators, 'content', 'print') {
48 | @include generate-print;
49 | }
50 |
51 | @if map.get($generators, 'form', 'btn') {
52 | @include generate-btn('.btn');
53 |
54 | .btn--primary { @include btn-variant(primary); }
55 | .btn--secondary { @include btn-variant(secondary); }
56 | .btn--outline-primary { @include btn-variant-outline(primary); }
57 | .btn--outline-secondary { @include btn-variant-outline(secondary); }
58 | }
59 |
60 | @if map.get($generators, 'form', 'file-btn') {
61 | @include generate-file-btn(
62 | '.form-file',
63 | '::file-selector-button',
64 | false,
65 | true
66 | );
67 | }
68 |
69 | @if map.get($generators, 'form', 'form-label') {
70 | @include generate-form-label;
71 | }
72 |
73 | @if map.get($generators, 'form', 'form-control') {
74 | @include generate-form-control(
75 | '.form-control',
76 | true,
77 | true,
78 | true
79 | );
80 | }
81 |
82 | @if map.get($generators, 'form', 'form-check') {
83 | @include generate-form-check(
84 | '.form-check',
85 | '.form-check__control',
86 | '.form-check__label',
87 | true
88 | );
89 | }
90 |
91 | @if map.get($generators, 'form', 'form-switch') {
92 | @include generate-form-switch(
93 | '.form-switch',
94 | '.form-switch__control',
95 | '.form-switch__label',
96 | true
97 | );
98 | }
99 |
100 | @if map.get($generators, 'form', 'form-fieldset') {
101 | @include generate-form-fieldset;
102 | }
103 |
104 | @if map.get($generators, 'form', 'form-group-label') {
105 | @include generate-form-group-label;
106 | }
107 |
108 | @if map.get($generators, 'form', 'form-group') {
109 | @include generate-form-group;
110 | }
111 |
112 | @if map.get($generators, 'form', 'form-row') {
113 | @include generate-form-row;
114 | }
115 |
116 | @if map.get($generators, 'form', 'form-feedback') {
117 | @include generate-form-feedback;
118 | }
119 |
120 | @if map.get($generators, 'form', 'form-range') {
121 | @include generate-form-range;
122 | }
123 |
124 | @if map.get($generators, 'form', 'form-description') {
125 | @include generate-form-description;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/scss/mixin/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'breakpoint';
2 | @forward 'button';
3 | @forward 'color';
4 | @forward 'css-variable';
5 | @forward 'font-face';
6 | @forward 'form';
7 | @forward 'layout';
8 | @forward 'transition';
9 | @forward 'selection';
10 | @forward 'utilities';
11 | @forward 'variables';
12 |
--------------------------------------------------------------------------------
/scss/mixin/_layout.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'sass:meta';
3 | @use 'sass:string';
4 | @use 'sass:list';
5 | @use '../function' as *;
6 | @use '../config' as *;
7 |
8 | /// Create center layout.
9 | /// @param {string} $gap - The gap between the container and the content.
10 | /// @param {string} $max-inline-size - The maximum width (inline-size) of the container.
11 | /// @return {mixin} - The centered layout.
12 | @mixin layout-center(
13 | $gap: m,
14 | $max-inline-size: config('container-inline-size', $layout)
15 | ) {
16 | @if map.has-key($spacers, $gap) {
17 | $gap: map.get($spacers, $gap);
18 | }
19 |
20 | inline-size: 100%;
21 | margin-inline: auto;
22 | max-inline-size: $max-inline-size;
23 | padding-inline: $gap;
24 | }
25 |
26 | /// Create stack layout.
27 | /// @param {string} $gap - The gap between the the elements.
28 | /// @param {boolean} $inline-size - Whether it has explicit width (inline-size).
29 | /// @param {string} $align - The horizontal alignment of the elements.
30 | /// @param {boolean} $important - Whether it should use the !important keyword.
31 | /// @return {mixin} - The stacked layout.
32 | @mixin layout-stack(
33 | $gap: 'm',
34 | $inline-size: false,
35 | $align: none,
36 | $important: false
37 | ) {
38 | @if map.has-key($spacers, $gap) {
39 | $gap: map.get($spacers, $gap);
40 | }
41 |
42 | @if $align == left or $align == right {
43 | display: flex;
44 | flex-direction: column;
45 | }
46 |
47 | @if $align == left {
48 | align-items: flex-start;
49 | }
50 |
51 | @if $align == right {
52 | align-items: flex-end;
53 | }
54 |
55 | > * {
56 | margin-block-end: 0;
57 | margin-block-start: 0;
58 |
59 | @if $inline-size and $align == none {
60 | inline-size: 100%;
61 | }
62 | }
63 |
64 | > * + * {
65 | @if $important == true {
66 | margin-block-start: $gap !important;
67 | } @else {
68 | margin-block-start: $gap;
69 | }
70 | }
71 | }
72 |
73 | /// Create grid layout.
74 | /// @param {string} $gap - The gap between the the elements.
75 | /// @param {string} $minimum - The minimum width (inline-size) of the elements.
76 | /// @return {mixin} - The grid layout.
77 | @mixin layout-grid(
78 | $gap: 'm',
79 | $minimum: 12.5rem
80 | ) {
81 | @if meta.type-of($gap) == string and string.index($gap, ':') {
82 | $gap: spacer($gap);
83 | } @else if map.has-key($spacers, $gap) {
84 | $gap: map.get($spacers, $gap);
85 | }
86 |
87 | display: grid;
88 | gap: $gap;
89 |
90 | @supports (inline-size: min(#{$minimum}, 100%)) {
91 | & {
92 | grid-template-columns: repeat(auto-fit, minmax(min(#{$minimum}, 100%), 1fr));
93 | }
94 | }
95 | }
96 |
97 | /// Create sidebar layout.
98 | /// @param {string} $gap - The gap between the the elements.
99 | /// @param {string} $inline-size - The width (flex-basis) of the sidebar.
100 | /// @return {mixin} - The sidebar layout.
101 | @mixin layout-sidebar(
102 | $gap: 'm',
103 | $inline-size: 18.75rem
104 | ) {
105 | @if meta.type-of($gap) == string and string.index($gap, ':') {
106 | $gap: spacer($gap);
107 | } @else if map.has-key($spacers, $gap) {
108 | $gap: map.get($spacers, $gap);
109 | }
110 |
111 | display: flex;
112 | flex-wrap: wrap;
113 | gap: $gap;
114 |
115 | & > :first-child {
116 | flex-basis: $inline-size;
117 | flex-grow: 1;
118 | }
119 |
120 | & > :last-child {
121 | flex-basis: 0;
122 | flex-grow: 999;
123 | min-inline-size: 50%;
124 | }
125 | }
126 |
127 | /// Create instinctive flex layout.
128 | /// @param {string} $gap - The gap between the the elements.
129 | /// @param {string} $inline-size - The width (inline size) of the elements.
130 | /// @return {mixin} - The instinctive flex layout.
131 | @mixin layout-flex(
132 | $gap: 'm',
133 | $inline-size: var(--inline-size)
134 | ) {
135 | @if map.has-key($spacers, $gap) {
136 | $gap: map.get($spacers, $gap);
137 | }
138 |
139 | display: flex;
140 | flex-wrap: wrap;
141 | gap: $gap;
142 |
143 | > * {
144 | flex: 1 1 $inline-size;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/scss/mixin/_selection.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../config' as *;
4 |
5 | /// Set the ::selection of an element with automatic foreground color.
6 | /// @param {color} $background - The background color.
7 | /// @param {color} $foreground - The foreground color. If null, the color will be automatically calculated.
8 | /// @param {boolean} $is-direct - If true, the selection will be applied to the current element if false it will be applied to its children.
9 | /// @return {mixin} - The selection mixin.
10 | @mixin selection(
11 | $background: 'primary',
12 | $foreground: null,
13 | $is-direct: false
14 | ) {
15 | $is-variable: false;
16 | $original-background: $background;
17 |
18 | @if map.has-key($colors, 'base', $background) {
19 | $background: color($background);
20 | $is-variable: true;
21 | }
22 |
23 | @if not $foreground and not $is-variable {
24 | $foreground: color-contrast($background);
25 | } @else if not $foreground and $is-variable {
26 | $foreground: color-contrast(color($original-background, $only-color: true));
27 | }
28 |
29 | @if $is-direct {
30 | &::selection {
31 | background-color: $background;
32 | color: $foreground;
33 | }
34 | } @else {
35 | ::selection {
36 | background-color: $background;
37 | color: $foreground;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/scss/mixin/_transition.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:string';
2 | @use '../function' as *;
3 | @use '../config' as *;
4 |
5 | /// Generates transition related declarations.
6 | /// @param {string} $duration - The duration of the transition.
7 | /// @param {string} $property - The property to which the transition is applied.
8 | /// @param {string} $timing-function - The speed curve of the transition.
9 | /// @return {string} - The generated transition declarations.
10 | /// @throws {error} - If the duration or timing-function is invalid.
11 | @mixin transition(
12 | $duration: config('duration', $transition),
13 | $property: all,
14 | $timing-function: config('timing-function', $transition),
15 | ) {
16 | transition-duration: $duration;
17 | transition-property: string.unquote($property);
18 | transition-timing-function: $timing-function;
19 | }
20 |
--------------------------------------------------------------------------------
/scss/mixin/_utilities.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../config' as *;
4 | @use 'form' as *;
5 |
6 |
7 | /// Hide something from the screen but keep it visible for assistive technology.
8 | /// @return {mixin} - The visually hidden mixin.
9 | @mixin visually-hidden {
10 | block-size: 1px !important;
11 | border: 0 !important;
12 | clip: rect(0, 0, 0, 0) !important;
13 | inline-size: 1px !important;
14 | margin: -1px !important;
15 | overflow: hidden !important;
16 | padding: 0 !important;
17 | position: absolute !important;
18 | white-space: nowrap !important;
19 | }
20 |
21 | /// Crop text and display an ellipsis with multiline.
22 | /// @param {number} $number-of-lines - The number of lines.
23 | /// @return {mixin} - The text ellipsis mixin.
24 | @mixin text-ellipsis(
25 | $number-of-lines: 1
26 | ) {
27 | overflow: hidden;
28 | text-overflow: ellipsis;
29 |
30 | @if $number-of-lines == 1 {
31 | white-space: nowrap;
32 | } @else {
33 | white-space: inherit;
34 |
35 | @supports (-webkit-line-clamp: $number-of-lines) {
36 | -webkit-box-orient: vertical;
37 | display: -webkit-box;
38 | -webkit-line-clamp: $number-of-lines;
39 | }
40 | }
41 | }
42 |
43 | /// Custom scrollbar.
44 | /// @param {string} $thumb-background-color - The background color of the thumb.
45 | /// @param {string} $thumb-background-color-hover - The background color of the thumb when hovered.
46 | /// @param {string} $track-background-color - The background color of the track.
47 | /// @param {string} $size - The size of the scrollbar.
48 | /// @param {string} $border-radius - The border radius of the scrollbar.
49 | /// @return {mixin} - The scrollbar mixin.
50 | @mixin scrollbar(
51 | $thumb-background-color: color('thumb-background', 'scrollbar'),
52 | $thumb-background-color-hover: color('thumb-background-hover', 'scrollbar'),
53 | $track-background-color: color('track-background', 'scrollbar'),
54 | $size: 0.5rem,
55 | $border-radius: config('border-radius-sm', $display)
56 | ) {
57 | &::-webkit-scrollbar {
58 | block-size: $size;
59 | inline-size: $size;
60 | }
61 |
62 | &::-webkit-scrollbar-thumb {
63 | background: $thumb-background-color;
64 | border-radius: $border-radius;
65 |
66 | &:hover {
67 | background: $thumb-background-color-hover;
68 | }
69 | }
70 |
71 | &::-webkit-scrollbar-track {
72 | background: $track-background-color;
73 | border-radius: $border-radius;
74 | }
75 | }
76 |
77 | /// Clear default button styles.
78 | /// @return {mixin} - The clear button mixin.
79 | @mixin clear-btn {
80 | background: none;
81 | border: 0;
82 | color: inherit;
83 | cursor: pointer;
84 | font: inherit;
85 | outline: inherit;
86 | padding: 0;
87 | }
88 |
89 | // Clear list styles
90 | @mixin clear-list {
91 | list-style: none;
92 | margin: 0;
93 | padding: 0;
94 | }
95 |
96 | /// More accessible card linking.
97 | /// @param {string} $link - The link element's selector.
98 | /// @param {boolean} $at-root - Whether to use @at-root.
99 | /// @return {mixin} - The a11y card link mixin.
100 | @mixin a11y-card-link(
101 | $link,
102 | $at-root: false
103 | ) {
104 | position: relative;
105 |
106 | @if $at-root == true {
107 | @at-root {
108 | #{$link}::before {
109 | content: '';
110 | inset: 0;
111 | position: absolute;
112 | }
113 | }
114 | } @else {
115 | #{$link}::before {
116 | content: '';
117 | inset: 0;
118 | position: absolute;
119 | }
120 | }
121 | }
122 |
123 | /// Break long string.
124 | /// @author Chris Coyier - https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/
125 | /// @return {mixin} - The word-wrap mixin.
126 | @mixin word-wrap {
127 | hyphens: auto;
128 | overflow-wrap: break-word;
129 | word-break: break-all;
130 | word-break: break-word;
131 | word-wrap: break-word;
132 | }
133 |
134 | /// Generate a focus ring.
135 | /// @param {string} $type - The type of the focus ring.
136 | /// @param {string} $btn-type - The type - hence color - of the button.
137 | /// @return {mixin} - The focus ring mixin.
138 | @mixin short-ring(
139 | $type: 'input',
140 | $btn-type: 'primary'
141 | ) {
142 | @if $type == 'input' {
143 | @include focus-ring(
144 | $type: config('focus-ring-type', $form-control, false),
145 | $border-color: color('border-focus', 'form'),
146 | $ring-color: color('ring-focus', 'form'),
147 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-control, false),
148 | $ring-size: config('focus-ring-size', $form-control, false),
149 | $ring-offset: config('focus-ring-offset', $form-control, false)
150 | );
151 | }
152 |
153 | @if $type == 'button' {
154 | $ring-color: null;
155 |
156 | @if map.has-key($colors, 'btn', $btn-type + '-focus-ring') {
157 | $ring-color: color($btn-type + '-focus-ring', 'btn');
158 | } @else {
159 | $ring-color: color($btn-type + '-background', 'btn');
160 | }
161 |
162 | @include focus-ring(
163 | $type: config('focus-ring-type', $btn, false),
164 | $ring-color: $ring-color,
165 | $box-shadow-type: config('focus-ring-box-shadow-type', $btn, false),
166 | $ring-size: config('focus-ring-size', $btn, false),
167 | $ring-offset: config('focus-ring-offset', $btn, false)
168 | );
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/scss/mixin/_variables.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'sass:string';
3 | @use '../config' as *;
4 |
5 | /// Generate CSS custom properties based on a map.
6 | /// @param {map} $map - The map to generate the CSS custom properties from.
7 | /// @param {list} $exclude - The list of keys (or a segment of it) to exclude.
8 | /// @param {list} $include - The list of keys (or a segment of it) to include.
9 | /// @return {string} - The generated CSS custom properties.
10 | /// @throws {error} - If you use both $exclude and $include arguments.
11 | @mixin generate-variables(
12 | $map,
13 | $exclude: null,
14 | $include: null
15 | ) {
16 | @if $exclude and $include {
17 | @error 'You can\'t use both $exclude and $include arguments.';
18 | }
19 |
20 | @if map.get($settings, 'css-custom-properties') {
21 | $exclude-map: $map;
22 | $include-map: ();
23 |
24 | @if $exclude {
25 | @each $key, $value in $map {
26 | @if $value {
27 | @each $fraction in $exclude {
28 | @if string.index($key, $fraction) {
29 | $exclude-map: map.remove($exclude-map, $key);
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
36 | @if $include {
37 | @each $key, $value in $map {
38 | @if $value {
39 | @each $fraction in $include {
40 | @if string.index($key, $fraction) {
41 | $include-map: map.set($include-map, $key, $value);
42 | }
43 | }
44 | }
45 | }
46 | }
47 |
48 | @if $exclude {
49 | @each $key, $value in $exclude-map {
50 | @if $value {
51 | --#{$internal-prefix}#{$key}: #{$value};
52 | }
53 | }
54 | }
55 |
56 | @if $include {
57 | @each $key, $value in $include-map {
58 | @if $value {
59 | --#{$internal-prefix}#{$key}: #{$value};
60 | }
61 | }
62 | }
63 |
64 | @if not $exclude and not $include {
65 | @each $key, $value in $map {
66 | @if $value {
67 | --#{$internal-prefix}#{$key}: #{$value};
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/scss/plugin/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'normalize';
2 |
--------------------------------------------------------------------------------
/scss/print/_index.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../function' as *;
4 | @use '../mixin' as *;
5 |
6 | @mixin generate-print {
7 | @if setting('print') == true {
8 | @media print {
9 | @page {
10 | margin: config('page-margin', $print);
11 | }
12 |
13 | #{config('hidden-elements', $print, false)} {
14 | display: none !important;
15 | }
16 |
17 | a[href^='http']::after {
18 | content: ' (' attr(href) ')';
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/scss/spruce-styles.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as * with (
2 | $settings: (
3 | 'css-custom-properties': true,
4 | ),
5 | );
6 |
7 | @include generate-styles;
8 |
--------------------------------------------------------------------------------
/scss/spruce.scss:
--------------------------------------------------------------------------------
1 | @forward 'config';
2 | @forward 'function';
3 | @forward 'mixin';
4 | @forward 'mixin/generator';
5 | @forward 'element';
6 | @forward 'form';
7 | @forward 'print';
8 | @forward 'plugin';
9 |
--------------------------------------------------------------------------------
/test/function/_config.scss:
--------------------------------------------------------------------------------
1 | @use 'true' as *;
2 | @use '../../scss/config' as *;
3 | @use '../../scss/function/config' as *;
4 |
5 | @include describe('config()') {
6 | @include it('should return "0.75em 1em" as the base padding for the button elements') {
7 | @include assert-equal(config('padding', $btn), 0.75em 1em);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/function/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'config';
2 |
--------------------------------------------------------------------------------
/test/sass.test.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const sassTrue = require('sass-true');
3 |
4 | const sassFile = path.join(__dirname, 'test.scss');
5 | sassTrue.runSass({ describe, it }, sassFile);
6 |
--------------------------------------------------------------------------------
/test/test.scss:
--------------------------------------------------------------------------------
1 | @forward 'function';
2 |
--------------------------------------------------------------------------------