@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,631 @@
|
|
|
1
|
+
/// oPrivateButtonsContent but with a custom theme.
|
|
2
|
+
/// @param {Map} $opts [('type': 'null', 'size': null, 'icon': null, 'icon-only': false)] - The kind of button styles to output.
|
|
3
|
+
/// @param {Map} $theme-override - The button theme to output. Either a default theme 'inverse' or custom theme map with properties `name`, `color`, and optional `context` key.
|
|
4
|
+
@mixin oPrivateButtonsContentWithThemeOverride(
|
|
5
|
+
$opts: (
|
|
6
|
+
'type': null,
|
|
7
|
+
'size': null,
|
|
8
|
+
'icon': null,
|
|
9
|
+
'icon-only': false,
|
|
10
|
+
),
|
|
11
|
+
$theme-override
|
|
12
|
+
) {
|
|
13
|
+
$type: map-get($opts, 'type');
|
|
14
|
+
$type: if($type, $type, 'primary');
|
|
15
|
+
$generated-colors: _oPrivateButtonsGenerateColors($type, $theme-override);
|
|
16
|
+
|
|
17
|
+
@include _oPrivateButtonsBase($opts);
|
|
18
|
+
@include _oPrivateButtonsTheme($generated-colors);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Generate the colours for a button type and theme, either an existing theme
|
|
22
|
+
/// or a new theme map.
|
|
23
|
+
///
|
|
24
|
+
/// @example
|
|
25
|
+
/// // $colors: _oPrivateButtonsGetColors('primary', ('color': 'white', 'context': 'slate'));
|
|
26
|
+
/// // (
|
|
27
|
+
/// // "color": black,
|
|
28
|
+
/// // "background": #ffffff,
|
|
29
|
+
/// // "border": transparent,
|
|
30
|
+
/// // "hover-background": #c9cacc,
|
|
31
|
+
/// // "hover-color": black,
|
|
32
|
+
/// // "focus-background": #c9cacc,
|
|
33
|
+
/// // "focus-color": black,
|
|
34
|
+
/// // "active-background": #9d9fa3,
|
|
35
|
+
/// // "active-color": black,
|
|
36
|
+
/// // "hover-border": transparent,
|
|
37
|
+
/// // "focus-border": transparent,
|
|
38
|
+
/// // "active-border": transparent
|
|
39
|
+
/// // )
|
|
40
|
+
///
|
|
41
|
+
/// @param {String} $type
|
|
42
|
+
/// @param {String|Map} $theme
|
|
43
|
+
/// @return {Map} - Button colours.
|
|
44
|
+
@function _oPrivateButtonsGenerateColors($type, $theme: null) {
|
|
45
|
+
@if ($type != 'primary' and $type != 'secondary' and $type != 'ghost') {
|
|
46
|
+
@error 'Expected a button type of "secondary" or "primary" or "ghost".';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Get button colour from custom theme map or brand config, see _brand.scss.
|
|
50
|
+
$color: map-get($theme, 'color');
|
|
51
|
+
$context-color: map-get($theme, 'context');
|
|
52
|
+
|
|
53
|
+
// Get button context, which is the background colour the button is placed on.
|
|
54
|
+
$page-background-usecase: oPrivateFoundationGet('o3-color-use-case-page-background');
|
|
55
|
+
$context-color: if($context-color, $context-color, $page-background-usecase);
|
|
56
|
+
|
|
57
|
+
// Get button colours depending on button type.
|
|
58
|
+
$color-map: ();
|
|
59
|
+
@if ($type == 'primary') {
|
|
60
|
+
$color-map: _oPrivateButtonsGetPrimaryButtonColors($color, $context-color);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@if ($type == 'secondary') {
|
|
64
|
+
$color-map: _oPrivateButtonsGetSecondaryButtonColors(
|
|
65
|
+
$color,
|
|
66
|
+
$context-color
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@if ($type == 'ghost') {
|
|
71
|
+
$color-map: _oPrivateButtonsGetGhostButtonColors($color, $context-color);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If a button state does not have a property configured, set it to the
|
|
75
|
+
// default button colour.
|
|
76
|
+
@each $property in ('color', 'background', 'border') {
|
|
77
|
+
@each $state in ('hover', 'focus', 'active') {
|
|
78
|
+
$state-color: map-get($color-map, '#{$state}-#{$property}');
|
|
79
|
+
@if (not $state-color) {
|
|
80
|
+
$color-map: map-merge(
|
|
81
|
+
$color-map,
|
|
82
|
+
(
|
|
83
|
+
'#{$state}-#{$property}': map-get($color-map, $property),
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
@return $color-map;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// For a colour and context colour (the background colour behind the button),
|
|
93
|
+
/// generate primary button colours and overrides for each button state.
|
|
94
|
+
///
|
|
95
|
+
/// @example
|
|
96
|
+
/// $colors: _oPrivateButtonsGetPrimaryButtonColors('lemon', 'slate');
|
|
97
|
+
/// // (
|
|
98
|
+
/// // "color": black,
|
|
99
|
+
/// // "background": #ffec1a,
|
|
100
|
+
/// // "border": transparent,
|
|
101
|
+
/// // "hover-background": #ccbd14,
|
|
102
|
+
/// // "hover-color": black,
|
|
103
|
+
/// // "focus-background": #ccbd14,
|
|
104
|
+
/// // "focus-color": black,
|
|
105
|
+
/// // "active-background": #a69911,
|
|
106
|
+
/// // "active-color": black
|
|
107
|
+
/// // )
|
|
108
|
+
///
|
|
109
|
+
/// @param {String|Color} $color - The button colour e.g. 'teal' (from `o-colors` palette).
|
|
110
|
+
/// @param {String|Color} $context-color - The page colour behind the button (from `o-colors` palette).
|
|
111
|
+
/// @return {Map} - Colours for a primary button. Including overrides for each button state.
|
|
112
|
+
@function _oPrivateButtonsGetPrimaryButtonColors($color, $context-color) {
|
|
113
|
+
$color-arg: $color;
|
|
114
|
+
$context-color-arg: $context-color;
|
|
115
|
+
$default-background: if(
|
|
116
|
+
type-of($color) == 'string',
|
|
117
|
+
oPrivateFoundationGet($color),
|
|
118
|
+
$color
|
|
119
|
+
);
|
|
120
|
+
$context-color: if(
|
|
121
|
+
type-of($context-color) == 'string',
|
|
122
|
+
oPrivateFoundationGet($context-color),
|
|
123
|
+
$context-color
|
|
124
|
+
);
|
|
125
|
+
$default-context: $context-color == oPrivateFoundationGet('o3-color-use-case-page-background');
|
|
126
|
+
|
|
127
|
+
// The default button text colour is black/white on the default page background.
|
|
128
|
+
// If a different context has been given the text colour matches if possible,
|
|
129
|
+
// or is mixed to be darker/lighter for higher contrast.
|
|
130
|
+
// o-buttons does custom contrast checks
|
|
131
|
+
$default-base-color: oPrivateColorsGetTextColor(
|
|
132
|
+
$color,
|
|
133
|
+
100,
|
|
134
|
+
$minimum-contrast: null
|
|
135
|
+
);
|
|
136
|
+
$default-color: if(
|
|
137
|
+
$default-context,
|
|
138
|
+
$default-base-color,
|
|
139
|
+
_oPrivateButtonsGetMixColor(
|
|
140
|
+
$color-a: $context-color,
|
|
141
|
+
$color-b: $default-base-color,
|
|
142
|
+
$contrast-color: $default-background,
|
|
143
|
+
$contrast-aim: 4.5,
|
|
144
|
+
$preferred-mix: 100,
|
|
145
|
+
$minimum-mix: 0,
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Derive the hover/focus/active background colour from the default
|
|
150
|
+
// background colour.
|
|
151
|
+
$hover-focus-background: null;
|
|
152
|
+
$active-background: null;
|
|
153
|
+
|
|
154
|
+
// If the background color may be tinted use tints, otherwise use a mix.
|
|
155
|
+
$is-tint: oPrivateColorsGetToneDetails($default-background);
|
|
156
|
+
@if ($is-tint) {
|
|
157
|
+
// The hover/focus background tint should be lower than the default
|
|
158
|
+
// button background. Preferably with a contrast ratio of at least 1.5,
|
|
159
|
+
// but should not have a tint value lower than ten e.g. teal-10.
|
|
160
|
+
$hover-focus-background: _oPrivateButtonsGetDarkerTint(
|
|
161
|
+
$tint: $default-background,
|
|
162
|
+
$contrast-aim: 1.5,
|
|
163
|
+
$minimum-tint-value: 10,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// The interactive background tint should be darker than the
|
|
167
|
+
// hover/focus background too.
|
|
168
|
+
$active-background: _oPrivateButtonsGetDarkerTint(
|
|
169
|
+
$tint: $hover-focus-background,
|
|
170
|
+
$contrast-aim: 1.5,
|
|
171
|
+
$minimum-tint-value: 0,
|
|
172
|
+
);
|
|
173
|
+
} @else {
|
|
174
|
+
// When lightening buttons with a mix use white. Except for the
|
|
175
|
+
// core brand where a slate button should be mixed with paper.
|
|
176
|
+
$light-mix: if(
|
|
177
|
+
oBrandIs('core') and $default-background == oPrivateFoundationGet('o3-color-palette-slate'),
|
|
178
|
+
oPrivateFoundationGet('o3-color-palette-paper'),
|
|
179
|
+
'o3-color-palette-white'
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// When darkening buttons with a mix use black. Except for the
|
|
183
|
+
// core and internal brands, where buttons on slate should be mixed
|
|
184
|
+
// with slate.
|
|
185
|
+
$dark-mix: if(
|
|
186
|
+
(oBrandIs('core') or oBrandIs('internal')) and
|
|
187
|
+
$context-color ==
|
|
188
|
+
oPrivateFoundationGet('o3-color-palette-slate'),
|
|
189
|
+
oPrivateFoundationGet('o3-color-palette-slate'),
|
|
190
|
+
'o3-color-palette-black'
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Make the button darker for hover/focus states, and
|
|
194
|
+
// darker still for active states. Unless the button color has low
|
|
195
|
+
// luminance, in which case make the button lighter instead.
|
|
196
|
+
// E.g. white: 1, claret: 0.07451, slate: 0.02307
|
|
197
|
+
$color-luminance: oPrivateColorsColorLuminance($default-background);
|
|
198
|
+
$mix-color: if($color-luminance > 0.05, $dark-mix, $light-mix);
|
|
199
|
+
|
|
200
|
+
// Mix the background colour to make the hover/focus state darker or
|
|
201
|
+
// lighter. The contrast should be at least 1.5. If not try lowering
|
|
202
|
+
// the button colour until a minimum mix value we'll accept is reached.
|
|
203
|
+
$hover-focus-background: _oPrivateButtonsGetMixColor(
|
|
204
|
+
$color-a: $color,
|
|
205
|
+
$color-b: $mix-color,
|
|
206
|
+
$contrast-color: $color,
|
|
207
|
+
$contrast-aim: 1.5,
|
|
208
|
+
$preferred-mix: 80,
|
|
209
|
+
$minimum-mix: 60,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// The active background tint should have at least a contrast of 1.5
|
|
213
|
+
// against the hover/focus background colour.
|
|
214
|
+
$active-background: _oPrivateButtonsGetMixColor(
|
|
215
|
+
$color-a: $color,
|
|
216
|
+
$color-b: $mix-color,
|
|
217
|
+
$contrast-color: $hover-focus-background,
|
|
218
|
+
$contrast-aim: 1.5,
|
|
219
|
+
$preferred-mix: 70,
|
|
220
|
+
$minimum-mix: 50,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Generate the hover/focus state text colour.
|
|
225
|
+
// The text colour is black/white on the default page background.
|
|
226
|
+
// If a different context has been given the text colour matches if possible,
|
|
227
|
+
// or is mixed to be darker/lighter for higher contrast.
|
|
228
|
+
$hover-focus-base-color: oPrivateColorsGetTextColor(
|
|
229
|
+
$hover-focus-background,
|
|
230
|
+
100,
|
|
231
|
+
$minimum-contrast: null
|
|
232
|
+
);
|
|
233
|
+
$hover-focus-color: if(
|
|
234
|
+
$default-context,
|
|
235
|
+
$hover-focus-base-color,
|
|
236
|
+
_oPrivateButtonsGetMixColor(
|
|
237
|
+
$color-a: $context-color,
|
|
238
|
+
$color-b: $hover-focus-base-color,
|
|
239
|
+
$contrast-color: $hover-focus-background,
|
|
240
|
+
$contrast-aim: 4.5,
|
|
241
|
+
$preferred-mix: 100,
|
|
242
|
+
$minimum-mix: 0,
|
|
243
|
+
)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Generate the active state text colour.
|
|
247
|
+
// The text colour is black/white on the default page background.
|
|
248
|
+
// If a different context has been given the text colour matches if possible,
|
|
249
|
+
// or is mixed to be darker/lighter for higher contrast.
|
|
250
|
+
$active-base-color: oPrivateColorsGetTextColor(
|
|
251
|
+
$active-background,
|
|
252
|
+
100,
|
|
253
|
+
$minimum-contrast: null
|
|
254
|
+
);
|
|
255
|
+
$active-color: if(
|
|
256
|
+
$default-context,
|
|
257
|
+
$active-base-color,
|
|
258
|
+
_oPrivateButtonsGetMixColor(
|
|
259
|
+
$color-a: $context-color,
|
|
260
|
+
$color-b: $active-base-color,
|
|
261
|
+
$contrast-color: $active-background,
|
|
262
|
+
$contrast-aim: 4.5,
|
|
263
|
+
$preferred-mix: 100,
|
|
264
|
+
$minimum-mix: 0,
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Confirm the contrast of default state.
|
|
269
|
+
$contrast-error-message: 'Could not create an accessible primary button of colour "#{$color-arg}" for a background context "#{$context-color-arg}".';
|
|
270
|
+
$default-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
271
|
+
$default-color,
|
|
272
|
+
$default-background
|
|
273
|
+
);
|
|
274
|
+
@if ($default-contrast-ratio < 4.5) {
|
|
275
|
+
@error ($contrast-error-message + ' The default text and background colour do not pass WCAG AA contrast checks.');
|
|
276
|
+
}
|
|
277
|
+
// Confirm the focus/hover state contrast.
|
|
278
|
+
$hover-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
279
|
+
$hover-focus-color,
|
|
280
|
+
$hover-focus-background
|
|
281
|
+
);
|
|
282
|
+
@if ($hover-contrast-ratio < 4.5) {
|
|
283
|
+
@error ($contrast-error-message + ' The text and background colour of the hover and focus state do not pass WCAG AA contrast checks.');
|
|
284
|
+
}
|
|
285
|
+
// Confirm the active state contrast.
|
|
286
|
+
$active-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
287
|
+
$active-color,
|
|
288
|
+
$active-background
|
|
289
|
+
);
|
|
290
|
+
@if ($active-contrast-ratio < 4.5) {
|
|
291
|
+
@error ($contrast-error-message + ' The text and background colour of the active state do not pass WCAG AA contrast checks.');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@return (
|
|
295
|
+
'color': $default-color,
|
|
296
|
+
'background': $default-background,
|
|
297
|
+
'border': transparent,
|
|
298
|
+
'hover-background': $hover-focus-background,
|
|
299
|
+
'hover-color': $hover-focus-color,
|
|
300
|
+
'focus-background': $hover-focus-background,
|
|
301
|
+
'focus-color': $hover-focus-color,
|
|
302
|
+
'active-background': $active-background,
|
|
303
|
+
'active-color': $active-color
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/// For a colour and context colour (the background colour behind the button),
|
|
308
|
+
/// generate secondary button colours and overrides for each button state.
|
|
309
|
+
///
|
|
310
|
+
/// @example
|
|
311
|
+
/// $colors: _oPrivateButtonsGetSecondaryButtonColors('lemon', 'slate');
|
|
312
|
+
/// // (
|
|
313
|
+
/// // "color": #ffec1a,
|
|
314
|
+
/// // "background": transparent,
|
|
315
|
+
/// // "border": #ffec1a,
|
|
316
|
+
/// // "hover-background": rgba(255, 236, 26, 0.15),
|
|
317
|
+
/// // "hover-color": #ffec1a,
|
|
318
|
+
/// // "focus-background": rgba(255, 236, 26, 0.15),
|
|
319
|
+
/// // "focus-color": #ffec1a,
|
|
320
|
+
/// // "active-background": #ffec1a,
|
|
321
|
+
/// // "active-color": black
|
|
322
|
+
/// // )
|
|
323
|
+
///
|
|
324
|
+
/// @param {String|Color} $color - The button colour e.g. 'teal' (from `o-colors` palette).
|
|
325
|
+
/// @param {String|Color} $context-color - The page colour behind the button (from `o-colors` palette).
|
|
326
|
+
/// @return {Map} - Colours for a secondary button. Including overrides for each button state.
|
|
327
|
+
@function _oPrivateButtonsGetSecondaryButtonColors($color, $context-color) {
|
|
328
|
+
$default-color: if(
|
|
329
|
+
type-of($color) == 'string',
|
|
330
|
+
oPrivateFoundationGet($color),
|
|
331
|
+
$color
|
|
332
|
+
);
|
|
333
|
+
$default-background: transparent;
|
|
334
|
+
$default-background-opaque: if(
|
|
335
|
+
type-of($context-color) == 'string',
|
|
336
|
+
oPrivateFoundationGet($context-color),
|
|
337
|
+
$context-color
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// Get hover/focus colors:
|
|
341
|
+
// The secondary button hover/focus background is transparent.
|
|
342
|
+
// It should be 15% opacity if the contrast ratio against the button
|
|
343
|
+
// copy is passes WCAG AA contrast checks, otherwise use 10%.
|
|
344
|
+
$hover-focus-background-mix: _oPrivateButtonsGetMix(
|
|
345
|
+
$color-a: $color,
|
|
346
|
+
$color-b: $context-color,
|
|
347
|
+
$contrast-color: $color,
|
|
348
|
+
$contrast-aim: 4.5,
|
|
349
|
+
$preferred-mix: 15,
|
|
350
|
+
$minimum-mix: 10,
|
|
351
|
+
);
|
|
352
|
+
// Use transparency so the button may be used on a different background
|
|
353
|
+
// context, and trust the user to test accessibility in their app.
|
|
354
|
+
// But use an opaque mix to test contrast with the known context.
|
|
355
|
+
$hover-focus-background: oPrivateColorsMix(
|
|
356
|
+
$color,
|
|
357
|
+
transparent,
|
|
358
|
+
$hover-focus-background-mix
|
|
359
|
+
);
|
|
360
|
+
$hover-focus-background-opaque: oPrivateColorsMix(
|
|
361
|
+
$color,
|
|
362
|
+
$context-color,
|
|
363
|
+
$hover-focus-background-mix
|
|
364
|
+
);
|
|
365
|
+
// If the hover/focus state doesn't have high enough contrast darken/lighten
|
|
366
|
+
// the button foreground colour until it does.
|
|
367
|
+
$hover-focus-color: _oPrivateButtonsGetMixColor(
|
|
368
|
+
$color-a: $color,
|
|
369
|
+
$color-b: oPrivateColorsGetTextColor($hover-focus-background-opaque, 100),
|
|
370
|
+
$contrast-color: $hover-focus-background-opaque,
|
|
371
|
+
$contrast-aim: 4.5,
|
|
372
|
+
$preferred-mix: 100,
|
|
373
|
+
$minimum-mix: 0,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// Get active colours.
|
|
377
|
+
$active-background: $default-color;
|
|
378
|
+
$active-color: oPrivateColorsGetTextColor($active-background, 100);
|
|
379
|
+
|
|
380
|
+
// Confirm the contrast of default state.
|
|
381
|
+
$contrast-error-message: 'Could not create an accessible secondary button of colour "#{$color}" for a background context "#{$context-color}".';
|
|
382
|
+
$default-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
383
|
+
$default-color,
|
|
384
|
+
$default-background-opaque
|
|
385
|
+
);
|
|
386
|
+
@if $default-contrast-ratio < 4.5 {
|
|
387
|
+
@error ($contrast-error-message + ' The default text and background colour do not pass WCAG AA contrast checks.');
|
|
388
|
+
}
|
|
389
|
+
// Confirm the focus/hover state contrast.
|
|
390
|
+
$hover-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
391
|
+
$hover-focus-color,
|
|
392
|
+
$hover-focus-background-opaque
|
|
393
|
+
);
|
|
394
|
+
@if $hover-contrast-ratio < 4.5 {
|
|
395
|
+
@error ($contrast-error-message + ' The text and background colour of the hover and focus state do not pass WCAG AA contrast checks.');
|
|
396
|
+
}
|
|
397
|
+
// Confirm the active state contrast.
|
|
398
|
+
$active-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
399
|
+
$active-color,
|
|
400
|
+
$active-background
|
|
401
|
+
);
|
|
402
|
+
@if $active-contrast-ratio < 4.5 {
|
|
403
|
+
@error ($contrast-error-message + ' The text and background colour of the active state do not pass WCAG AA contrast checks.');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@return (
|
|
407
|
+
'color': $default-color,
|
|
408
|
+
'background': $default-background,
|
|
409
|
+
'border': $default-color,
|
|
410
|
+
'hover-background': $hover-focus-background,
|
|
411
|
+
'hover-color': $hover-focus-color,
|
|
412
|
+
'focus-background': $hover-focus-background,
|
|
413
|
+
'focus-color': $hover-focus-color,
|
|
414
|
+
'active-background': $active-background,
|
|
415
|
+
'active-color': $active-color
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/// For a colour and context colour (the background colour behind the button),
|
|
420
|
+
/// generate ghost button colours and overrides for each button state.
|
|
421
|
+
///
|
|
422
|
+
/// @example
|
|
423
|
+
/// $colors: _oPrivateButtonsGetGhostButtonColors('lemon', 'slate');
|
|
424
|
+
/// // (
|
|
425
|
+
/// // "color": #ffec1a,
|
|
426
|
+
/// // "background": transparent,
|
|
427
|
+
/// // "border": #ffec1a,
|
|
428
|
+
/// // "hover-background": rgba(255, 236, 26, 0.15),
|
|
429
|
+
/// // "hover-color": #ffec1a,
|
|
430
|
+
/// // "focus-background": rgba(255, 236, 26, 0.15),
|
|
431
|
+
/// // "focus-color": #ffec1a,
|
|
432
|
+
/// // "active-background": #ffec1a,
|
|
433
|
+
/// // "active-color": black
|
|
434
|
+
/// // )
|
|
435
|
+
///
|
|
436
|
+
/// @param {String|Color} $color - The button colour e.g. 'teal' (from `o-colors` palette).
|
|
437
|
+
/// @param {String|Color} $context-color - The page colour behind the button (from `o-colors` palette).
|
|
438
|
+
/// @return {Map} - Colours for a ghost button. Including overrides for each button state.
|
|
439
|
+
@function _oPrivateButtonsGetGhostButtonColors($color, $context-color) {
|
|
440
|
+
$default-color: if(
|
|
441
|
+
type-of($color) == 'string',
|
|
442
|
+
oPrivateFoundationGet($color),
|
|
443
|
+
$color
|
|
444
|
+
);
|
|
445
|
+
$default-background: transparent;
|
|
446
|
+
$default-background-opaque: if(
|
|
447
|
+
type-of($context-color) == 'string',
|
|
448
|
+
oPrivateFoundationGet($context-color),
|
|
449
|
+
$context-color
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
// Get hover/focus colors:
|
|
453
|
+
// The secondary button hover/focus background is transparent.
|
|
454
|
+
// It should be 15% opacity if the contrast ratio against the button
|
|
455
|
+
// copy is passes WCAG AA contrast checks, otherwise use 10%.
|
|
456
|
+
$hover-focus-background-mix: _oPrivateButtonsGetMix(
|
|
457
|
+
$color-a: $color,
|
|
458
|
+
$color-b: $context-color,
|
|
459
|
+
$contrast-color: $color,
|
|
460
|
+
$contrast-aim: 4.5,
|
|
461
|
+
$preferred-mix: 15,
|
|
462
|
+
$minimum-mix: 10,
|
|
463
|
+
);
|
|
464
|
+
// Use transparency so the button may be used on a different background
|
|
465
|
+
// context, and trust the user to test accessibility in their app.
|
|
466
|
+
// But use an opaque mix to test contrast with the known context.
|
|
467
|
+
$hover-focus-background: oPrivateColorsMix(
|
|
468
|
+
$color,
|
|
469
|
+
transparent,
|
|
470
|
+
$hover-focus-background-mix
|
|
471
|
+
);
|
|
472
|
+
$hover-focus-background-opaque: oPrivateColorsMix(
|
|
473
|
+
$color,
|
|
474
|
+
$context-color,
|
|
475
|
+
$hover-focus-background-mix
|
|
476
|
+
);
|
|
477
|
+
// If the hover/focus state doesn't have high enough contrast darken/lighten
|
|
478
|
+
// the button foreground colour until it does.
|
|
479
|
+
$hover-focus-color: _oPrivateButtonsGetMixColor(
|
|
480
|
+
$color-a: $color,
|
|
481
|
+
$color-b: oPrivateColorsGetTextColor($hover-focus-background-opaque, 100),
|
|
482
|
+
$contrast-color: $hover-focus-background-opaque,
|
|
483
|
+
$contrast-aim: 4.5,
|
|
484
|
+
$preferred-mix: 100,
|
|
485
|
+
$minimum-mix: 0,
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// Get active colours.
|
|
489
|
+
$active-background: $default-color;
|
|
490
|
+
$active-color: oPrivateColorsGetTextColor($active-background, 100);
|
|
491
|
+
|
|
492
|
+
// Confirm the contrast of default state.
|
|
493
|
+
$contrast-error-message: 'Could not create an accessible ghost button of colour "#{$color}" for a background context "#{$context-color}".';
|
|
494
|
+
$default-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
495
|
+
$default-color,
|
|
496
|
+
$default-background-opaque
|
|
497
|
+
);
|
|
498
|
+
@if $default-contrast-ratio < 4.5 {
|
|
499
|
+
@error ($contrast-error-message + ' The default text and background colour do not pass WCAG AA contrast checks.');
|
|
500
|
+
}
|
|
501
|
+
// Confirm the focus/hover state contrast.
|
|
502
|
+
$hover-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
503
|
+
$hover-focus-color,
|
|
504
|
+
$hover-focus-background-opaque
|
|
505
|
+
);
|
|
506
|
+
@if $hover-contrast-ratio < 4.5 {
|
|
507
|
+
@error ($contrast-error-message + ' The text and background colour of the hover and focus state do not pass WCAG AA contrast checks.');
|
|
508
|
+
}
|
|
509
|
+
// Confirm the active state contrast.
|
|
510
|
+
$active-contrast-ratio: oPrivateColorsGetContrastRatio(
|
|
511
|
+
$active-color,
|
|
512
|
+
$active-background
|
|
513
|
+
);
|
|
514
|
+
@if $active-contrast-ratio < 4.5 {
|
|
515
|
+
@error ($contrast-error-message + ' The text and background colour of the active state do not pass WCAG AA contrast checks.');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
@return (
|
|
519
|
+
'color': $default-color,
|
|
520
|
+
'background': $default-background,
|
|
521
|
+
'border': $default-background,
|
|
522
|
+
'hover-background': $hover-focus-background,
|
|
523
|
+
'hover-color': $hover-focus-color,
|
|
524
|
+
'focus-background': $hover-focus-background,
|
|
525
|
+
'focus-color': $hover-focus-color,
|
|
526
|
+
'active-background': $active-background,
|
|
527
|
+
'active-color': $active-color
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/// Mix two colours, a and b, at the preferred mix percentage. If the contrast
|
|
532
|
+
/// of the mix does not meet the contrast ratio aimed for try a lower mix
|
|
533
|
+
/// percentage incrementally until either the contrast meets our aim or the
|
|
534
|
+
/// minimum mix percentage is reached.
|
|
535
|
+
///
|
|
536
|
+
/// @param {Color|String} $color-a - the colour to mix
|
|
537
|
+
/// @param {Color|String} $color-b - the colour to mix
|
|
538
|
+
/// @param {Color} $contrast-color - the colour to compare the mix contrast ratio against
|
|
539
|
+
/// @param {Number} $contrast-aim - the desired contrast ratio between the mix and contrast colour
|
|
540
|
+
/// @param {Number} $preferred-mix - the preferable percentage to mix colours a and b
|
|
541
|
+
/// @param {Number} $minimum-mix - the minimum percentage to mix colours a and b by if the aimed for contrast ratio is not met
|
|
542
|
+
/// @param {Color} - A mix of colours a and b.
|
|
543
|
+
@function _oPrivateButtonsGetMixColor(
|
|
544
|
+
$color-a,
|
|
545
|
+
$color-b,
|
|
546
|
+
$contrast-color,
|
|
547
|
+
$contrast-aim,
|
|
548
|
+
$preferred-mix,
|
|
549
|
+
$minimum-mix
|
|
550
|
+
) {
|
|
551
|
+
$mix: _oPrivateButtonsGetMix(
|
|
552
|
+
$color-a,
|
|
553
|
+
$color-b,
|
|
554
|
+
$contrast-color,
|
|
555
|
+
$contrast-aim,
|
|
556
|
+
$preferred-mix,
|
|
557
|
+
$minimum-mix
|
|
558
|
+
);
|
|
559
|
+
@return oPrivateColorsMix($color-a, $color-b, $mix);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/// The mix percentage used by `_oPrivateButtonsGetMixColor`.
|
|
563
|
+
/// @see _oPrivateButtonsGetMixColor
|
|
564
|
+
///
|
|
565
|
+
/// @param {Color|String} $color-a - the colour to mix
|
|
566
|
+
/// @param {Color|String} $color-b - the colour to mix
|
|
567
|
+
/// @param {Color} $contrast-color - the colour to compare the mix contrast ratio against
|
|
568
|
+
/// @param {Number} $contrast-aim - the desired contrast ratio between the mix and contrast colour
|
|
569
|
+
/// @param {Number} $preferred-mix - the preferable percentage to mix colours a and b
|
|
570
|
+
/// @param {Number} $minimum-mix - the minimum percentage to mix colours a and b by if the aimed for contrast ratio is not met
|
|
571
|
+
/// @param {Number} - A mix percentage for colours a and b
|
|
572
|
+
@function _oPrivateButtonsGetMix(
|
|
573
|
+
$color-a,
|
|
574
|
+
$color-b,
|
|
575
|
+
$contrast-color,
|
|
576
|
+
$contrast-aim,
|
|
577
|
+
$preferred-mix,
|
|
578
|
+
$minimum-mix
|
|
579
|
+
) {
|
|
580
|
+
$decrement: 5;
|
|
581
|
+
$value: $preferred-mix;
|
|
582
|
+
@while $value >= $minimum-mix {
|
|
583
|
+
$mix: oPrivateColorsMix($color-a, $color-b, $value);
|
|
584
|
+
$ratio: oPrivateColorsGetContrastRatio($mix, $contrast-color);
|
|
585
|
+
@if $ratio >= $contrast-aim {
|
|
586
|
+
@return $value;
|
|
587
|
+
}
|
|
588
|
+
$value: $value - $decrement;
|
|
589
|
+
}
|
|
590
|
+
@return $minimum-mix;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/// For a given tint colour return a darker tint which either meets the contrast
|
|
594
|
+
/// ratio we're aiming for or is the lowest tint we can use.
|
|
595
|
+
/// Start with a decrement of 10, then decrement by 5 onward.
|
|
596
|
+
///
|
|
597
|
+
// This contrast ratio and decrement is arbitrary, chosen
|
|
598
|
+
// to recreate as close as possible colours chosen by the design
|
|
599
|
+
// team (each button state used to be specified manually).
|
|
600
|
+
@function _oPrivateButtonsGetDarkerTint(
|
|
601
|
+
$tint,
|
|
602
|
+
$contrast-aim,
|
|
603
|
+
$minimum-tint-value
|
|
604
|
+
) {
|
|
605
|
+
// Current tint.
|
|
606
|
+
$tint-details: oPrivateColorsGetToneDetails($tint);
|
|
607
|
+
$tint-color: map-get($tint-details, 'color-name');
|
|
608
|
+
$tint-brightness: map-get($tint-details, 'brightness');
|
|
609
|
+
// Lower the tint value.
|
|
610
|
+
// Ensure the initial tint value isn't lower than the minimum value.
|
|
611
|
+
$initial-tint-decrement: 10;
|
|
612
|
+
$decrement: 5;
|
|
613
|
+
$initial-tint-value: $tint-brightness - $initial-tint-decrement;
|
|
614
|
+
$value: if(
|
|
615
|
+
$initial-tint-value > $minimum-tint-value,
|
|
616
|
+
$initial-tint-value,
|
|
617
|
+
$minimum-tint-value
|
|
618
|
+
);
|
|
619
|
+
// Return the new tint if the current decrement is of high enough contrast.
|
|
620
|
+
@while $value >= $minimum-tint-value {
|
|
621
|
+
$darker-tint: oPrivateColorsGetTone($tint-color, $value);
|
|
622
|
+
$ratio: oPrivateColorsGetContrastRatio($darker-tint, $tint);
|
|
623
|
+
@if $ratio >= $contrast-aim {
|
|
624
|
+
@return $darker-tint;
|
|
625
|
+
}
|
|
626
|
+
$value: $value - $decrement;
|
|
627
|
+
}
|
|
628
|
+
// No tint of a high enough contrast could be produced.
|
|
629
|
+
// Return the darkest tint we'll allow.
|
|
630
|
+
@return oPrivateColorsGetTone($tint-color, $minimum-tint-value);
|
|
631
|
+
}
|