@financial-times/o-private-foundation 1.0.0
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.
- package/README.md +108 -0
- package/demos/src/demo.mustache +0 -0
- package/demos/src/demo.scss +1 -0
- package/main.js +60 -0
- package/main.scss +32 -0
- package/origami.json +26 -0
- package/package.json +33 -0
- package/src/scss/_brand.scss +68 -0
- package/src/scss/_tokens.scss +15 -0
- package/src/scss/_variables.scss +1 -0
- package/src/scss/o-buttons/custom-themes.scss +631 -0
- package/src/scss/o-buttons/main.scss +255 -0
- package/src/scss/o-colors/_functions.scss +354 -0
- package/src/scss/o-colors/_palette.scss +2 -0
- package/src/scss/o-colors/_variables.scss +50 -0
- package/src/scss/o-colors/main.scss +6 -0
- package/src/scss/o-grid/_functions.scss +90 -0
- package/src/scss/o-grid/_mixins.scss +384 -0
- package/src/scss/o-grid/_variables.scss +105 -0
- package/src/scss/o-grid/main.scss +5 -0
- package/src/scss/o-icons/_mixins.scss +71 -0
- package/src/scss/o-icons/main.scss +1 -0
- package/src/scss/o-normalise/_mixins.scss +126 -0
- package/src/scss/o-normalise/_variables.scss +10 -0
- package/src/scss/o-normalise/main.scss +4 -0
- package/src/scss/o-spacing/_variables.scss +29 -0
- package/src/scss/o-spacing/main.scss +28 -0
- package/src/scss/o-typography/main.scss +428 -0
- package/src/scss/o-visual-effects/main.scss +4 -0
- package/src/scss/o-visual-effects/scss/_shadows.scss +31 -0
- package/src/scss/o-visual-effects/scss/_variables.scss +13 -0
- package/src/scss/tokens/core.scss +563 -0
- package/src/scss/tokens/internal.scss +456 -0
- package/src/scss/tokens/professional.scss +537 -0
- package/src/scss/tokens/sustainable-views.scss +493 -0
- package/src/scss/tokens/whitelabel.scss +465 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
@import './custom-themes';
|
|
2
|
+
|
|
3
|
+
/// Create a single button with a custom class.
|
|
4
|
+
/// @param {Map} $opts [('type': 'null', 'theme': null, 'size': null, 'icon': null, 'icon-only': false)] - The kind of button styles to output.
|
|
5
|
+
@mixin oPrivateButtonsContent(
|
|
6
|
+
$opts: (
|
|
7
|
+
'type': null,
|
|
8
|
+
'theme': null,
|
|
9
|
+
'size': null,
|
|
10
|
+
'icon': null,
|
|
11
|
+
'icon-only': false,
|
|
12
|
+
)
|
|
13
|
+
) {
|
|
14
|
+
$theme: map-get($opts, 'theme');
|
|
15
|
+
$theme: if($theme, $theme, 'standard');
|
|
16
|
+
$type: map-get($opts, 'type');
|
|
17
|
+
$type: if($type, $type, 'primary');
|
|
18
|
+
|
|
19
|
+
@include _oPrivateButtonsBase($opts);
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
Type and theme specific styles.
|
|
23
|
+
FT Professional was treated as a theme, but is now considered a brand in tokens.
|
|
24
|
+
*/
|
|
25
|
+
$sub-brand: null;
|
|
26
|
+
@if $theme == 'professional' {
|
|
27
|
+
$theme: 'standard';
|
|
28
|
+
$sub-brand: 'professional';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@if $theme == 'professional-inverse' {
|
|
32
|
+
$theme: 'inverse';
|
|
33
|
+
$sub-brand: 'professional';
|
|
34
|
+
}
|
|
35
|
+
@include _oPrivateButtonsTheme(
|
|
36
|
+
(
|
|
37
|
+
'color':
|
|
38
|
+
oPrivateFoundationGet(
|
|
39
|
+
'_o3-button-' + $type + '-' + $theme + '-color',
|
|
40
|
+
$sub-brand
|
|
41
|
+
),
|
|
42
|
+
'background':
|
|
43
|
+
oPrivateFoundationGet(
|
|
44
|
+
'_o3-button-' + $type + '-' + $theme + '-background',
|
|
45
|
+
$sub-brand
|
|
46
|
+
),
|
|
47
|
+
'border':
|
|
48
|
+
oPrivateFoundationGet(
|
|
49
|
+
'_o3-button-' + $type + '-' + $theme + '-border',
|
|
50
|
+
$sub-brand
|
|
51
|
+
),
|
|
52
|
+
'hover-color':
|
|
53
|
+
oPrivateFoundationGet(
|
|
54
|
+
'_o3-button-' + $type + '-' + $theme + '-hover-color',
|
|
55
|
+
$sub-brand
|
|
56
|
+
),
|
|
57
|
+
'hover-background':
|
|
58
|
+
oPrivateFoundationGet(
|
|
59
|
+
'_o3-button-' + $type + '-' + $theme + '-hover-background',
|
|
60
|
+
$sub-brand
|
|
61
|
+
),
|
|
62
|
+
'hover-border':
|
|
63
|
+
oPrivateFoundationGet(
|
|
64
|
+
'_o3-button-' + $type + '-' + $theme + '-hover-border',
|
|
65
|
+
$sub-brand
|
|
66
|
+
),
|
|
67
|
+
'focus-color':
|
|
68
|
+
oPrivateFoundationGet(
|
|
69
|
+
'_o3-button-' + $type + '-' + $theme + '-focus-color',
|
|
70
|
+
$sub-brand
|
|
71
|
+
),
|
|
72
|
+
'focus-background':
|
|
73
|
+
oPrivateFoundationGet(
|
|
74
|
+
'_o3-button-' + $type + '-' + $theme + '-focus-background',
|
|
75
|
+
$sub-brand
|
|
76
|
+
),
|
|
77
|
+
'focus-border':
|
|
78
|
+
oPrivateFoundationGet(
|
|
79
|
+
'_o3-button-' + $type + '-' + $theme + '-focus-border',
|
|
80
|
+
$sub-brand
|
|
81
|
+
),
|
|
82
|
+
'active-color':
|
|
83
|
+
oPrivateFoundationGet(
|
|
84
|
+
'_o3-button-' + $type + '-' + $theme + '-active-color',
|
|
85
|
+
$sub-brand
|
|
86
|
+
),
|
|
87
|
+
'active-background':
|
|
88
|
+
oPrivateFoundationGet(
|
|
89
|
+
'_o3-button-' + $type + '-' + $theme + '-active-background',
|
|
90
|
+
$sub-brand
|
|
91
|
+
),
|
|
92
|
+
'active-border':
|
|
93
|
+
oPrivateFoundationGet(
|
|
94
|
+
'_o3-button-' + $type + '-' + $theme + '-active-border',
|
|
95
|
+
$sub-brand
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Button styles shared across all types/theme.
|
|
102
|
+
@mixin _oPrivateButtonsBase(
|
|
103
|
+
$opts: (
|
|
104
|
+
'size': null,
|
|
105
|
+
'icon': null,
|
|
106
|
+
'icon-only': false,
|
|
107
|
+
)
|
|
108
|
+
) {
|
|
109
|
+
$icon-only: map-get($opts, 'icon-only');
|
|
110
|
+
$size: map-get($opts, 'size');
|
|
111
|
+
$icon: map-get($opts, 'icon');
|
|
112
|
+
|
|
113
|
+
font-family: oPrivateFoundationGet('o3-type-body-highlight-font-family');
|
|
114
|
+
font-size: oPrivateFoundationGet('o3-type-body-highlight-font-size');
|
|
115
|
+
line-height: oPrivateFoundationGet('o3-type-body-highlight-line-height');
|
|
116
|
+
font-weight: oPrivateFoundationGet('o3-type-body-highlight-font-weight');
|
|
117
|
+
|
|
118
|
+
--_o-pf-button-border-size: 1px;
|
|
119
|
+
--_o-pf-button-icon-size: calc(
|
|
120
|
+
var(--_o-pf-button-min-height) - (var(--_o-pf-button-block-padding) * 2)
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
/* Button Sizes */
|
|
124
|
+
@if ($size == 'big') {
|
|
125
|
+
--_o-pf-button-min-width: 80px;
|
|
126
|
+
--_o-pf-button-min-height: 44px;
|
|
127
|
+
|
|
128
|
+
--_o-pf-button-inline-padding-start: #{oPrivateFoundationGet(
|
|
129
|
+
'o3-spacing-2xs'
|
|
130
|
+
)};
|
|
131
|
+
--_o-pf-button-inline-padding-end: #{oPrivateFoundationGet(
|
|
132
|
+
'o3-spacing-2xs'
|
|
133
|
+
)};
|
|
134
|
+
--_o-pf-button-block-padding: #{oPrivateFoundationGet('o3-spacing-4xs')};
|
|
135
|
+
} @else {
|
|
136
|
+
--_o-pf-button-min-width: 60px;
|
|
137
|
+
--_o-pf-button-min-height: 28px;
|
|
138
|
+
|
|
139
|
+
--_o-pf-button-inline-padding-start: #{oPrivateFoundationGet(
|
|
140
|
+
'o3-spacing-4xs'
|
|
141
|
+
)};
|
|
142
|
+
--_o-pf-button-inline-padding-end: #{oPrivateFoundationGet(
|
|
143
|
+
'o3-spacing-4xs'
|
|
144
|
+
)};
|
|
145
|
+
--_o-pf-button-block-padding: #{oPrivateFoundationGet('o3-spacing-5xs')};
|
|
146
|
+
// Non-standard font size for small buttons.
|
|
147
|
+
font-size: oPrivateFoundationGet('o3-font-size-negative-2');
|
|
148
|
+
line-height: oPrivateFoundationGet('o3-font-lineheight-negative-2');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
min-width: var(--_o-pf-button-min-width);
|
|
152
|
+
min-height: var(--_o-pf-button-min-height, 44px);
|
|
153
|
+
padding: 0 var(--_o-pf-button-inline-padding-end) 0
|
|
154
|
+
var(--_o-pf-button-inline-padding-start);
|
|
155
|
+
display: inline-flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: center;
|
|
158
|
+
box-sizing: border-box;
|
|
159
|
+
vertical-align: middle;
|
|
160
|
+
border: var(--_o-pf-button-border-size) solid transparent;
|
|
161
|
+
border-radius: oPrivateFoundationGet('o3-border-radius-1');
|
|
162
|
+
text-align: center;
|
|
163
|
+
text-decoration: none;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
transition: 0.3s background-color, 0.15s color ease-out,
|
|
166
|
+
0.15s border-color ease-out;
|
|
167
|
+
appearance: none;
|
|
168
|
+
|
|
169
|
+
&[disabled] {
|
|
170
|
+
pointer-events: none;
|
|
171
|
+
opacity: 0.4;
|
|
172
|
+
cursor: default;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* ICON */
|
|
176
|
+
@if ($icon) {
|
|
177
|
+
--_o-pf-button-inline-padding-end: #{oPrivateFoundationGet('o3-spacing-s')};
|
|
178
|
+
@if ($size != 'big') {
|
|
179
|
+
--_o-pf-button-inline-padding-end: #{oPrivateFoundationGet(
|
|
180
|
+
'o3-spacing-4xs'
|
|
181
|
+
)};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
&::before {
|
|
185
|
+
content: '';
|
|
186
|
+
width: var(--_o-pf-button-icon-size);
|
|
187
|
+
height: var(--_o-pf-button-icon-size);
|
|
188
|
+
mask-image: #{oPrivateFoundationGet('o3-icon-' + $icon)};
|
|
189
|
+
mask-repeat: no-repeat;
|
|
190
|
+
mask-size: contain;
|
|
191
|
+
display: inline-block;
|
|
192
|
+
background-color: currentColor;
|
|
193
|
+
margin-right: #{oPrivateFoundationGet('o3-spacing-4xs')};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
Ensure iconography is maintained when forced colours is enabled.
|
|
198
|
+
E.g. Windows High Contrast Mode
|
|
199
|
+
https://drafts.csswg.org/css-color/#valdef-color-buttontext
|
|
200
|
+
*/
|
|
201
|
+
@media (forced-colors: active) {
|
|
202
|
+
&:is(button)::before {
|
|
203
|
+
background-color: ButtonText;
|
|
204
|
+
}
|
|
205
|
+
&:is(a)::before {
|
|
206
|
+
background-color: LinkText;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ICON ONLY
|
|
212
|
+
@if ($icon-only) {
|
|
213
|
+
--_o-pf-button-min-width: var(--_o-pf-button-min-height);
|
|
214
|
+
--_o-pf-button-inline-padding-start: 0;
|
|
215
|
+
--_o-pf-button-inline-padding-end: 0;
|
|
216
|
+
&::before {
|
|
217
|
+
margin-right: 0;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Button styles unique to a theme.
|
|
223
|
+
@mixin _oPrivateButtonsTheme($theme) {
|
|
224
|
+
color: map-get($theme, 'color');
|
|
225
|
+
background-color: map-get($theme, 'background');
|
|
226
|
+
border-color: map-get($theme, 'border');
|
|
227
|
+
|
|
228
|
+
&:hover {
|
|
229
|
+
color: map-get($theme, 'hover-color');
|
|
230
|
+
background-color: map-get($theme, 'hover-background');
|
|
231
|
+
border-color: map-get($theme, 'hover-border');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
&:focus-visible {
|
|
235
|
+
outline: 0; // Undo standard o2 focus styles
|
|
236
|
+
color: map-get($theme, 'focus-color');
|
|
237
|
+
background-color: map-get($theme, 'focus-background');
|
|
238
|
+
border-color: map-get($theme, 'focus-border');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@include oPrivateNormaliseFocusApply() {
|
|
242
|
+
@include oPrivateNormaliseFocusContentForElementColour(
|
|
243
|
+
map-get($theme, 'focus-background')
|
|
244
|
+
);
|
|
245
|
+
// Ensure that the focus ring of a button is not hidden by
|
|
246
|
+
// adjacent buttons, e.g. in tabs or pagination contexts.
|
|
247
|
+
z-index: 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
&:is(:active, [aria-selected='true'], [aria-current], [aria-pressed='true']) {
|
|
251
|
+
color: map-get($theme, 'active-color');
|
|
252
|
+
background-color: map-get($theme, 'active-background');
|
|
253
|
+
border-color: map-get($theme, 'active-border');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/// Returns a brighter or darker tone of a colour, where the hue remains
|
|
2
|
+
/// the same but the saturation and luminance changes.
|
|
3
|
+
///
|
|
4
|
+
/// Not all our colours allow tones. If a colour cannot be toned an error is
|
|
5
|
+
/// thrown. You may however mix the colour with another supported colour.
|
|
6
|
+
/// @see oPrivateColorsMix
|
|
7
|
+
///
|
|
8
|
+
/// @param {String} $color-name - the name of the color to be shaded
|
|
9
|
+
/// @param {Number} $brightness - the brightness value of the new color, 0-100
|
|
10
|
+
@function oPrivateColorsGetTone($color-name, $brightness) {
|
|
11
|
+
// Find palette colour information.
|
|
12
|
+
$color: oPrivateFoundationGet($color-name);
|
|
13
|
+
$color-map: map-get($_o-pf-colors-palette, $color-name);
|
|
14
|
+
$color-meta: map-get($color-map, 'meta');
|
|
15
|
+
|
|
16
|
+
// Validate brightness.
|
|
17
|
+
@if(type-of($brightness) != 'number' or $brightness > 100 or $brightness < 0) {
|
|
18
|
+
@return _oPrivateColorsError('"$brightness" must be a number between 0 and 100.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Error for colours which have no hue, such as white and black, which we do not allow tones of.
|
|
22
|
+
@if (hue($color) == 0 and saturation($color) == 0) {
|
|
23
|
+
@return _oPrivateColorsError('"#{$color-name}" does not support tones. ' +
|
|
24
|
+
'Use a mix instead: ' +
|
|
25
|
+
'`oPrivateColorsMix(\'#{$color-name}\', $percentage: #{$brightness})`');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Error for any palette colour which hasn't been configured to allow tones.
|
|
29
|
+
$allows-tones: map-get($color-meta, 'allow-tones');
|
|
30
|
+
@if (not $allows-tones) {
|
|
31
|
+
@return _oPrivateColorsError('"#{$color-name}" does not allow tones. ' +
|
|
32
|
+
'We only allow tones for some colours, to reduce the number ' +
|
|
33
|
+
'of different colours used across sites. ' +
|
|
34
|
+
'For custom colours, set the `allow-tones` option ' +
|
|
35
|
+
'of `oPrivateColorsSetColor` to enable tones. ' +
|
|
36
|
+
'If using a default o-colors colour consider using the `oPrivateColorsMix` ' +
|
|
37
|
+
'function to mix with black to darken or white to lighten.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Convert the given colour to the HSB colour space.
|
|
41
|
+
$hsb: _oPrivateColorsHexToHsbValues($color);
|
|
42
|
+
$hsb-hue: map-get($hsb, 'h');
|
|
43
|
+
$hsb-saturation: map-get($hsb, 's');
|
|
44
|
+
// Update the colours brightness with the given brightness value,
|
|
45
|
+
// using the HSB colour space.
|
|
46
|
+
@return _oPrivateColorsHsbToHex($hsb-hue, $hsb-saturation, $brightness);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Figure out if a given colour is a tone. If it is a tone return the original
|
|
50
|
+
/// colour name and its tone brightness, otherwise return null.
|
|
51
|
+
///
|
|
52
|
+
/// @example Get the tone brightness of 'claret-80'
|
|
53
|
+
/// $tone-details: oPrivateColorsGetToneDetails('claret-80');
|
|
54
|
+
/// $color-name: map-get($tone-details, 'color-name'); // claret
|
|
55
|
+
/// $brightness: map-get($tone-details, 'brightness'); // 80
|
|
56
|
+
///
|
|
57
|
+
/// @param {String} $color - the palette colour or color name e.g. 'claret-80'
|
|
58
|
+
/// @return {Map|Null} - the details of the given tone e.g. ('color-name': 'claret', 'brightness': 80) )
|
|
59
|
+
@function oPrivateColorsGetToneDetails($color) {
|
|
60
|
+
$color: if(type-of($color) == 'string', oPrivateFoundationGet($color), $color);
|
|
61
|
+
$hue: hue($color);
|
|
62
|
+
@each $tone-color, $tone-config in $_o-pf-colors-default-palette-tones {
|
|
63
|
+
// Check the given colour against the tone to find the tone brightness.
|
|
64
|
+
$brightness: 0;
|
|
65
|
+
@while $brightness <= 100 {
|
|
66
|
+
@if(inspect(oPrivateColorsGetTone($tone-color, $brightness)) == inspect($color)) {
|
|
67
|
+
@return (
|
|
68
|
+
'color-name': $tone-color,
|
|
69
|
+
'brightness': $brightness
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
$brightness: $brightness + 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// No tone matched.
|
|
76
|
+
@return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Returns a color based on the background context and base color
|
|
80
|
+
/// at the supplied percentage
|
|
81
|
+
///
|
|
82
|
+
/// @param {String|Color} $color [black] - palette name of color
|
|
83
|
+
/// @param {String|Color} $background [paper] - palette name of background color
|
|
84
|
+
/// @param {Number} $percentage [60] - percentage opacity of the foreground color over the background
|
|
85
|
+
@function oPrivateColorsMix($color: 'o3-color-palette-black', $background: oPrivateFoundationGet('o3-color-use-case-page-background'), $percentage: 80) {
|
|
86
|
+
// Cast colour arguments to string before checking if they exist in our pallet.
|
|
87
|
+
// If colour names are passed without quotes they will be of type `color`.
|
|
88
|
+
// We want both of these to be equivalent:
|
|
89
|
+
// oPrivateColorsMix('o3-color-palette-paper', 'o3-color-palette-wheat', 30);
|
|
90
|
+
// oPrivateColorsMix(o3-color-palette-paper, o3-color-palette-wheat, 30);
|
|
91
|
+
// https://www.w3.org/wiki/CSS/Properties/color/keywords
|
|
92
|
+
$base: if(type-of($background) == color, $background, oPrivateFoundationGet(#{$background}));
|
|
93
|
+
$mixer: if(type-of($color) == "color", $color, oPrivateFoundationGet(#{$color}));
|
|
94
|
+
|
|
95
|
+
@if type-of($base) != color {
|
|
96
|
+
@return _oPrivateColorsError("'#{inspect($background)}' is not a valid base color.");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@if type-of($mixer) != color {
|
|
100
|
+
@return _oPrivateColorsError("'#{inspect($color)}' is not a valid mixing color.");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@if (unitless($percentage)) {
|
|
104
|
+
@return mix($mixer, $base, $percentage * 1%);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@return mix($mixer, $base, $percentage);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Returns a text color based on the background and
|
|
111
|
+
/// an opacity percentage the color should appear at
|
|
112
|
+
///
|
|
113
|
+
/// @param {Color|String} $background - the color or palette color name of the background the text will appear on
|
|
114
|
+
/// @param {Number} $opacity [100] - the opacity percentage the text color should appear at
|
|
115
|
+
/// @param {String|Null} $minimum-contrast ['aa-normal'] - the minimum contrast ratio standard between the background and the returned text color, one of: aa-normal, aa-large, aaa-normal, aaa-large. See [WCAG 2.1 guidelines](https://www.w3.org/TR/WCAG21/#contrast-minimum). If the contrast ratio is too low to meet the selected guideline an error is thrown. Set to `null` to remove contrast checking and never throw an error.
|
|
116
|
+
@function oPrivateColorsGetTextColor($background, $opacity: 90, $minimum-contrast: 'aa-normal') {
|
|
117
|
+
$background-name: $background;
|
|
118
|
+
// Get background color if palette colour name has been given.
|
|
119
|
+
$background: if(type-of($background) == 'string', oPrivateFoundationGet($background), $background);
|
|
120
|
+
|
|
121
|
+
// Contrast values. See https://www.w3.org/TR/WCAG21/#contrast-minimum
|
|
122
|
+
$contrast-levels: (
|
|
123
|
+
'aa-normal': 4.5,
|
|
124
|
+
'aa-large': 3,
|
|
125
|
+
'aaa-normal': 7,
|
|
126
|
+
'aaa-large': 4.5
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Validate arguments.
|
|
130
|
+
@if($minimum-contrast != null and not map-has-key($contrast-levels, $minimum-contrast)) {
|
|
131
|
+
@return _oPrivateColorsError('The minimum contrast must by one of "#{map-keys($contrast-levels)}" '+
|
|
132
|
+
'or `null`. Found "#{inspect($minimum-contrast)}".');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@if type-of($background) != color {
|
|
136
|
+
@return _oPrivateColorsError("'#{inspect($background)}' is not a valid color. To get a text color, please supply a valid color or palette color name for the background color'");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@if type-of($opacity) != 'number' {
|
|
140
|
+
@return _oPrivateColorsError("'#{inspect($opacity)}' is not a valid opacity, set to a number.'");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
$contrast-ratio-aim: map-get($contrast-levels, if($minimum-contrast, $minimum-contrast, 'aa-normal'));
|
|
144
|
+
|
|
145
|
+
// Calculate text colour for background and opacity.
|
|
146
|
+
$base-color-a: if(oPrivateColorsColorBrightness($background) < 65%, 'o3-color-palette-white', 'o3-color-palette-black');
|
|
147
|
+
$text-color-a: oPrivateColorsMix($base-color-a, $background, $opacity);
|
|
148
|
+
$contrast-ratio-a: oPrivateColorsGetContrastRatio($text-color-a, $background);
|
|
149
|
+
@if $contrast-ratio-a > $contrast-ratio-aim {
|
|
150
|
+
@return $text-color-a;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Switch the base colour if the first attempt did not pass contrast checks.
|
|
154
|
+
$base-color-b: if($base-color-a == 'o3-color-palette-black', 'o3-color-palette-white', 'o3-color-palette-black');
|
|
155
|
+
$text-color-b: oPrivateColorsMix($base-color-b, $background, $opacity);
|
|
156
|
+
$contrast-ratio-b: oPrivateColorsGetContrastRatio($text-color-b, $background);
|
|
157
|
+
@if $contrast-ratio-b > $contrast-ratio-aim {
|
|
158
|
+
@return $text-color-b;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Error if neither base colour produced a text colour of high enough contrast.
|
|
162
|
+
@if $minimum-contrast != null {
|
|
163
|
+
$best-contrast-ratio: if($contrast-ratio-a > $contrast-ratio-b, $contrast-ratio-a, $contrast-ratio-b);
|
|
164
|
+
@return _oPrivateColorsError(
|
|
165
|
+
'The text colour generated for #{inspect($background-name)} at ' +
|
|
166
|
+
'#{inspect($opacity)}% opacity has a contrast ratio of ' +
|
|
167
|
+
'"#{inspect($best-contrast-ratio)}" and does not pass the WCAG 2.1 ' +
|
|
168
|
+
'#{$minimum-contrast} required contrast ratio of at least ' +
|
|
169
|
+
'#{$contrast-ratio-aim}:1. Update the `$minimum-contrast` argument ' +
|
|
170
|
+
'if a lower contrast is acceptable.'
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@return if($contrast-ratio-a > $contrast-ratio-b, $text-color-a, $text-color-b);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Work out the brightness value in % of a color
|
|
178
|
+
/// From: https://gist.github.com/jlong/f06f5843104ee10006fe
|
|
179
|
+
///
|
|
180
|
+
/// @param {Color} $color - color value to get brightness from (either a CSS colour or o-colors palette colour name)
|
|
181
|
+
@function oPrivateColorsColorBrightness($color) {
|
|
182
|
+
$color: if(type-of($color) == 'string', oPrivateFoundationGet($color), $color);
|
|
183
|
+
|
|
184
|
+
$red-magic-number: 241;
|
|
185
|
+
$green-magic-number: 691;
|
|
186
|
+
$blue-magic-number: 68;
|
|
187
|
+
|
|
188
|
+
$brightness-divisor: $red-magic-number + $green-magic-number + $blue-magic-number;
|
|
189
|
+
|
|
190
|
+
// Extract color components
|
|
191
|
+
$red-component: red($color);
|
|
192
|
+
$green-component: green($color);
|
|
193
|
+
$blue-component: blue($color);
|
|
194
|
+
|
|
195
|
+
// Calculate a brightness value in 3d color space between 0 and 255
|
|
196
|
+
$number: sqrt(div(($red-component * $red-component * $red-magic-number) + ($green-component * $green-component * $green-magic-number) + ($blue-component * $blue-component * $blue-magic-number), $brightness-divisor));
|
|
197
|
+
|
|
198
|
+
// Convert to percentage and return
|
|
199
|
+
@return 100% * div($number, 255);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Returns the luminance of `$color` as a float (between 0 and 1)
|
|
203
|
+
/// 1 is pure white, 0 is pure black.
|
|
204
|
+
/// From: https://css-tricks.com/snippets/sass/luminance-color-function/
|
|
205
|
+
/// @param {String|Color} $color - The colour to return a luminance for (either a CSS colour or o-colors palette colour name)
|
|
206
|
+
/// @return {Number} - a number between 0 and 1
|
|
207
|
+
@function oPrivateColorsColorLuminance($color) {
|
|
208
|
+
$color: if(type-of($color) == 'string', oPrivateFoundationGet($color), $color);
|
|
209
|
+
|
|
210
|
+
$colors: (
|
|
211
|
+
'red': red($color),
|
|
212
|
+
'green': green($color),
|
|
213
|
+
'blue': blue($color)
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
@each $name, $value in $colors {
|
|
217
|
+
$adjusted: 0;
|
|
218
|
+
$value: div($value, 255);
|
|
219
|
+
|
|
220
|
+
@if $value < 0.03928 {
|
|
221
|
+
$value: div($value, 12.92);
|
|
222
|
+
} @else {
|
|
223
|
+
$value: div($value + 0.055, 1.055);
|
|
224
|
+
$value: pow($value, 2.4);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
$colors: map-merge($colors, ($name: $value));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@return (map-get($colors, 'red') * 0.2126) + (map-get($colors, 'green') * 0.7152) + (map-get($colors, 'blue') * 0.0722);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// Calculate the contrast ratio between two colours.
|
|
234
|
+
///
|
|
235
|
+
/// @param {String|Color} $color-a - first colour to compare (either a CSS colour or o-colors palette colour name)
|
|
236
|
+
/// @param {String|Color} $color-b - second colour to compare (either a CSS colour or o-colors palette colour name)
|
|
237
|
+
/// Based on the JS in https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/contrast-ratio.js
|
|
238
|
+
@function oPrivateColorsGetContrastRatio($color-a, $color-b) {
|
|
239
|
+
$l1: oPrivateColorsColorLuminance($color-a) + 0.05;
|
|
240
|
+
$l2: oPrivateColorsColorLuminance($color-b) + 0.05;
|
|
241
|
+
|
|
242
|
+
$ratio: div($l1, $l2);
|
|
243
|
+
|
|
244
|
+
@if $l2 > $l1 {
|
|
245
|
+
$ratio: div(1, $ratio);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
$ratio: _oPrivateColorsPreciseFloor($ratio);
|
|
249
|
+
|
|
250
|
+
@return $ratio;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// @access private
|
|
254
|
+
@function _oPrivateColorsPreciseFloor($number, $decimals: 2) {
|
|
255
|
+
$multiplier: pow(10, $decimals);
|
|
256
|
+
@return div(floor($number * $multiplier), $multiplier);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Allows for errors to be tested in dev environments
|
|
260
|
+
/// Code from: https://github.com/oddbird/true/issues/92
|
|
261
|
+
/// @access private
|
|
262
|
+
@function _oPrivateColorsError($message, $capture: $_o-pf-colors-test-environment) {
|
|
263
|
+
@if $capture {
|
|
264
|
+
@return 'ERROR: #{$message}';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@error('#{$message}');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Get a namespace from a colour name.
|
|
271
|
+
/// Returns null if there is no namespace.
|
|
272
|
+
/// @example
|
|
273
|
+
/// $namespace: _oPrivateColorsGetNameSpace('o-example/paper'); // o-example
|
|
274
|
+
/// $namespace: _oPrivateColorsGetNameSpace('o-colors/paper'); // o-colors
|
|
275
|
+
/// $namespace: _oPrivateColorsGetNameSpace('paper'); // null
|
|
276
|
+
@function _oPrivateColorsGetNameSpace($color-name) {
|
|
277
|
+
$slash-index: str-index($color-name, '/');
|
|
278
|
+
@return if($slash-index, str-slice($color-name, 0, $slash-index - 1), null);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// Add the default o-colors namespace if a namespace isn't given.
|
|
282
|
+
/// @example
|
|
283
|
+
/// $namespace: _oPrivateColorsRemoveDefaultNamespace('o-example/paper'); // o-example/paper
|
|
284
|
+
/// $namespace: _oPrivateColorsRemoveDefaultNamespace('o-colors/paper'); // paper
|
|
285
|
+
/// $namespace: _oPrivateColorsRemoveDefaultNamespace('paper'); // paper
|
|
286
|
+
@function _oPrivateColorsRemoveDefaultNamespace($color-name) {
|
|
287
|
+
$namespace: _oPrivateColorsGetNameSpace($color-name);
|
|
288
|
+
$slash-index: str-index($color-name, '/');
|
|
289
|
+
$color-part: if($namespace, str-slice($color-name, $slash-index + 1, -1), $color-name);
|
|
290
|
+
@return if($namespace == 'o-colors', $color-part, $color-name);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/// Check if a colour name exists in the palette.
|
|
294
|
+
@function _oPrivateColorsNameExists($color-name) {
|
|
295
|
+
@return map-has-key($_o-pf-colors-palette, $color-name);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// Converts HSB/HSV values to a hex colour
|
|
299
|
+
///
|
|
300
|
+
/// @access private
|
|
301
|
+
///
|
|
302
|
+
/// @param {Number} $hue - number between 0-360
|
|
303
|
+
/// @param {Number} $saturation - number between 0-100
|
|
304
|
+
/// @param {Number} $brigthness - number between 0-100
|
|
305
|
+
///
|
|
306
|
+
/// @return {Colour} - the hex representation of given hsb values
|
|
307
|
+
@function _oPrivateColorsHsbToHex($hue, $saturation, $brightness) {
|
|
308
|
+
@if $brightness == 0 {
|
|
309
|
+
@return hsl(0, 0%, 0%);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
$hsl-luminance: (div($brightness, 2)) * (2 - (div($saturation, 100)));
|
|
313
|
+
$hsl-saturation: div($brightness * $saturation, if($hsl-luminance < 50, $hsl-luminance * 2, 200 - $hsl-luminance * 2));
|
|
314
|
+
@return hsl($hue, $hsl-saturation * 1%, $hsl-luminance * 1%);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Returns HSB/HSV colour values for a given colour hex.
|
|
318
|
+
///
|
|
319
|
+
/// Logic derived from chroma.js
|
|
320
|
+
/// https://github.com/gka/chroma.js/blob/088e18f50a3b5b1d009ce68f540265cafa0cb6a1/src/io/hsv/rgb2hsv.js#L10
|
|
321
|
+
/// HSL to HSB is also documented here, if you prefer math:
|
|
322
|
+
/// https://www.rapidtables.com/convert/color/rgb-to-hsv.html
|
|
323
|
+
///
|
|
324
|
+
/// @access private
|
|
325
|
+
///
|
|
326
|
+
/// @param {Color} $hex - The hex colour value to find hsb values for.
|
|
327
|
+
/// @return {Map} - map of `h`, `s`, `b` colour values.
|
|
328
|
+
@function _oPrivateColorsHexToHsbValues($hex) {
|
|
329
|
+
$redValue: red($hex);
|
|
330
|
+
$greenValue: green($hex);
|
|
331
|
+
$blueValue: blue($hex);
|
|
332
|
+
|
|
333
|
+
// Find smallest and largest amount of colour.
|
|
334
|
+
$min: null;
|
|
335
|
+
$max: null;
|
|
336
|
+
@each $var in ($redValue, $greenValue, $blueValue) {
|
|
337
|
+
@if $min == null or $var < $min {
|
|
338
|
+
$min: $var;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@if $max == null or $var > $max {
|
|
342
|
+
$max: $var;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Difference between the smallest and largest amount of colour.
|
|
347
|
+
$delta: $max - $min;
|
|
348
|
+
|
|
349
|
+
$hue: hue($hex);
|
|
350
|
+
$saturation: if($max == 0, 0, div($delta, $max));
|
|
351
|
+
$brightness: div($max, 255);
|
|
352
|
+
|
|
353
|
+
@return ('h': $hue, 's': $saturation * 100, 'b': $brightness * 100);
|
|
354
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/// A map to define default palette colours:
|
|
2
|
+
/// A list of arguments for `oPrivateColorsSetColor`, except the project name.
|
|
3
|
+
///
|
|
4
|
+
/// @see _oPrivateColorsSetDefaultPaletteColors
|
|
5
|
+
/// @see oPrivateColorsSetColor
|
|
6
|
+
/// @type List
|
|
7
|
+
/// @access private
|
|
8
|
+
$_o-pf-colors-default-palette-colors: () !default;
|
|
9
|
+
|
|
10
|
+
/// A map to define default tones in the palette.
|
|
11
|
+
/// This is also used to decide which colours are
|
|
12
|
+
/// allowed to have tones, when users request a
|
|
13
|
+
/// non-default tone.
|
|
14
|
+
/// @see _oPrivateColorsSetPaletteTones
|
|
15
|
+
/// @type Map
|
|
16
|
+
/// @access private
|
|
17
|
+
$_o-pf-colors-default-palette-tones: ();
|
|
18
|
+
|
|
19
|
+
/// A map to define default mixes in the palette.
|
|
20
|
+
/// @see _oPrivateColorsSetPaletteTones
|
|
21
|
+
/// @type Map
|
|
22
|
+
/// @access private
|
|
23
|
+
$_o-pf-colors-default-palette-mixes: ();
|
|
24
|
+
|
|
25
|
+
/// A map to define default colour usecases:
|
|
26
|
+
/// A list of arguments for `oPrivateColorsSetUseCase`, except the project name.
|
|
27
|
+
///
|
|
28
|
+
/// @see _oPrivateColorsSetDefaultPaletteColors
|
|
29
|
+
/// @see oPrivateColorsSetUseCase
|
|
30
|
+
/// @type List
|
|
31
|
+
/// @access private
|
|
32
|
+
$_o-pf-colors-default-usecases: () !default;
|
|
33
|
+
|
|
34
|
+
/// A map to store all set colour usecases.
|
|
35
|
+
/// @see oPrivateColorsSetUseCase
|
|
36
|
+
/// @type Map
|
|
37
|
+
/// @access private
|
|
38
|
+
$_o-pf-colors-usecases: () !default;
|
|
39
|
+
|
|
40
|
+
/// A map to store all set palette colours.
|
|
41
|
+
/// @see oPrivateColorsSetColor
|
|
42
|
+
/// @type Map
|
|
43
|
+
/// @access private
|
|
44
|
+
$_o-pf-colors-palette: () !default;
|
|
45
|
+
|
|
46
|
+
/// Function errors may return a string rather than error
|
|
47
|
+
/// when in test mode, so automated tests may assert errors.
|
|
48
|
+
/// @access private
|
|
49
|
+
/// @type Bool
|
|
50
|
+
$_o-pf-colors-test-environment: false !default;
|