@apolitical/component-library 0.0.2 → 0.0.4-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +2 -2
  2. package/style.css +1 -0
  3. package/styles/README.md +53 -0
  4. package/styles/base-styles/_accessibility.scss +31 -0
  5. package/styles/base-styles/_blockquotes.scss +31 -0
  6. package/styles/base-styles/_hr.scss +17 -0
  7. package/styles/base-styles/_index.scss +8 -0
  8. package/styles/base-styles/_links.scss +18 -0
  9. package/styles/base-styles/_lists.scss +30 -0
  10. package/styles/base-styles/_text.scss +83 -0
  11. package/styles/base-styles/_titles.scss +132 -0
  12. package/styles/base-styles/buttons/_buttons.scss +236 -0
  13. package/styles/base-styles/buttons/_icons.scss +24 -0
  14. package/styles/base-styles/buttons/_index.scss +1 -0
  15. package/styles/base-styles/buttons/_mixins.scss +30 -0
  16. package/styles/functions/_index.scss +1 -0
  17. package/styles/functions/_size-conversions.scss +8 -0
  18. package/styles/index.scss +6 -0
  19. package/styles/mixins/_animations.scss +7 -0
  20. package/styles/mixins/_backgrounds.scss +41 -0
  21. package/styles/mixins/_breakpoints.scss +18 -0
  22. package/styles/mixins/_index.scss +5 -0
  23. package/styles/mixins/_selectors.scss +8 -0
  24. package/styles/mixins/_transitions.scss +32 -0
  25. package/styles/reset/_index.scss +1 -0
  26. package/styles/reset/_reset.scss +62 -0
  27. package/styles/variables/_index.scss +3 -0
  28. package/styles/variables/assets/_assets.scss +4 -0
  29. package/styles/variables/assets/_index.scss +1 -0
  30. package/styles/variables/colors/_colors.scss +64 -0
  31. package/styles/variables/colors/_index.scss +1 -0
  32. package/styles/variables/colors/_theme.scss +62 -0
  33. package/styles/variables/sizes/_common.scss +3 -0
  34. package/styles/variables/sizes/_index.scss +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apolitical/component-library",
3
- "version": "0.0.2",
3
+ "version": "0.0.4-beta.2",
4
4
  "main": "./index.js",
5
5
  "types": "./index.d.ts",
6
6
  "exports": {
@@ -9,4 +9,4 @@
9
9
  "require": "./index.js"
10
10
  }
11
11
  }
12
- }
12
+ }
package/style.css CHANGED
@@ -0,0 +1 @@
1
+ h1{color:#4f43ba}
@@ -0,0 +1,53 @@
1
+ # Styles
2
+
3
+ ## `#root`
4
+
5
+ NextJS is very opinionated and won't let us style general elements (e.g. `h1`) - it wants elements to be 'pure', using classes or IDs to identify them (e.g. `.specific-section h1`).
6
+
7
+ To get the benefit of global base styling, where we get styling out of the box without having to explicitly add it to every element, we use the `#root` ID, on the `body`, to scope our global styling.
8
+
9
+ ### Adding a selector between `#root` and an element
10
+
11
+ One of the big benefits of using Sass is the `&` selector, which uses the full path to the element as the selector, letting us quickly add different states, like `:hover` and `:focus`, or show different styling inside a specific parent element.
12
+
13
+ This works as expected for states, but - because we scope everything to `#root` - using `&` would result in a selector like `.new-parent #root .element` - which is not going to be a valid path.
14
+
15
+ Instead, we've created a mixin, `selector-after-root`, to write the expected path.
16
+
17
+ `@include selector-after-root('.element')` will translate to the path `#root .element &`.
18
+
19
+ ---
20
+
21
+ ## `@layer`
22
+
23
+ We're using [CSS cascade layers (`@layer`)](https://www.smashingmagazine.com/2022/01/introduction-css-cascade-layers/) to organise our styles, to help us avoid specificity issues and to make it easier to find and update styles.
24
+
25
+ The `global.css` file defines the order of our layers, with the browser reset as the lowest.
26
+
27
+ If you need to overwrite CSS, you don't need to create an overly specific (and less performant) rule or add an `!important` (as an aside: please, please [never add an `!important`](https://dev.to/alvaromontoro/using-important-in-css-75c)) - just put the styling in a higher layer.
28
+
29
+ ---
30
+
31
+ ## `button` styling
32
+
33
+ We handle button styling through classes.
34
+
35
+ ### Variations
36
+
37
+ By default, buttons have `primary` styling (a purple background and white text) and don't need a `primary` class to be styled. Buttons can also be `secondary` (a ghost button with a purple border) or `tertiary` (no border or background color). There are additional variations for `destructive` (only available on primary buttons), `muted` (only available on secondary and tertiary buttons), and `disabled`.
38
+
39
+ ### Sizes
40
+
41
+ By default, buttons are `medium`. They don't need a class to be styled. Buttons can also be `small` or `large`.
42
+
43
+ ### Icons
44
+
45
+ Buttons can have an icon but - because we can't mix pre-processing and data passed into CSS for file names, like CSS variables or data attributes - icons on buttons need to be set up in advance with classes. The list of available icons is maintained in `base-styles/buttons/_icons.scss`. To add a new icon, add a new item to the list with arguments for the sizes on large, medium, and small buttons.
46
+
47
+ To add an icon to a button, add the class name `icon [file name of icon without file extension]`, e.g. `icon heart_empty`.
48
+
49
+ The icon file, with a matching name, should be uploaded to the `apolitical-assets` bucket on Google Cloud Platform. (Referred to as `$assets-bucket` in the styling.)
50
+
51
+ You can also pass `left` or `right` to position the icon - this will add margin between the text and the icon. e.g. `icon heart_empty right`.
52
+
53
+ You can change the icon on hover by adding a second file name, prepended with `hover_`, e.g. `icon heart_empty hover_heart` will change the heart icon from an outline to a filled in heart on hover.
@@ -0,0 +1,31 @@
1
+ @use 'sass:map';
2
+ @import './../functions',
3
+ './../mixins',
4
+ './../variables';
5
+
6
+ @layer base {
7
+ #root {
8
+
9
+ /* stylelint-disable no-descending-specificity */
10
+ a, button, input, textarea, [role="menu"], li[tabindex] {
11
+ &:focus, &:focus-visible {
12
+ outline: none;
13
+ box-shadow: $outline map.get($theme, 'default_focus');
14
+ }
15
+ &:active { box-shadow: none; }
16
+ }
17
+
18
+ [role="menu"], [role="menu"] a, li[tabindex] {
19
+ &:focus, &:focus-visible { box-shadow: $outline map.get($theme, 'default_focus') inset; }
20
+ }
21
+ /* stylelint-enable no-descending-specificity */
22
+
23
+ .hidden {
24
+ width: 0;
25
+ height: 0;
26
+ display: block;
27
+ overflow: hidden;
28
+ }
29
+ }
30
+ }
31
+
@@ -0,0 +1,31 @@
1
+ @use 'sass:map';
2
+ @import './../functions',
3
+ './../mixins',
4
+ './../variables';
5
+
6
+ $blockquote: ('margin-top': 20, 'margin-top_md': 30, 'margin-bottom': 10, 'margin-bottom_md': 20, 'padding-vertical': 4, 'padding-left': 24, 'font-size': 18, 'font-size_md': 20, 'line-height': 28, 'border-width': 6);
7
+
8
+ @layer base {
9
+ #root {
10
+ blockquote {
11
+ margin: px-to-rem(map.get($blockquote, 'margin-top')) 0 px-to-rem(map.get($blockquote, 'margin-bottom')) 0;
12
+ padding: px-to-rem(map.get($blockquote, 'padding-vertical')) 0 0 px-to-rem(map.get($blockquote, 'padding-left'));
13
+ font-size: px-to-rem(map.get($blockquote, 'font-size'));
14
+ color: map.get($theme, 'blockquote');
15
+ font-weight: 700;
16
+ font-style: italic;
17
+ line-height: px-to-rem(map.get($blockquote, 'line-height'));
18
+ border-left: px-to-rem(map.get($blockquote, 'border-width')) solid map.get($theme, 'blockquote_border');
19
+
20
+ p:last-of-type {
21
+ margin-bottom: 0;
22
+ }
23
+
24
+ @include breakpoint('min-width', 'md') {
25
+ margin-top: px-to-rem(map.get($blockquote, 'margin-top_md'));
26
+ margin-bottom: px-to-rem(map.get($blockquote, 'margin-bottom_md'));
27
+ font-size: px-to-rem(map.get($blockquote, 'font-size_md'));
28
+ }
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,17 @@
1
+ @use 'sass:map';
2
+ @import './../functions',
3
+ './../variables';
4
+
5
+ $hr: ('height': 1, 'margin-vertical': 40);
6
+
7
+ @layer base {
8
+ #root {
9
+ hr {
10
+ width: 100%;
11
+ height: px-to-rem(map.get($hr, 'height'));
12
+ background: map.get($theme, 'divider');
13
+ margin: px-to-rem(map.get($hr, 'margin-vertical')) auto;
14
+ border: none;
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,8 @@
1
+ @import 'accessibility',
2
+ 'blockquotes',
3
+ 'buttons',
4
+ 'hr',
5
+ 'links',
6
+ 'lists',
7
+ 'text',
8
+ 'titles';
@@ -0,0 +1,18 @@
1
+ @use 'sass:map';
2
+ @import './../variables';
3
+
4
+ @layer base {
5
+ #root {
6
+ a {
7
+ color: map.get($theme, 'link');
8
+ text-decoration: underline;
9
+ font-weight: 900;
10
+
11
+ &:hover,
12
+ &:focus,
13
+ &:active {
14
+ color: map.get($theme, 'link_hover');
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,30 @@
1
+ @use 'sass:map';
2
+ @import './../functions',
3
+ './../mixins';
4
+
5
+ $ul: ('margin-bottom': 16, 'padding-left': 40);
6
+ $li: ('margin-bottom': 18, 'margin-bottom_sm': 12, 'margin-bottom_md': 16);
7
+
8
+ @layer base {
9
+ #root {
10
+ ol, ul {
11
+ margin: 0 0 px-to-rem(map.get($ul, 'margin-bottom')) 0;
12
+ padding-left: px-to-rem(map.get($ul, 'padding-left'));
13
+ display: block;
14
+ }
15
+
16
+ li {
17
+ margin-bottom: px-to-rem(map.get($li, 'margin-bottom'));
18
+
19
+ @include selector-after-root('.text-small') { margin-bottom: px-to-rem(map.get($li, 'margin-bottom_sm')); }
20
+
21
+ @include selector-after-root('.text-medium') { margin-bottom: px-to-rem(map.get($li, 'margin-bottom_md')); }
22
+
23
+ &:last-child {
24
+ margin-bottom: 0;
25
+
26
+ p:last-child { margin-bottom: 0; }
27
+ }
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,83 @@
1
+ @use 'sass:map';
2
+ @import './../functions',
3
+ './../mixins',
4
+ './../variables';
5
+
6
+ $default: ('font-size': 16, 'font-size_md': 18, 'line-height': 24, 'line-height_md': 28, 'margin-bottom': 20); // The default size is the 'large paragraph' in Figma
7
+ $small: ('font-size': 12, 'font-size_md': 14, 'letter-spacing': 0.16, 'line-height': 16, 'margin-bottom': 16);
8
+ $medium: ('font-size': 14, 'font-size_md': 16, 'line-height': 16, 'line-height_md': 20, 'margin-bottom': 18);
9
+ $sup: ('font-size': 10);
10
+
11
+ @layer base {
12
+ #root {
13
+ p, li, small {
14
+ color: map.get($theme, 'text');
15
+ font-weight: 500;
16
+ }
17
+
18
+ p, li {
19
+ font-size: px-to-rem(map.get($default, 'font-size'));
20
+ line-height: px-to-rem(map.get($default, 'line-height'));
21
+
22
+ @include selector-after-root('.text-small') {
23
+ font-size: px-to-rem(map.get($small, 'font-size'));
24
+ line-height: px-to-rem(map.get($small, 'line-height'));
25
+ letter-spacing: px-to-rem(map.get($small, 'letter-spacing'));
26
+ }
27
+
28
+ @include selector-after-root('.text-medium') {
29
+ font-size: px-to-rem(map.get($medium, 'font-size'));
30
+ line-height: px-to-rem(map.get($medium, 'line-height'));
31
+ }
32
+
33
+ @include breakpoint('min-width', 'md') {
34
+ font-size: px-to-rem(map.get($default, 'font-size_md'));
35
+ line-height: px-to-rem(map.get($default, 'line-height_md'));
36
+
37
+ @include selector-after-root('.text-small') {
38
+ font-size: px-to-rem(map.get($small, 'font-size_md'));
39
+ }
40
+
41
+ @include selector-after-root('.text-medium') {
42
+ font-size: px-to-rem(map.get($medium, 'font-size_md'));
43
+ line-height: px-to-rem(map.get($medium, 'line-height_md'));
44
+ }
45
+ }
46
+ }
47
+
48
+ p {
49
+ margin-bottom: px-to-rem(map.get($default, 'margin-bottom'));
50
+
51
+ @include selector-after-root('.text-small') { margin-bottom: px-to-rem(map.get($small, 'margin-bottom')); }
52
+
53
+ @include selector-after-root('.text-medium') { margin-bottom: px-to-rem(map.get($medium, 'margin-bottom')); }
54
+ }
55
+
56
+ small {
57
+ font-size: px-to-rem(map.get($small, 'font-size'));
58
+ line-height: px-to-rem(map.get($small, 'line-height'));
59
+
60
+ @include breakpoint('min-width', 'md') {
61
+ font-size: px-to-rem(map.get($small, 'font-size_md'));
62
+ }
63
+ }
64
+
65
+ strong { font-weight: 900; }
66
+ em, i { font-style: italic; }
67
+
68
+ sup, sub {
69
+ font-size: px-to-rem(map.get($sup, 'font-size'));
70
+ line-height: normal;
71
+ }
72
+
73
+ sup {
74
+ vertical-align: super;
75
+ }
76
+
77
+ sub {
78
+ vertical-align: sub;
79
+ }
80
+
81
+ }
82
+
83
+ }
@@ -0,0 +1,132 @@
1
+ @use 'sass:map';
2
+ @import './../functions',
3
+ './../mixins',
4
+ './../variables';
5
+
6
+ $pre-title: ('font-size': 14, 'font-size_md': 16, 'letter-spacing': 1.28, 'line-height': 16, 'line-height_md': 20, 'margin-bottom': 24);
7
+ $title: ('font-size': 32, 'font-size_md': 52, 'letter-spacing': -0.16, 'line-height': 40, 'line-height_md': 64, 'margin-bottom': 16, 'margin-bottom_md': 32);
8
+ $h1: ('font-size': 28, 'font-size_md': 40, 'line-height': 32, 'line-height_md': 48, 'letter-spacing': -0.16, 'margin-bottom': 16);
9
+ $h2: ('font-size': 24, 'font-size_md': 28, 'line-height': 32, 'line-height_md': 40, 'margin-top': 40, 'margin-bottom': 16);
10
+ $h3: ('font-size': 20, 'font-size_md': 24, 'line-height': 28, 'line-height_md': 32, 'margin-top': 60, 'margin-bottom': 20);
11
+ $h4: ('font-size': 18, 'font-size_md': 20, 'line-height': 24, 'line-height_md': 28, 'margin-top': 40, 'margin-bottom': 20);
12
+ $h5: ('font-size': 12, 'font-size_md': 14, 'line-height': 16, 'margin-top': 16, 'margin-bottom': 16);
13
+ $h6: ('font-size': 12, 'font-size_md': 11, 'line-height': 16);
14
+ $subtitle: ('font-size': 20, 'font-size_md': 24, 'line-height': 28, 'line-height_md': 32, 'margin-bottom': 20);
15
+
16
+ @layer base {
17
+ #root {
18
+ .pre-title, h5, h6 {
19
+ color: map.get($theme, 'title');
20
+ text-transform: uppercase;
21
+ font-weight: 700;
22
+ }
23
+
24
+ .title, h1, h2, h3, h4 {
25
+ color: map.get($theme, 'title');
26
+ font-weight: 900;
27
+ }
28
+
29
+ .pre-title {
30
+ margin-bottom: px-to-rem(map.get($pre-title, 'margin-bottom'));
31
+ font-size: px-to-rem(map.get($pre-title, 'font-size'));
32
+ line-height: px-to-rem(map.get($pre-title, 'line-height'));
33
+ letter-spacing: px-to-rem(map.get($pre-title, 'letter-spacing'));
34
+ text-transform: uppercase;
35
+
36
+ @include breakpoint('min-width', 'md') {
37
+ font-size: px-to-rem(map.get($pre-title, 'font-size_md'));
38
+ line-height: px-to-rem(map.get($title, 'line-height_md'));
39
+ }
40
+ }
41
+
42
+ .title {
43
+ margin-bottom: px-to-rem(map.get($title, 'margin-bottom'));
44
+ font-size: px-to-rem(map.get($title, 'font-size'));
45
+ line-height: px-to-rem(map.get($title, 'line-height'));
46
+ letter-spacing: px-to-rem(map.get($title, 'letter-spacing'));
47
+
48
+ @include breakpoint('min-width', 'md') {
49
+ font-size: px-to-rem(map.get($title, 'font-size_md'));
50
+ line-height: px-to-rem(map.get($title, 'line-height_md'));
51
+ }
52
+ }
53
+
54
+ h1 {
55
+ margin-bottom: px-to-rem(map.get($h1, 'margin-bottom'));
56
+ font-size: px-to-rem(map.get($h1, 'font-size'));
57
+ line-height: px-to-rem(map.get($h1, 'line-height'));
58
+ letter-spacing: px-to-rem(map.get($h1, 'letter-spacing'));
59
+
60
+ @include breakpoint('min-width', 'md') {
61
+ font-size: px-to-rem(map.get($h1, 'font-size_md'));
62
+ line-height: px-to-rem(map.get($h1, 'line-height_md'));
63
+ }
64
+ }
65
+
66
+ h2 {
67
+ margin: px-to-rem(map.get($h2, 'margin-top')) 0 px-to-rem(map.get($h2, 'margin-bottom')) 0;
68
+ font-size: px-to-rem(map.get($h2, 'font-size'));
69
+ line-height: px-to-rem(map.get($h2, 'line-height'));
70
+
71
+ @include breakpoint('min-width', 'md') {
72
+ font-size: px-to-rem(map.get($h2, 'font-size_md'));
73
+ line-height: px-to-rem(map.get($h2, 'line-height_md'));
74
+ }
75
+ }
76
+
77
+ h3 {
78
+ margin: px-to-rem(map.get($h3, 'margin-top')) 0 px-to-rem(map.get($h3, 'margin-bottom')) 0;
79
+ font-size: px-to-rem(map.get($h3, 'font-size'));
80
+ line-height: px-to-rem(map.get($h3, 'line-height'));
81
+
82
+ @include breakpoint('min-width', 'md') {
83
+ font-size: px-to-rem(map.get($h3, 'font-size_md'));
84
+ line-height: px-to-rem(map.get($h3, 'line-height_md'));
85
+ }
86
+ }
87
+
88
+ h4 {
89
+ margin: px-to-rem(map.get($h4, 'margin-top')) 0 px-to-rem(map.get($h4, 'margin-bottom')) 0;
90
+ font-size: px-to-rem(map.get($h4, 'font-size'));
91
+ line-height: px-to-rem(map.get($h4, 'line-height'));
92
+
93
+ @include breakpoint('min-width', 'md') {
94
+ font-size: px-to-rem(map.get($h4, 'font-size_md'));
95
+ line-height: px-to-rem(map.get($h4, 'line-height_md'));
96
+ }
97
+ }
98
+
99
+
100
+ h5 {
101
+ margin: px-to-rem(map.get($h5, 'margin-top')) 0 px-to-rem(map.get($h5, 'margin-bottom')) 0;
102
+ font-size: px-to-rem(map.get($h5, 'font-size'));
103
+ line-height: px-to-rem(map.get($h5, 'line-height'));
104
+
105
+ @include breakpoint('min-width', 'md') {
106
+ font-size: px-to-rem(map.get($h5, 'font-size_md'));
107
+ }
108
+ }
109
+
110
+ h6 {
111
+ font-size: px-to-rem(map.get($h6, 'font-size'));
112
+ line-height: px-to-rem(map.get($h6, 'line-height'));
113
+
114
+ @include breakpoint('min-width', 'md') {
115
+ font-size: px-to-rem(map.get($h6, 'font-size_md'));
116
+ }
117
+ }
118
+
119
+ .subtitle {
120
+ margin-bottom: px-to-rem(map.get($subtitle, 'margin-bottom'));
121
+ font-size: px-to-rem(map.get($subtitle, 'font-size'));
122
+ color: map.get($theme, 'subtitle');
123
+ font-weight: 400;
124
+ line-height: px-to-rem(map.get($subtitle, 'line-height'));
125
+
126
+ @include breakpoint('min-width', 'md') {
127
+ font-size: px-to-rem(map.get($subtitle, 'font-size_md'));
128
+ line-height: px-to-rem(map.get($subtitle, 'line-height_md'));
129
+ }
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,236 @@
1
+ @use 'sass:map';
2
+ @use 'sass:meta';
3
+ @import './../../functions',
4
+ './../../mixins',
5
+ './../../variables';
6
+ @import './icons', './mixins';
7
+
8
+ // The button defaults to the primary variant and medium styling so are not added as classes - they live on the default `button` styling
9
+ $large: ('font-size': 18, 'padding-top': 14, 'padding-horizontal': 24, 'padding-bottom': 16, 'icon-width': 18, 'icon-offset': 2, 'icon-gutter': 12);
10
+ $medium: ('font-size': 16, 'padding-top': 10, 'padding-horizontal': 24, 'padding-bottom': 12, 'icon-width': 16, 'icon-offset': 2, 'icon-gutter': 10);
11
+ $small: ('font-size': 14, 'padding-top': 8, 'padding-horizontal': 16, 'padding-bottom': 10, 'icon-width': 12, 'icon-offset': 2, 'icon-gutter': 8);
12
+ $sizes: ('large', $large), ('small', $small);
13
+ $secondary: ('border': 1);
14
+
15
+ // The tertiary styling has its own padding; regardless of size, we add a tiny amount of padding to accommodate
16
+ // the focus outline and to ensure any icons are centered
17
+ $tertiary: ('padding-top': 1.6, 'padding-horizontal': 8,'padding-bottom': 3.2);
18
+ $animation: ('scale': 1.1, 'duration': 0.5, 'iteration': 1);
19
+
20
+ @layer base {
21
+ #root {
22
+ button, .button {
23
+ background: map.get($theme, 'button_primary_bg');
24
+ padding: px-to-rem(map.get($medium, 'padding-top')) px-to-rem(map.get($medium, 'padding-horizontal'))
25
+ px-to-rem(map.get($medium, 'padding-bottom')) px-to-rem(map.get($medium, 'padding-horizontal'));
26
+ position: relative;
27
+ text-align: center;
28
+ font-size: px-to-rem(map.get($medium, 'font-size'));
29
+ color: map.get($theme, 'button_primary');
30
+ font-weight: 700;
31
+ text-decoration: none;
32
+ border-radius: 360rem; // Completely round corners. This could be any large number but 360 evokes a circle well!
33
+ border: none;
34
+ cursor: pointer;
35
+ box-sizing: border-box;
36
+ display: inline-block;
37
+
38
+ &:hover {
39
+ background: map.get($theme, 'button_primary_bg_hover');
40
+ }
41
+
42
+ &:active {
43
+ background: map.get($theme, 'button_primary_bg_active');
44
+ }
45
+
46
+ &:focus, &:focus-visible {
47
+ background: map.get($theme, 'button_primary_bg_focus');
48
+ }
49
+
50
+ @each $size, $styles in $sizes {
51
+ &.#{$size} {
52
+ padding: px-to-rem(map.get($styles, 'padding-top')) px-to-rem(map.get($styles, 'padding-horizontal'))
53
+ px-to-rem(map.get($styles, 'padding-bottom')) px-to-rem(map.get($styles, 'padding-horizontal'));
54
+ font-size: px-to-rem(map.get($styles, 'font-size'));
55
+
56
+ &.icon {
57
+ margin-top: px-to-rem(map.get($styles, 'icon-offset'));
58
+
59
+ &.left::before { margin-right: px-to-rem(map.get($styles, 'icon-gutter')); }
60
+ &.right::before { margin-left: px-to-rem(map.get($styles, 'icon-gutter')); }
61
+ }
62
+ }
63
+ }
64
+
65
+ &.full-width {
66
+ width: 100%;
67
+ }
68
+
69
+ &.destructive {
70
+ &, &:hover, &:active, &:focus, &:focus-visible {
71
+ background: map.get($theme, 'button_primary_destructive_bg');
72
+ color: map.get($theme, 'button_primary_destructive');
73
+ }
74
+
75
+ &:hover, &:active {
76
+ background: map.get($theme, 'button_primary_destructive_bg_hover');
77
+ }
78
+
79
+ &:focus, &:focus-visible {
80
+ box-shadow: $outline map.get($theme, 'button_primary_destructive_box-shadow');
81
+ }
82
+
83
+ }
84
+
85
+ &.secondary {
86
+ background: map.get($theme, 'button_secondary_bg');
87
+ color: map.get($theme, 'button_secondary');
88
+ border: px-to-rem(map.get($secondary, 'border')) solid map.get($theme, 'button_secondary_border');
89
+
90
+ &:hover {
91
+ background: map.get($theme, 'button_secondary_bg_hover');
92
+ }
93
+
94
+ &:active, &:focus, &:focus-visible {
95
+ color: map.get($theme, 'button_secondary_focus');
96
+ border-color: map.get($theme, 'button_secondary_border_focus');
97
+ }
98
+
99
+ &:active {
100
+ background: map.get($theme, 'button_secondary_bg_active');
101
+ }
102
+
103
+ &:focus, &:focus-visible {
104
+ background: map.get($theme, 'button_secondary_bg_focus');
105
+ }
106
+
107
+ &.muted {
108
+ border-color: map.get($theme, 'button_secondary_border_muted');
109
+
110
+ &, &:hover, &:active, &:focus, &:focus-visible {
111
+ background: map.get($theme, 'button_secondary_bg_muted');
112
+ color: map.get($theme, 'button_secondary_muted');
113
+ }
114
+
115
+ &:hover, &:focus, &:focus-visible {
116
+ color: map.get($theme, 'button_secondary_muted_hover');
117
+ }
118
+ }
119
+
120
+ &.disabled {
121
+ &, &:hover, &:active, &:focus, &:focus-visible {
122
+ background: map.get($theme, 'button_secondary_bg_disabled');
123
+ color: map.get($theme, 'button_secondary_disabled');
124
+ border-color: map.get($theme, 'button_secondary_border_disabled');
125
+ }
126
+ }
127
+ }
128
+
129
+ &.tertiary {
130
+ @include transition(color text-decoration-color);
131
+
132
+ padding: px-to-rem(map.get($tertiary, 'padding-top')) px-to-rem(map.get($tertiary, 'padding-horizontal'))
133
+ px-to-rem(map.get($tertiary, 'padding-bottom')) px-to-rem(map.get($tertiary, 'padding-horizontal'));
134
+ bottom: px-to-rem(map.get($tertiary, 'padding-top'));
135
+ right: px-to-rem(map.get($tertiary, 'padding-horizontal'));
136
+ text-decoration: underline;
137
+ text-decoration-color: transparent;
138
+
139
+ &, &:focus, &:focus-visible {
140
+ background: map.get($theme, 'button_tertiary_bg');
141
+ color: map.get($theme, 'button_tertiary');
142
+ text-decoration-color: map.get($theme, 'button_tertiary_text-decoration');
143
+ }
144
+
145
+ &:hover, &:active {
146
+ text-decoration-color: currentcolor;
147
+ }
148
+
149
+ &:hover {
150
+ color: map.get($theme, 'button_tertiary_hover');
151
+ }
152
+
153
+ &:active {
154
+ color: map.get($theme, 'button_tertiary_active');
155
+ }
156
+
157
+ &.muted {
158
+ color: map.get($theme, 'button_tertiary_muted');
159
+
160
+ &:hover, &:focus, &:focus-visible {
161
+ color: map.get($theme, 'button_tertiary_muted_hover');
162
+ }
163
+ }
164
+
165
+ &.disabled {
166
+ &, &:hover, &:active, &:focus, &:focus-visible {
167
+ background: map.get($theme, 'button_tertiary_disabled_bg');
168
+ color: map.get($theme, 'button_tertiary_disabled');
169
+ text-decoration-color: map.get($theme, 'button_tertiary_disabled_text-decoration');
170
+ }
171
+ }
172
+
173
+ }
174
+
175
+ &.icon {
176
+ display: inline-flex;
177
+ flex-flow: row;
178
+ align-content: center;
179
+ justify-content: center;
180
+
181
+ &::before {
182
+ content: '';
183
+ background: currentcolor;
184
+ width: px-to-rem(map.get($medium, 'icon-width'));
185
+ height: px-to-rem(map.get($medium, 'icon-width'));
186
+ display: block;
187
+ }
188
+
189
+ &.right::before { order: 2; }
190
+
191
+ @each $icon, $large-icon, $medium-icon, $small-icon in $icons {
192
+ $icon-sizes: ('large', $large-icon), ('medium', $medium-icon), ('small', $small-icon);
193
+
194
+ %icon-#{$icon} {
195
+ @include icon-styling($icon, $icon-sizes);
196
+ }
197
+
198
+ &.#{$icon} {
199
+ @extend %icon-#{$icon};
200
+ }
201
+
202
+ &.hover_#{$icon} {
203
+ &:hover, &:focus, &:focus-visible {
204
+ @extend %icon-#{$icon};
205
+ }
206
+ }
207
+
208
+ }
209
+
210
+ &.animate {
211
+ @include animate {
212
+ @keyframes bounce {
213
+ 0%, 100% { transform: scale(1); }
214
+
215
+ 70% {
216
+ transform: scale(#{map.get($animation, 'scale')});
217
+ }
218
+ }
219
+
220
+ &:focus, &:active, &.do-animation {
221
+ &::before { animation: bounce #{map.get($animation, 'duration')}s #{map.get($animation, 'iteration')}; }
222
+ }
223
+
224
+ }
225
+ }
226
+ }
227
+
228
+ &.disabled {
229
+ cursor: not-allowed;
230
+ opacity: 0.3;
231
+ }
232
+
233
+ }
234
+ }
235
+ }
236
+
@@ -0,0 +1,24 @@
1
+ // Because we can't mix pre-processing and data passed into CSS for file names, like CSS variables or data attributes,
2
+ // icons on buttons need to be set up in advance. To add a new icon, add the name of the file, without the file extension,
3
+ // to the $icons list below.
4
+ // The format is: ('file-name', (small width, small height, small offset, small gutter),
5
+ // (medium width, medium height, medium offset, medium gutter), (large width, large height, large offset, large gutter))
6
+ // By default, icons on buttons use the same width, height, and margin set on the sized lists in `_buttons`, e.g.
7
+ // large buttons default to 18px width, set in $large. If the default values are correct, put `d` as the value.
8
+ // Otherwise, put the value in pixels.
9
+
10
+ $icons: ('arrow_left', (d, d, 3, 8), (d, d, 3, 8), (d, d, 4, 7)),
11
+ ('arrow_right', (d, d, 3, 8), (d, d, 3, 8), (d, d, 4, 7)),
12
+ ('arrow_corner-down_right', (12, 12, 6, 8), (12, 12, 4, 12), (d, d, d, d)),
13
+ ('arrow_corner-down_left', (12, 12, 6, 8), (12, 12, 4, 12), (d, d, d, d)),
14
+ ('arrow-down', (d, d, 4, 10), (d, d, 3, 8), (d, d, 4, d)),
15
+ ('chevron_left', (16, 16, 4, 8), (14, 14, 3, 8), (14, 14, d, d)),
16
+ ('chevron_right', (16, 16, 4, 8), (14, 14, 3, 8), (14, 14, d, d)),
17
+ ('heart', (22, 22, 0, 10), (18, 18, 1, d), (d, d, 3, 10)),
18
+ ('heart_empty', (22, 22, 0, 10), (18, 18, 1, d), (d, d, 3, 10)),
19
+ ('navigation_arrow', (d, d, 4, 10), (d, d, d, d), (14, 14, d, 10)),
20
+ ('pencil', (d, d, d, d), (d, d, d, 11), (d, d, 3, 9)),
21
+ ('speech-bubble_square_thick', (22, 22, 0, 10), (18, 18, 1, d), (d, d, 3, 10)),
22
+ ('star', (16, 16, 0, 0), (d, d, 0, 0), (16, 16, 0, 0)),
23
+ ('star_empty', (16, 16, 0, 0), (d, d, 0, 0), (16, 16, 0, 0)),
24
+ ('tick', (14, 11, 8, 10), (12, 9, 7, 8), (9, 7, 6, 6));
@@ -0,0 +1 @@
1
+ @import 'buttons';
@@ -0,0 +1,30 @@
1
+ @use 'sass:list';
2
+ @import './../../functions';
3
+
4
+ @mixin icon-styling($icon, $icon-sizes) {
5
+ &::before { @include image('icons/#{$icon}.svg', true); }
6
+
7
+ @each $size, $styles in $icon-sizes {
8
+ @include icon-classes(width, list.nth($styles, 1), $size);
9
+ @include icon-classes(height, list.nth($styles, 2), $size);
10
+ @include icon-classes(margin-top, list.nth($styles, 3), $size);
11
+ @include icon-classes(margin-right, list.nth($styles, 4), $size, 'left');
12
+ @include icon-classes(margin-left, list.nth($styles, 4), $size, 'right');
13
+ }
14
+ }
15
+
16
+ // If the $value passed in isn't d (for default), render the styling on the passed-in class (if it exists)
17
+ // and the size (if it isn't medium, which is default)
18
+ @mixin icon-classes($property, $value, $size: 'medium', $class: false) {
19
+ @if $value != 'd' {
20
+ $classname: '';
21
+
22
+ @if $class { $classname: '.#{$class}' }
23
+
24
+ @if $size != 'medium' { $classname: $classname + '.#{$size}'; }
25
+
26
+ &#{$classname}:before {
27
+ #{$property}: px-to-rem($value);
28
+ }
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ @import 'size-conversions';
@@ -0,0 +1,8 @@
1
+ @use 'sass:math';
2
+
3
+ // Change px to rem
4
+ // We want to use rem for accessibility, but Figma uses px (which is also a bit easier for us
5
+ // to read and reason about). This mixin does the conversion for us.
6
+ @function px-to-rem($size, $baseTextSize: 16) {
7
+ @return math.div($size, $baseTextSize) + rem;
8
+ }
@@ -0,0 +1,6 @@
1
+ @import 'functions',
2
+ 'variables',
3
+ 'mixins',
4
+
5
+ 'reset',
6
+ 'base-styles';
@@ -0,0 +1,7 @@
1
+ @mixin animate() {
2
+ @at-root {
3
+ @media (prefers-reduced-motion: no-preference) {
4
+ @content;
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,41 @@
1
+ @use 'sass:string';
2
+ @import './../variables';
3
+
4
+ // Handle the repeated code for CSS backgrounds. By default, we assume the image is a background image,
5
+ // but if you pass in $is-mask: true, it will be a mask-image instead.
6
+ // If no 'http' is in the image path, we will prepend the image bucket URL, or the asset bucket URL for masks.
7
+ // The asset paths can be passed in directly as part of `$image` to avoid this.
8
+
9
+ @mixin image($image, $is-mask: false, $size: 100% auto, $repeat: no-repeat, $position: 0 0) {
10
+ @if string.index($image, 'http') {
11
+ $image: $image;
12
+ } @else {
13
+ @if $is-mask {
14
+ $image: $assets-bucket + $image;
15
+ } @else {
16
+ $image: $image-bucket + $image;
17
+ }
18
+ }
19
+
20
+ @if $is-mask {
21
+ @include mask-image($image, $size, $repeat);
22
+ } @else {
23
+ @include background-image($image, $size, $repeat, $position);
24
+ }
25
+ }
26
+
27
+ @mixin background-image($image, $size: 100% auto, $repeat: no-repeat, $position: 0 0) {
28
+ background-image: url($image);
29
+ background-repeat: $repeat;
30
+ background-position: $position;
31
+ background-size: $size;
32
+ display: block;
33
+ }
34
+
35
+ @mixin mask-image($image, $size: 100% auto, $repeat: no-repeat, $position: 0 0) {
36
+ mask-image: url($image);
37
+ mask-repeat: $repeat;
38
+ mask-position: $position;
39
+ mask-size: $size;
40
+ display: block;
41
+ }
@@ -0,0 +1,18 @@
1
+ @use 'sass:map';
2
+
3
+ $breakpoint-sizes: (
4
+ 'md': 769
5
+ );
6
+
7
+ // This mixin lets us use fast references for common breakpoints, using the map above,
8
+ // and lets us reference custom breakpoints by passing in a number. It lets us code
9
+ // breakpoints a bit faster by not having to type out the full media query each time.
10
+ @mixin breakpoint($type, $breakpoint) {
11
+ @if map-has-key($breakpoint-sizes, $breakpoint) {
12
+ $breakpoint: map.get($breakpoint-sizes, $breakpoint);
13
+ }
14
+
15
+ @media only screen and (#{$type}: #{$breakpoint}px) {
16
+ @content;
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ @import 'animations',
2
+ 'backgrounds',
3
+ 'breakpoints',
4
+ 'selectors',
5
+ 'transitions';
@@ -0,0 +1,8 @@
1
+ // We need to scope everything to #root, but that stops us adding a parent before the
2
+ // current selector. This mixin puts the selector path in the expected order. See the README for
3
+ // more details.
4
+ @mixin selector-after-root($selector) {
5
+ @at-root #root #{selector-replace(&, '#root', $selector)} {
6
+ @content;
7
+ }
8
+ }
@@ -0,0 +1,32 @@
1
+ @use 'sass:list';
2
+
3
+ // For accessibility, we need to respect the user's prefers-reduced-motion setting.
4
+ // Find out more: https://css-tricks.com/introduction-reduced-motion-media-query/
5
+ @mixin transition($properties, $duration: 0.2s, $ease: ease-in-out, $delay: 0s) {
6
+ @media (prefers-reduced-motion: no-preference) {
7
+ $transition: ();
8
+
9
+ @for $property from 1 through length($properties) {
10
+ @for $i from 0 to (length($properties)) - (length($duration)) {
11
+ $duration: list.join($duration, list.nth($duration, -1));
12
+ }
13
+
14
+ @for $i from 0 to (length($properties)) - (length($ease)) {
15
+ $ease: list.join($ease, list.nth($ease, -1));
16
+ }
17
+
18
+ @for $i from 0 to (length($properties)) - (length($delay)) {
19
+ $delay: list.join($delay, list.nth($delay, -1));
20
+ }
21
+
22
+ $transition: list.append(
23
+ $transition,
24
+ (list.nth($properties, $property) list.nth($duration, $property) list.nth($ease, $property) list.nth($delay, $property)),
25
+ $separator: comma
26
+ );
27
+ }
28
+
29
+ transition: $transition;
30
+ }
31
+
32
+ }
@@ -0,0 +1 @@
1
+ @import 'reset';
@@ -0,0 +1,62 @@
1
+ /* A reset for all browsers, based on Eric Meyer's CSS reset
2
+ * https://meyerweb.com/eric/tools/css/reset/ */
3
+
4
+ @layer reset {
5
+ html, body, div, span, applet, object, iframe,
6
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
7
+ a, abbr, acronym, address, big, cite, code,
8
+ del, dfn, em, img, ins, kbd, q, s, samp,
9
+ small, strike, strong, sub, sup, tt, var,
10
+ b, u, i, center,
11
+ dl, dt, dd, ol, ul, li,
12
+ fieldset, form, label, legend,
13
+ table, caption, tbody, tfoot, thead, tr, th, td,
14
+ article, aside, canvas, details, embed,
15
+ figure, figcaption, footer, header, hgroup,
16
+ menu, nav, output, ruby, section, summary,
17
+ time, mark, audio, video {
18
+ margin: 0;
19
+ padding: 0;
20
+ border: 0;
21
+ font: inherit;
22
+ font-size: 100%;
23
+ vertical-align: baseline;
24
+ }
25
+
26
+ article, aside, details, figcaption, figure,
27
+ footer, header, hgroup, menu, nav, section {
28
+ display: block;
29
+ }
30
+
31
+ html {
32
+ font-size: 100%;
33
+ }
34
+
35
+ body {
36
+ -moz-osx-font-smoothing: grayscale;
37
+ -webkit-font-smoothing: antialiased;
38
+
39
+ --webkit-text-size-adjust: 100%;
40
+
41
+ text-size-adjust: 100%;
42
+ text-rendering: optimizelegibility;
43
+ line-height: 1;
44
+ }
45
+
46
+ ul { list-style: disc; }
47
+ ol { list-style: decimal; }
48
+
49
+ blockquote, q {
50
+ quotes: none;
51
+
52
+ &::before, &::after {
53
+ content: '';
54
+ content: none;
55
+ }
56
+ }
57
+
58
+ table {
59
+ border-collapse: collapse;
60
+ border-spacing: 0;
61
+ }
62
+ }
@@ -0,0 +1,3 @@
1
+ @import 'assets',
2
+ 'colors',
3
+ 'sizes';
@@ -0,0 +1,4 @@
1
+ $storage: 'https://storage.googleapis.com/';
2
+ $image-bucket: $storage + 'apolitical-images/assets/';
3
+ $assets-bucket: $storage + 'apolitical-assets/';
4
+
@@ -0,0 +1 @@
1
+ @import 'assets';
@@ -0,0 +1,64 @@
1
+ // The default colours are defined here and referenced in `theme.scss`, which is what is used across the app.
2
+ // This lets us use a meaningful name across the platform, such as `background` or `title`, rather than use a colour name,
3
+ // like b900, which is likely to change in the future, but still have a clear consistency with Figma, which uses colour
4
+ // names.
5
+
6
+ $default-colors: (
7
+ 'white': rgb(255 255 255 / 100%),
8
+ 'black': rgb(0 0 0 / 100%),
9
+ 'sand': rgb(252 250 243 / 100%),
10
+
11
+ 'b50': rgb(248 247 255 / 100%),
12
+ 'b100': rgb(222 220 250 / 100%),
13
+ 'b200': rgb(197 194 245 / 100%),
14
+ 'b300': rgb(175 171 239 / 100%),
15
+ 'b500': rgb(124 114 225 / 100%),
16
+ 'b600': rgb(101 89 212 / 100%),
17
+ 'b700': rgb(79 67 186 / 100%),
18
+ 'b800': rgb(57 45 135 / 100%),
19
+ 'b900': rgb(33 24 77 / 100%),
20
+
21
+ 'g50': rgb(235 250 246 / 100%),
22
+ 'g100': rgb(195 240 228 / 100%),
23
+ 'g300': rgb(124 220 197 / 100%),
24
+ 'g600': rgb(43 137 117 / 100%),
25
+ 'g700': rgb(28 104 88 / 100%),
26
+ 'g900': rgb(10 38 33 / 100%),
27
+
28
+ 'n50': rgb(240 245 255 / 100%),
29
+ 'n100': rgb(215 224 239 / 100%),
30
+ 'n200': rgb(192 203 223 / 100%),
31
+ 'n500': rgb(117 131 160 / 100%),
32
+ 'n600': rgb(89 101 128 / 100%),
33
+ 'n700': rgb(64 74 97 / 100%),
34
+ 'n800': rgb(42 49 65 / 100%),
35
+ 'n900': rgb(21 25 33 / 100%),
36
+
37
+ 'p50': rgb(255 242 255 / 100%),
38
+ 'p100': rgb(238 200 237 / 100%),
39
+ 'p300': rgb(203 130 197 / 100%),
40
+ 'p500': rgb(166 55 152 / 100%),
41
+ 'p900': rgb(66 1 55 / 100%),
42
+
43
+ 'y50': rgb(255 248 232 / 100%),
44
+ 'y100': rgb(255 238 193 / 100%),
45
+ 'y300': rgb(255 230 139 / 100%),
46
+ 'y700': rgb(122 81 5 / 100%),
47
+ 'y900': rgb(48 29 0 / 100%),
48
+
49
+ 'error50': rgb(255 242 242 / 100%),
50
+ 'error600': rgb(195 65 65 / 100%),
51
+ 'error700': rgb(155 36 36 / 100%),
52
+
53
+ 'info50': rgb(240 245 247 / 100%),
54
+ 'info500': rgb(30 117 166 / 100%),
55
+ 'info600': rgb(20 95 139 / 100%),
56
+
57
+ 'success50': rgb(235 247 239 / 100%),
58
+ 'success500': rgb(60 124 82 / 100%),
59
+ 'success600': rgb(38 103 60 / 100%),
60
+
61
+ 'warning50': rgb(255 242 230 / 100%),
62
+ 'warning500': rgb(220 121 29 / 100%),
63
+ 'warning700': rgb(137 71 11 / 100%),
64
+ );
@@ -0,0 +1 @@
1
+ @import 'theme';
@@ -0,0 +1,62 @@
1
+ @use 'sass:map';
2
+ @import 'colors';
3
+
4
+
5
+ $c: $default-colors;
6
+ $theme: (
7
+ default_focus: map.get($c, 'b100'),
8
+
9
+ background: map.get($c, 'white'),
10
+
11
+ title: map.get($c, 'n900'),
12
+ subtitle: map.get($c, 'n800'),
13
+ text: map.get($c, 'n900'),
14
+
15
+ link: map.get($c, 'b700'),
16
+ link_hover: map.get($c, 'b800'),
17
+
18
+ button_primary: map.get($c, 'white'),
19
+ button_primary_bg: map.get($c, 'b700'),
20
+ button_primary_bg_hover: map.get($c, 'b500'),
21
+ button_primary_bg_active: map.get($c, 'b800'),
22
+ button_primary_bg_focus: map.get($c, 'b600'),
23
+ button_primary_bg_disabled: map.get($c, 'b700'),
24
+ button_primary_destructive: map.get($c, 'white'),
25
+ button_primary_destructive_bg: map.get($c, 'error600'),
26
+ button_primary_destructive_bg_hover: map.get($c, 'error700'),
27
+ button_primary_destructive_box-shadow: map.get($c, 'error50'),
28
+
29
+ button_secondary: map.get($c, 'b700'),
30
+ button_secondary_focus: map.get($c, 'white'),
31
+ button_secondary_muted: map.get($c, 'n600'),
32
+ button_secondary_muted_hover: map.get($c, 'b700'),
33
+ button_secondary_disabled: map.get($c, 'b700'),
34
+ button_secondary_bg: map.get($c, 'white'),
35
+ button_secondary_bg_hover: map.get($c, 'b200'),
36
+ button_secondary_bg_focus: map.get($c, 'b600'),
37
+ button_secondary_bg_active: map.get($c, 'b800'),
38
+ button_secondary_bg_muted: map.get($c, 'white'),
39
+ button_secondary_bg_disabled: map.get($c, 'white'),
40
+ button_secondary_border: map.get($c, 'b700'),
41
+ button_secondary_border_focus: transparent,
42
+ button_secondary_border_muted: map.get($c, 'n200'),
43
+ button_secondary_border_disabled: map.get($c, 'b700'),
44
+
45
+ button_tertiary: map.get($c, 'b700'),
46
+ button_tertiary_hover: map.get($c, 'b500'),
47
+ button_tertiary_active: map.get($c, 'b800'),
48
+ button_tertiary_muted: map.get($c, 'n600'),
49
+ button_tertiary_muted_hover: map.get($c, 'b500'),
50
+ button_tertiary_disabled: map.get($c, 'b700'),
51
+ button_tertiary_bg: none,
52
+ button_tertiary_disabled_bg: none,
53
+ button_tertiary_text-decoration: transparent,
54
+ button_tertiary_disabled_text-decoration: transparent,
55
+
56
+
57
+ blockquote: map.get($c, 'n900'),
58
+ 'blockquote_border': map.get($c, 'g300'),
59
+
60
+ divider: map.get($c, 'n100'),
61
+ );
62
+
@@ -0,0 +1,3 @@
1
+ @import './../../functions';
2
+
3
+ $outline: 0 0 0 px-to-rem(4);
@@ -0,0 +1 @@
1
+ @import 'common';