@justeattakeaway/pie-css 0.31.1 → 0.32.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.
@@ -0,0 +1,434 @@
1
+ // INTERNAL USE ONLY
2
+ // These mixins are intended for internal use by PIE components (e.g., pie-button).
3
+ // They are not part of the public API and may change without notice.
4
+ // For styling elements as buttons in your application, use the pre-compiled CSS classes
5
+ // from @justeattakeaway/pie-css/dist/components/button.css instead.
6
+ // See: packages/tools/pie-css/docs/components/BUTTON.md
7
+
8
+ @use '../interactiveStates' as *;
9
+ @use '../focus' as *;
10
+ @use '../../functions' as *;
11
+ @use '../../settings' as *;
12
+
13
+ // ============================================================================
14
+ // Button Base
15
+ // ============================================================================
16
+
17
+ /// Core mixin that applies all base button styles including layout, typography,
18
+ /// custom properties, and focus ring.
19
+ /// @param {Boolean} $include-icon-selector [true] - Whether to include the `.c-pieIcon` sizing rule.
20
+ /// Set to `false` when consuming from pie-button (which handles icon sizing via `::slotted(svg)`).
21
+ /// @example scss - Basic usage
22
+ /// .my-button {
23
+ /// @include button-base();
24
+ /// }
25
+ @mixin button-base($include-icon-selector: true) {
26
+ // CSS custom properties for theming
27
+ --btn-border-radius: var(--dt-radius-rounded-e);
28
+ --btn-font-family: var(--dt-font-interactive-l-family);
29
+ --btn-font-weight: var(--dt-font-interactive-l-weight);
30
+
31
+ // Default background and text colour (primary variant)
32
+ --int-states-mixin-bg-color: var(--dt-color-interactive-brand);
33
+ --btn-text-color: var(--dt-color-content-interactive-light-solid);
34
+
35
+ // Vertical padding values per size
36
+ --btn-padding-vertical-xsmall: 6px;
37
+ --btn-padding-vertical-small-expressive: 8px;
38
+ --btn-padding-vertical-small-productive: 10px;
39
+ --btn-padding-vertical-medium: 12px;
40
+ --btn-padding-vertical-large: 16px;
41
+
42
+ // Horizontal padding values per size
43
+ --btn-padding-horizontal-small: var(--dt-spacing-b);
44
+ --btn-padding-horizontal-medium: var(--dt-spacing-d);
45
+ --btn-padding-horizontal-large: var(--dt-spacing-e);
46
+
47
+ // Layout
48
+ position: relative;
49
+ display: inline-flex;
50
+ gap: var(--dt-spacing-b);
51
+ align-items: center;
52
+ justify-content: center;
53
+
54
+ // Appearance
55
+ border: none;
56
+ border-radius: var(--btn-border-radius);
57
+ outline: none;
58
+ background-color: var(--int-states-mixin-bg-color);
59
+ cursor: pointer;
60
+ user-select: none;
61
+ text-decoration: none;
62
+
63
+ // Typography
64
+ font-family: var(--btn-font-family);
65
+ font-size: var(--btn-font-size);
66
+ font-weight: var(--btn-font-weight);
67
+ color: var(--btn-text-color);
68
+ line-height: var(--btn-line-height);
69
+
70
+ // Width control
71
+ inline-size: var(--btn-inline-size);
72
+
73
+ // Focus styles
74
+ &:focus-visible {
75
+ @include focus;
76
+ }
77
+
78
+ // Icon sizing via .c-pieIcon (CSS-only buttons using pie-icons-webc)
79
+ @if $include-icon-selector {
80
+ .c-pieIcon {
81
+ height: var(--btn-icon-size);
82
+ width: var(--btn-icon-size);
83
+ }
84
+ }
85
+ }
86
+
87
+ // ============================================================================
88
+ // Button Sizes
89
+ // ============================================================================
90
+
91
+ /// Internal mixin for setting font-size, line-height, and icon size per size.
92
+ /// @param {String} $size - The size name
93
+ /// @access private
94
+ @mixin button-font-size($size) {
95
+ @if $size == 'xsmall' {
96
+ --btn-font-size: #{font-size(--dt-font-interactive-xs-size)};
97
+ --btn-line-height: calc(var(--dt-font-interactive-xs-line-height) * 1px);
98
+ --btn-icon-size: 16px;
99
+ --icon-size-override: 16px;
100
+ } @else if $size == 'small-expressive' {
101
+ --btn-font-size: #{font-size(--dt-font-interactive-l-size)};
102
+ --btn-line-height: calc(var(--dt-font-interactive-l-line-height) * 1px);
103
+ --btn-icon-size: 20px;
104
+ --icon-size-override: 20px;
105
+ } @else if $size == 'small-productive' {
106
+ --btn-font-size: #{font-size(--dt-font-interactive-s-size)};
107
+ --btn-line-height: calc(var(--dt-font-interactive-s-line-height) * 1px);
108
+ --btn-icon-size: 20px;
109
+ --icon-size-override: 20px;
110
+ } @else if $size == 'medium' {
111
+ --btn-font-size: #{font-size(--dt-font-interactive-l-size)};
112
+ --btn-line-height: calc(var(--dt-font-interactive-l-line-height) * 1px);
113
+ --btn-icon-size: 24px;
114
+ --icon-size-override: 24px;
115
+ } @else if $size == 'large' {
116
+ --btn-font-size: #{font-size(--dt-font-interactive-l-size)};
117
+ --btn-line-height: calc(var(--dt-font-interactive-l-line-height) * 1px);
118
+ --btn-icon-size: 24px;
119
+ --icon-size-override: 24px;
120
+ }
121
+ }
122
+
123
+ /// Internal mixin for setting padding per size.
124
+ /// @param {String} $size - The size name
125
+ /// @param {Boolean} $offset-padding-for-border [false] - Whether to offset padding by 1px for bordered variants
126
+ /// @access private
127
+ @mixin button-padding($size, $offset-padding-for-border: false) {
128
+ $vertical-padding: '';
129
+ $horizontal-padding: '';
130
+
131
+ @if $size == 'xsmall' {
132
+ $vertical-padding: '--btn-padding-vertical-xsmall';
133
+ $horizontal-padding: '--btn-padding-horizontal-small';
134
+ } @else if $size == 'small-expressive' {
135
+ $vertical-padding: '--btn-padding-vertical-small-expressive';
136
+ $horizontal-padding: '--btn-padding-horizontal-medium';
137
+ } @else if $size == 'small-productive' {
138
+ $vertical-padding: '--btn-padding-vertical-small-productive';
139
+ $horizontal-padding: '--btn-padding-horizontal-medium';
140
+ } @else if $size == 'medium' {
141
+ $vertical-padding: '--btn-padding-vertical-medium';
142
+ $horizontal-padding: '--btn-padding-horizontal-large';
143
+ } @else if $size == 'large' {
144
+ $vertical-padding: '--btn-padding-vertical-large';
145
+ $horizontal-padding: '--btn-padding-horizontal-large';
146
+ }
147
+
148
+ @if $offset-padding-for-border {
149
+ padding: calc(var(#{$vertical-padding}) - 1px) var(#{$horizontal-padding});
150
+ } @else {
151
+ padding: var(#{$vertical-padding}) var(#{$horizontal-padding});
152
+ }
153
+ }
154
+
155
+ /// Mixin for responsive behaviour.
156
+ /// When the responsive modifier class is present and viewport is >md, applies the content.
157
+ /// @param {String} $prefix ['c-button'] - The class prefix for modifier selectors
158
+ /// @access private
159
+ @mixin responsive-wide($prefix: 'c-button') {
160
+ &.#{$prefix}--responsive {
161
+ @include media('>md') {
162
+ @content;
163
+ }
164
+ }
165
+ }
166
+
167
+ /// Size mixin that sets font-size, line-height, padding, icon size and responsive behaviour.
168
+ /// @param {String} $size - One of: xsmall, small-productive, small-expressive, medium, large
169
+ /// @param {String} $prefix ['c-button'] - The class prefix for modifier selectors
170
+ /// @example scss - Basic usage
171
+ /// .my-button--medium {
172
+ /// @include button-size('medium');
173
+ /// }
174
+ @mixin button-size($size, $prefix: 'c-button') {
175
+ @include button-font-size($size);
176
+ @include button-padding($size);
177
+
178
+ @if $size == 'xsmall' {
179
+ @include responsive-wide($prefix) {
180
+ // productive is the default responsive size
181
+ @include button-font-size('small-productive');
182
+ @include button-padding('small-productive');
183
+
184
+ &.#{$prefix}--expressive {
185
+ @include button-font-size('small-expressive');
186
+ @include button-padding('small-expressive');
187
+ }
188
+ }
189
+
190
+ &.#{$prefix}--outline,
191
+ &.#{$prefix}--outline-inverse {
192
+ @include button-padding('xsmall', true);
193
+
194
+ @include responsive-wide($prefix) {
195
+ @include button-padding('small-productive', true);
196
+
197
+ &.#{$prefix}--expressive {
198
+ @include button-padding('small-expressive', true);
199
+ }
200
+ }
201
+ }
202
+ } @else if $size == 'small-expressive' {
203
+ @include responsive-wide($prefix) {
204
+ @include button-font-size('medium');
205
+ @include button-padding('medium');
206
+ }
207
+
208
+ &.#{$prefix}--outline,
209
+ &.#{$prefix}--outline-inverse {
210
+ @include button-padding('small-expressive', true);
211
+
212
+ @include responsive-wide($prefix) {
213
+ @include button-padding('medium', true);
214
+ }
215
+ }
216
+ } @else if $size == 'small-productive' {
217
+ @include responsive-wide($prefix) {
218
+ @include button-font-size('medium');
219
+ @include button-padding('medium');
220
+ }
221
+
222
+ &.#{$prefix}--outline,
223
+ &.#{$prefix}--outline-inverse {
224
+ @include button-padding('small-productive', true);
225
+
226
+ @include responsive-wide($prefix) {
227
+ @include button-padding('medium', true);
228
+ }
229
+ }
230
+ } @else if $size == 'medium' {
231
+ @include responsive-wide($prefix) {
232
+ @include button-font-size('large');
233
+ @include button-padding('large');
234
+ }
235
+
236
+ &.#{$prefix}--outline,
237
+ &.#{$prefix}--outline-inverse {
238
+ @include button-padding('medium', true);
239
+
240
+ @include responsive-wide($prefix) {
241
+ @include button-padding('large', true);
242
+ }
243
+ }
244
+ } @else if $size == 'large' {
245
+ &.#{$prefix}--outline,
246
+ &.#{$prefix}--outline-inverse {
247
+ @include button-padding('large', true);
248
+ }
249
+ }
250
+ }
251
+
252
+ // ============================================================================
253
+ // Button Variants
254
+ // ============================================================================
255
+
256
+ /// Variant mixin that sets background colour, text colour and interactive states.
257
+ /// @param {String} $variant - One of: primary, primary-alternative, primary-alternative-dark,
258
+ /// secondary, outline, ghost, ghost-dark, inverse, ghost-inverse, outline-inverse,
259
+ /// ghost-inverse-light, destructive, destructive-ghost
260
+ /// @param {String} $prefix ['c-button'] - The class prefix for modifier selectors
261
+ /// @example scss - Basic usage
262
+ /// .my-button--primary {
263
+ /// @include button-variant('primary');
264
+ /// }
265
+ @mixin button-variant($variant, $prefix: 'c-button') {
266
+ @if $variant == 'primary' {
267
+ @include interactive-states('--dt-color-interactive-brand');
268
+
269
+ // Where the font-size is smaller, update the button backgrounds
270
+ // so that the text is accessible
271
+ &.#{$prefix}--xsmall,
272
+ &.#{$prefix}--small-productive {
273
+ --int-states-mixin-bg-color: var(--dt-color-interactive-primary);
274
+ --btn-text-color: var(--dt-color-content-interactive-primary-solid);
275
+
276
+ @include interactive-states('--dt-color-interactive-primary', 'inverse', '02');
277
+ }
278
+
279
+ // Handle the background colour change getting overridden above
280
+ // but then being put in responsive mode
281
+ &.#{$prefix}--xsmall.#{$prefix}--expressive,
282
+ &.#{$prefix}--small-productive {
283
+ @include responsive-wide($prefix) {
284
+ --int-states-mixin-bg-color: var(--dt-color-interactive-brand);
285
+
286
+ @include interactive-states('--dt-color-interactive-brand');
287
+ }
288
+ }
289
+ } @else if $variant == 'primary-alternative' {
290
+ --int-states-mixin-bg-color: var(--dt-color-interactive-primary);
291
+ --btn-text-color: var(--dt-color-content-interactive-primary);
292
+
293
+ @include interactive-states('--dt-color-interactive-primary', 'inverse', '02');
294
+ } @else if $variant == 'primary-alternative-dark' {
295
+ --int-states-mixin-bg-color: var(--dt-color-interactive-dark);
296
+ --btn-text-color: var(--dt-color-content-interactive-light);
297
+
298
+ @include interactive-states('--dt-color-interactive-dark', 'inverse', '02');
299
+ } @else if $variant == 'secondary' {
300
+ --int-states-mixin-bg-color: var(--dt-color-interactive-secondary);
301
+ --btn-text-color: var(--dt-color-content-interactive-secondary);
302
+
303
+ @include interactive-states('--dt-color-interactive-secondary');
304
+ } @else if $variant == 'outline' {
305
+ --int-states-mixin-bg-color: transparent;
306
+ --btn-text-color: var(--dt-color-content-interactive-secondary-solid);
307
+
308
+ border: 1px solid var(--dt-color-border-strong);
309
+
310
+ @include interactive-states('--dt-color-transparent', 'transparent', '01');
311
+ } @else if $variant == 'ghost' {
312
+ --int-states-mixin-bg-color: transparent;
313
+ --btn-text-color: var(--dt-color-content-interactive-secondary-solid);
314
+
315
+ @include interactive-states('--dt-color-transparent', 'transparent', '01');
316
+ } @else if $variant == 'ghost-dark' {
317
+ --int-states-mixin-bg-color: transparent;
318
+ --btn-text-color: var(--dt-color-content-interactive-dark-solid);
319
+
320
+ @include interactive-states('--dt-color-transparent', 'transparent', '01');
321
+ } @else if $variant == 'inverse' {
322
+ --int-states-mixin-bg-color: var(--dt-color-interactive-inverse);
323
+ --btn-text-color: var(--dt-color-content-interactive-secondary);
324
+
325
+ @include interactive-states('--dt-color-interactive-inverse');
326
+ } @else if $variant == 'ghost-inverse' {
327
+ --int-states-mixin-bg-color: transparent;
328
+ --btn-text-color: var(--dt-color-content-interactive-primary-solid);
329
+
330
+ @include interactive-states('--dt-color-transparent', 'transparent-inverse', '02');
331
+ } @else if $variant == 'outline-inverse' {
332
+ --int-states-mixin-bg-color: transparent;
333
+ --btn-text-color: var(--dt-color-content-interactive-primary-solid);
334
+
335
+ border: 1px solid var(--dt-color-border-strong);
336
+
337
+ @include interactive-states('--dt-color-transparent', 'transparent-inverse', '02');
338
+ } @else if $variant == 'ghost-inverse-light' {
339
+ --int-states-mixin-bg-color: transparent;
340
+ --btn-text-color: var(--dt-color-content-interactive-light-solid);
341
+
342
+ @include interactive-states('--dt-color-transparent', 'transparent-inverse', '02');
343
+ } @else if $variant == 'destructive' {
344
+ --int-states-mixin-bg-color: var(--dt-color-support-error);
345
+
346
+ @include interactive-states('--dt-color-support-error');
347
+ } @else if $variant == 'destructive-ghost' {
348
+ --int-states-mixin-bg-color: transparent;
349
+ --btn-text-color: var(--dt-color-content-interactive-error-solid);
350
+
351
+ @include interactive-states('--dt-color-transparent', 'transparent', '01');
352
+ }
353
+ }
354
+
355
+ // ============================================================================
356
+ // Button Disabled
357
+ // ============================================================================
358
+
359
+ /// Disabled state modifier that overrides colours and cursor.
360
+ /// @param {String} $prefix ['c-button'] - The class prefix for modifier selectors.
361
+ /// For pie-button, pass 'o-btn'.
362
+ /// @param {Boolean} $include-pointer-events [true] - Whether to set `pointer-events: none`.
363
+ /// Set to `false` for pie-button which uses the native `[disabled]` attribute instead.
364
+ /// @example scss - Basic usage
365
+ /// .my-button--disabled {
366
+ /// @include button-disabled();
367
+ /// }
368
+ @mixin button-disabled($prefix: 'c-button', $include-pointer-events: true) {
369
+ --btn-text-color: var(--dt-color-content-disabled-solid);
370
+
371
+ cursor: not-allowed;
372
+
373
+ @if $include-pointer-events {
374
+ pointer-events: none;
375
+ }
376
+
377
+ // For every variant except ghost variants, set the disabled background colour
378
+ &:not(
379
+ .#{$prefix}--ghost,
380
+ .#{$prefix}--ghost-dark,
381
+ .#{$prefix}--ghost-inverse,
382
+ .#{$prefix}--ghost-inverse-light,
383
+ .#{$prefix}--destructive-ghost
384
+ ) {
385
+ --int-states-mixin-bg-color: var(--dt-color-disabled-01);
386
+ --btn-text-color: var(--dt-color-content-disabled);
387
+ }
388
+
389
+ // For outline variants, set the border to the disabled colour
390
+ &.#{$prefix}--outline,
391
+ &.#{$prefix}--outline-inverse {
392
+ border-color: var(--dt-color-disabled-01);
393
+ }
394
+ }
395
+
396
+ // ============================================================================
397
+ // Button Full Width
398
+ // ============================================================================
399
+
400
+ /// Full-width modifier that makes the button span 100% of its container.
401
+ /// @example scss - Basic usage
402
+ /// .my-button--fullWidth {
403
+ /// @include button-full-width();
404
+ /// }
405
+ @mixin button-full-width() {
406
+ --btn-inline-size: 100%;
407
+ }
408
+
409
+ // ============================================================================
410
+ // Button Truncate
411
+ // ============================================================================
412
+
413
+ /// Optional modifier that truncates the button label with an ellipsis when the
414
+ /// text overflows the button's width. This only takes effect when the button's
415
+ /// width is constrained externally (e.g. via `button-full-width` inside a sized
416
+ /// container, or a `max-width` on a parent element).
417
+ ///
418
+ /// The `> span` selector is required because `text-overflow: ellipsis` does not
419
+ /// work directly on flex containers. The label must be wrapped in a `<span>`.
420
+ ///
421
+ /// `white-space: nowrap` is required because, unlike pie-button (which has a
422
+ /// shadow DOM host set to inline-block that constrains the inner flex container),
423
+ /// the CSS-only button IS the flex container with no outer wrapper.
424
+ /// @example scss - Basic usage
425
+ /// .my-button--truncate {
426
+ /// @include button-truncate();
427
+ /// }
428
+ @mixin button-truncate() {
429
+ > span {
430
+ text-overflow: ellipsis;
431
+ overflow: hidden;
432
+ white-space: nowrap;
433
+ }
434
+ }
@@ -1 +1,2 @@
1
+ @forward 'button';
1
2
  @forward 'radio';