@slotted/styles 0.0.0-stage → 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +10 -2
- package/package.json +276 -3
- package/src/accordion/accordion.css +96 -0
- package/src/accordion/accordion.tokens.json +14 -0
- package/src/action-bar/action-bar.css +455 -0
- package/src/action-bar/action-bar.tokens.json +50 -0
- package/src/action-sheet/action-sheet.css +462 -0
- package/src/action-sheet/action-sheet.tokens.json +59 -0
- package/src/alert/alert.css +764 -0
- package/src/alert/alert.tokens.json +77 -0
- package/src/app-shell/app-shell.css +1899 -0
- package/src/app-shell/app-shell.tokens.json +88 -0
- package/src/aspect-ratio/aspect-ratio.css +131 -0
- package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
- package/src/attachment/attachment.css +906 -0
- package/src/attachment/attachment.tokens.json +85 -0
- package/src/avatar/avatar.css +598 -0
- package/src/avatar/avatar.tokens.json +64 -0
- package/src/avatar-group/avatar-group.css +268 -0
- package/src/avatar-group/avatar-group.tokens.json +16 -0
- package/src/badge/badge.css +462 -0
- package/src/badge/badge.tokens.json +58 -0
- package/src/breadcrumb/breadcrumb.css +441 -0
- package/src/breadcrumb/breadcrumb.tokens.json +50 -0
- package/src/button/button.css +754 -0
- package/src/button/button.tokens.json +72 -0
- package/src/button-group/button-group.css +455 -0
- package/src/button-group/button-group.tokens.json +11 -0
- package/src/calendar/calendar.css +846 -0
- package/src/calendar/calendar.tokens.json +67 -0
- package/src/card/card.css +602 -0
- package/src/card/card.tokens.json +68 -0
- package/src/carousel/carousel.css +860 -0
- package/src/carousel/carousel.tokens.json +62 -0
- package/src/cascader/cascader.css +1320 -0
- package/src/cascader/cascader.tokens.json +103 -0
- package/src/chart/chart.css +1473 -0
- package/src/chart/chart.tokens.json +107 -0
- package/src/checkbox/checkbox.css +1011 -0
- package/src/checkbox/checkbox.tokens.json +102 -0
- package/src/checkbox-group/checkbox-group.css +70 -0
- package/src/checkbox-group/checkbox-group.tokens.json +6 -0
- package/src/code-block/code-block.css +555 -0
- package/src/code-block/code-block.tokens.json +69 -0
- package/src/collapsible/collapsible.css +546 -0
- package/src/collapsible/collapsible.tokens.json +55 -0
- package/src/color-picker/color-picker.css +607 -0
- package/src/color-picker/color-picker.tokens.json +58 -0
- package/src/column-manager/column-manager.css +452 -0
- package/src/column-manager/column-manager.tokens.json +43 -0
- package/src/combobox/combobox.css +1048 -0
- package/src/combobox/combobox.tokens.json +112 -0
- package/src/command-palette/command-palette.css +1000 -0
- package/src/command-palette/command-palette.tokens.json +75 -0
- package/src/container/container.css +198 -0
- package/src/container/container.tokens.json +18 -0
- package/src/context-menu/context-menu.css +104 -0
- package/src/context-menu/context-menu.tokens.json +11 -0
- package/src/currency-field/currency-field.css +787 -0
- package/src/currency-field/currency-field.tokens.json +80 -0
- package/src/dashboard-grid/dashboard-grid.css +748 -0
- package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
- package/src/data-grid/data-grid.css +2484 -0
- package/src/data-grid/data-grid.tokens.json +100 -0
- package/src/data-list/data-list.css +455 -0
- package/src/data-list/data-list.tokens.json +35 -0
- package/src/date-field/date-field.css +560 -0
- package/src/date-field/date-field.tokens.json +80 -0
- package/src/date-picker/date-picker.css +420 -0
- package/src/date-picker/date-picker.tokens.json +49 -0
- package/src/date-range-picker/date-range-picker.css +983 -0
- package/src/date-range-picker/date-range-picker.tokens.json +109 -0
- package/src/datetime-field/datetime-field.css +850 -0
- package/src/datetime-field/datetime-field.tokens.json +91 -0
- package/src/datetime-picker/datetime-picker.css +843 -0
- package/src/datetime-picker/datetime-picker.tokens.json +78 -0
- package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
- package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
- package/src/description-list/description-list.css +675 -0
- package/src/description-list/description-list.tokens.json +66 -0
- package/src/detail-panel/detail-panel.css +329 -0
- package/src/detail-panel/detail-panel.tokens.json +33 -0
- package/src/dialog/dialog.css +968 -0
- package/src/dialog/dialog.tokens.json +66 -0
- package/src/divider/divider.css +131 -0
- package/src/divider/divider.tokens.json +18 -0
- package/src/drawer/drawer.css +1303 -0
- package/src/drawer/drawer.tokens.json +75 -0
- package/src/entity-picker/entity-picker.css +1367 -0
- package/src/entity-picker/entity-picker.tokens.json +118 -0
- package/src/event-calendar/event-calendar.css +2148 -0
- package/src/event-calendar/event-calendar.tokens.json +87 -0
- package/src/field/field.css +707 -0
- package/src/field/field.tokens.json +58 -0
- package/src/field-group/field-group.css +226 -0
- package/src/field-group/field-group.tokens.json +33 -0
- package/src/filter-bar/filter-bar.css +1306 -0
- package/src/filter-bar/filter-bar.tokens.json +78 -0
- package/src/form/form.css +218 -0
- package/src/form/form.tokens.json +32 -0
- package/src/fullscreen/fullscreen.css +376 -0
- package/src/fullscreen/fullscreen.tokens.json +50 -0
- package/src/grid/grid.css +173 -0
- package/src/grid/grid.tokens.json +20 -0
- package/src/highlight/highlight.css +135 -0
- package/src/highlight/highlight.tokens.json +16 -0
- package/src/hover-card/hover-card.css +93 -0
- package/src/hover-card/hover-card.tokens.json +17 -0
- package/src/icon/icon.css +136 -0
- package/src/icon/icon.tokens.json +14 -0
- package/src/image/image.css +449 -0
- package/src/image/image.tokens.json +27 -0
- package/src/import-export-center/import-export-center.css +1175 -0
- package/src/import-export-center/import-export-center.tokens.json +84 -0
- package/src/inline/inline.css +149 -0
- package/src/inline/inline.tokens.json +14 -0
- package/src/inline-message/inline-message.css +396 -0
- package/src/inline-message/inline-message.tokens.json +19 -0
- package/src/kbd/kbd.css +490 -0
- package/src/kbd/kbd.tokens.json +40 -0
- package/src/link/link.css +237 -0
- package/src/link/link.tokens.json +25 -0
- package/src/list/list.css +240 -0
- package/src/list/list.tokens.json +42 -0
- package/src/list-item/list-item.css +937 -0
- package/src/list-item/list-item.tokens.json +114 -0
- package/src/listbox/listbox.css +674 -0
- package/src/listbox/listbox.tokens.json +72 -0
- package/src/loading-overlay/loading-overlay.css +730 -0
- package/src/loading-overlay/loading-overlay.tokens.json +48 -0
- package/src/markdown/markdown.css +168 -0
- package/src/markdown/markdown.tokens.json +18 -0
- package/src/menu/menu.css +817 -0
- package/src/menu/menu.tokens.json +78 -0
- package/src/menubar/menubar.css +170 -0
- package/src/menubar/menubar.tokens.json +32 -0
- package/src/message/message.css +1453 -0
- package/src/message/message.tokens.json +85 -0
- package/src/message-scroller/message-scroller.css +371 -0
- package/src/message-scroller/message-scroller.tokens.json +47 -0
- package/src/multi-select/multi-select.css +1427 -0
- package/src/multi-select/multi-select.tokens.json +114 -0
- package/src/navigation-menu/navigation-menu.css +1029 -0
- package/src/navigation-menu/navigation-menu.tokens.json +70 -0
- package/src/notification-center/notification-center.css +856 -0
- package/src/notification-center/notification-center.tokens.json +84 -0
- package/src/number-field/number-field.css +794 -0
- package/src/number-field/number-field.tokens.json +80 -0
- package/src/otp-input/otp-input.css +525 -0
- package/src/otp-input/otp-input.tokens.json +69 -0
- package/src/page/page.css +179 -0
- package/src/page/page.tokens.json +20 -0
- package/src/page-header/page-header.css +1181 -0
- package/src/page-header/page-header.tokens.json +71 -0
- package/src/pagination/pagination.css +500 -0
- package/src/pagination/pagination.tokens.json +56 -0
- package/src/popover/popover.css +526 -0
- package/src/popover/popover.tokens.json +60 -0
- package/src/progress-indicator/progress-indicator.css +1584 -0
- package/src/progress-indicator/progress-indicator.tokens.json +50 -0
- package/src/prompt-composer/prompt-composer.css +680 -0
- package/src/prompt-composer/prompt-composer.tokens.json +99 -0
- package/src/prose/prose.css +733 -0
- package/src/prose/prose.tokens.json +79 -0
- package/src/radio-group/radio-group.css +778 -0
- package/src/radio-group/radio-group.tokens.json +103 -0
- package/src/rating/rating.css +1071 -0
- package/src/rating/rating.tokens.json +88 -0
- package/src/result-state/result-state.css +980 -0
- package/src/result-state/result-state.tokens.json +63 -0
- package/src/runtime-properties.mjs +321 -0
- package/src/saved-views/saved-views.css +394 -0
- package/src/saved-views/saved-views.tokens.json +41 -0
- package/src/scroll-area/scroll-area.css +481 -0
- package/src/scroll-area/scroll-area.tokens.json +15 -0
- package/src/section/section.css +177 -0
- package/src/section/section.tokens.json +39 -0
- package/src/segmented-control/segmented-control.css +564 -0
- package/src/segmented-control/segmented-control.tokens.json +67 -0
- package/src/select/select.css +1593 -0
- package/src/select/select.tokens.json +110 -0
- package/src/side-navigation/side-navigation.css +1096 -0
- package/src/side-navigation/side-navigation.tokens.json +87 -0
- package/src/skeleton/skeleton.css +431 -0
- package/src/skeleton/skeleton.tokens.json +29 -0
- package/src/slider/slider.css +792 -0
- package/src/slider/slider.tokens.json +75 -0
- package/src/sparkline/sparkline.css +282 -0
- package/src/sparkline/sparkline.tokens.json +45 -0
- package/src/spinner/spinner.css +224 -0
- package/src/spinner/spinner.tokens.json +8 -0
- package/src/split-button/split-button.css +253 -0
- package/src/split-button/split-button.tokens.json +12 -0
- package/src/splitter/splitter.css +336 -0
- package/src/splitter/splitter.tokens.json +24 -0
- package/src/stack/stack.css +128 -0
- package/src/stack/stack.tokens.json +14 -0
- package/src/stat-card/stat-card.css +641 -0
- package/src/stat-card/stat-card.tokens.json +95 -0
- package/src/status-indicator/status-indicator.css +481 -0
- package/src/status-indicator/status-indicator.tokens.json +38 -0
- package/src/stepper/stepper.css +629 -0
- package/src/stepper/stepper.tokens.json +72 -0
- package/src/switch/switch.css +703 -0
- package/src/switch/switch.tokens.json +75 -0
- package/src/table/table.css +1482 -0
- package/src/table/table.tokens.json +69 -0
- package/src/table-of-contents/table-of-contents.css +533 -0
- package/src/table-of-contents/table-of-contents.tokens.json +53 -0
- package/src/tabs/tabs.css +998 -0
- package/src/tabs/tabs.tokens.json +82 -0
- package/src/tag/tag.css +768 -0
- package/src/tag/tag.tokens.json +85 -0
- package/src/tags-input/tags-input.css +757 -0
- package/src/tags-input/tags-input.tokens.json +84 -0
- package/src/text-field/text-field.css +900 -0
- package/src/text-field/text-field.tokens.json +87 -0
- package/src/textarea/textarea.css +365 -0
- package/src/textarea/textarea.tokens.json +59 -0
- package/src/time-field/time-field.css +719 -0
- package/src/time-field/time-field.tokens.json +86 -0
- package/src/time-picker/time-picker.css +665 -0
- package/src/time-picker/time-picker.tokens.json +66 -0
- package/src/time-range-picker/time-range-picker.css +1287 -0
- package/src/time-range-picker/time-range-picker.tokens.json +134 -0
- package/src/timeline/timeline.css +1596 -0
- package/src/timeline/timeline.tokens.json +96 -0
- package/src/toast/toast.css +943 -0
- package/src/toast/toast.tokens.json +90 -0
- package/src/toggle/toggle.css +290 -0
- package/src/toggle/toggle.tokens.json +54 -0
- package/src/toggle-group/toggle-group.css +463 -0
- package/src/toggle-group/toggle-group.tokens.json +54 -0
- package/src/toolbar/toolbar.css +434 -0
- package/src/toolbar/toolbar.tokens.json +49 -0
- package/src/tooltip/tooltip.css +368 -0
- package/src/tooltip/tooltip.tokens.json +39 -0
- package/src/top-navigation/top-navigation.css +1287 -0
- package/src/top-navigation/top-navigation.tokens.json +91 -0
- package/src/tour/tour.css +680 -0
- package/src/tour/tour.tokens.json +67 -0
- package/src/transfer/transfer.css +571 -0
- package/src/transfer/transfer.tokens.json +49 -0
- package/src/tree/tree.css +1000 -0
- package/src/tree/tree.tokens.json +80 -0
- package/src/typography/typography.css +211 -0
- package/src/typography/typography.tokens.json +31 -0
- package/src/upload/upload.css +1706 -0
- package/src/upload/upload.tokens.json +123 -0
- package/src/visually-hidden/visually-hidden.css +52 -0
- package/src/visually-hidden/visually-hidden.tokens.json +1 -0
- package/src/wizard/wizard.css +740 -0
- package/src/wizard/wizard.tokens.json +63 -0
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
@layer slotted.components {
|
|
2
|
+
/**
|
|
3
|
+
* One view out of several, and the panels under it.
|
|
4
|
+
*
|
|
5
|
+
* The family is a four-column, two-row grid, and every part is placed in a
|
|
6
|
+
* cell rather than positioned over one. That is what lets the two paging
|
|
7
|
+
* controls stand beside the list instead of floating above it: an overlay
|
|
8
|
+
* would need a gradient the width of the control, mirrored for a
|
|
9
|
+
* right-to-left document, and it would cover the hairline it sits on. Two
|
|
10
|
+
* `auto` tracks collapse to nothing while the controls are absent, so the
|
|
11
|
+
* layout costs nothing until the list stops fitting.
|
|
12
|
+
*
|
|
13
|
+
* ┌──────────┬───────────────────┬────────┬─────────┐
|
|
14
|
+
* │ scroll ◀ │ list │ ▶ │ actions │
|
|
15
|
+
* ├──────────┴───────────────────┴────────┴─────────┤
|
|
16
|
+
* │ panel │
|
|
17
|
+
* └──────────────────────────────────────────────────┘
|
|
18
|
+
*/
|
|
19
|
+
[data-ui='tabs'] {
|
|
20
|
+
/**
|
|
21
|
+
* The control scales, read once at the root, so `size` and the three
|
|
22
|
+
* densities are one decision each rather than one per rule.
|
|
23
|
+
*/
|
|
24
|
+
--_height: var(--slotted-control-height-md, 34px);
|
|
25
|
+
--_font-size: var(--slotted-control-font-size-md, 0.875rem);
|
|
26
|
+
--_padding-inline: var(--slotted-control-padding-inline-md, 0.75rem);
|
|
27
|
+
--_inner-gap: var(--slotted-control-gap-md, 0.375rem);
|
|
28
|
+
--_icon-size: var(--slotted-control-icon-size-md, 16px);
|
|
29
|
+
--_inner-size: var(--slotted-control-inner-size-md, 24px);
|
|
30
|
+
/**
|
|
31
|
+
* The distance between two tabs. A step below the control gap: two tabs are
|
|
32
|
+
* one row of one control, and the gap that separates two buttons reads as
|
|
33
|
+
* two components when what it separates is two labels on a shared line.
|
|
34
|
+
*/
|
|
35
|
+
--_list-gap: var(--slotted-space-1, 0.125rem);
|
|
36
|
+
--_track-size: var(--slotted-border-width-thin, 1px);
|
|
37
|
+
/**
|
|
38
|
+
* The box around the X, and how far that box overhangs the glyph inside it.
|
|
39
|
+
* The box is a target and the glyph is the mark: at the dense step the mark
|
|
40
|
+
* is twelve pixels, under the twenty-four WCAG 2.2 SC 2.5.8 asks for, so the
|
|
41
|
+
* box is the larger of the minimum target and the mark with a step of air
|
|
42
|
+
* around it. It was the control's whole height before, which put a
|
|
43
|
+
* thirty-four pixel square beside a sixteen pixel mark.
|
|
44
|
+
*/
|
|
45
|
+
--_dismiss-size: max(
|
|
46
|
+
var(--slotted-target-min, 24px),
|
|
47
|
+
min(calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
|
|
48
|
+
);
|
|
49
|
+
--_dismiss-overhang: calc((var(--_dismiss-size) - var(--_icon-size)) / 2);
|
|
50
|
+
--_marker-size: var(--slotted-border-width-thick, 2px);
|
|
51
|
+
/**
|
|
52
|
+
* The length of the fade a scrolling list takes at an edge that has more
|
|
53
|
+
* behind it, read once here so the five mask gradients below state a
|
|
54
|
+
* length and no public property of their own.
|
|
55
|
+
*/
|
|
56
|
+
--_fade: var(--slotted-tabs-fade-size, var(--slotted-space-6, 1rem));
|
|
57
|
+
/**
|
|
58
|
+
* Which way the selection travelled, resolved into a transform origin. The
|
|
59
|
+
* marker of the tab being left collapses toward the direction of travel
|
|
60
|
+
* and the marker of the tab being entered grows from the side the reader
|
|
61
|
+
* came from, so the pair reads as one bar moving. Nothing is measured:
|
|
62
|
+
* every surveyed indicator reads the active tab's box, which is why Base UI
|
|
63
|
+
* ships `renderBeforeHydration` and Ark UI a `setIndicatorRect`.
|
|
64
|
+
*/
|
|
65
|
+
--_marker-origin: 50%;
|
|
66
|
+
--_marker-origin-selected: 50%;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* No column gap on the grid itself: three gaps stood around two empty
|
|
70
|
+
* paging columns and an empty actions column, and the list — and the
|
|
71
|
+
* track drawn under it — started eight pixels in and stopped sixteen
|
|
72
|
+
* short. The parts that fill those columns carry the distance instead.
|
|
73
|
+
*/
|
|
74
|
+
display: grid;
|
|
75
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
76
|
+
/**
|
|
77
|
+
* One explicit row; a panel makes the second. Two explicit rows kept the
|
|
78
|
+
* row gap under a list with no panel beside it — a tab bar inside a page
|
|
79
|
+
* header stood sixteen pixels off the edge it was meant to sit on.
|
|
80
|
+
*/
|
|
81
|
+
grid-template-rows: auto;
|
|
82
|
+
min-inline-size: 0;
|
|
83
|
+
row-gap: var(--slotted-tabs-gap, var(--slotted-space-6, 1rem));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
[data-ui='tabs'][data-size='sm'] {
|
|
87
|
+
--_height: var(--slotted-control-height-sm, 30px);
|
|
88
|
+
--_font-size: var(--slotted-control-font-size-sm, 0.8125rem);
|
|
89
|
+
--_padding-inline: var(--slotted-control-padding-inline-sm, 0.625rem);
|
|
90
|
+
--_inner-gap: var(--slotted-control-gap-sm, 0.25rem);
|
|
91
|
+
--_icon-size: var(--slotted-control-icon-size-sm, 14px);
|
|
92
|
+
--_inner-size: var(--slotted-control-inner-size-sm, 22px);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
[data-ui='tabs'][data-size='lg'] {
|
|
96
|
+
--_height: var(--slotted-control-height-lg, 40px);
|
|
97
|
+
--_font-size: var(--slotted-control-font-size-lg, 1rem);
|
|
98
|
+
--_padding-inline: var(--slotted-control-padding-inline-lg, 1rem);
|
|
99
|
+
--_inner-gap: var(--slotted-control-gap-lg, 0.5rem);
|
|
100
|
+
--_icon-size: var(--slotted-control-icon-size-lg, 20px);
|
|
101
|
+
--_inner-size: var(--slotted-control-inner-size-lg, 28px);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A quarter turn, and one column instead of four: the list takes the first
|
|
106
|
+
* column, the panels the second, and the actions sit under the list. The
|
|
107
|
+
* paging controls are not drawn at all — a vertical list stacks and the page
|
|
108
|
+
* scrolls, which is the answer Carbon gives for the same case.
|
|
109
|
+
*
|
|
110
|
+
* Both columns are filled, so a column gap stands only between the list and
|
|
111
|
+
* the panel: the distance the row gap keeps in a horizontal family. Without
|
|
112
|
+
* it the panel's first letter touched the track on the list's end edge.
|
|
113
|
+
*/
|
|
114
|
+
[data-ui='tabs'][data-orientation='vertical'] {
|
|
115
|
+
column-gap: var(--slotted-tabs-gap, var(--slotted-space-6, 1rem));
|
|
116
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
117
|
+
grid-template-rows: auto auto;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ── The list ─────────────────────────────────────────────────────────── */
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The scroll container and the one tab stop. The hairline is its own border,
|
|
124
|
+
* which is on the border box and therefore does not scroll with the tabs —
|
|
125
|
+
* a track drawn on the content would slide away with the first tab.
|
|
126
|
+
*/
|
|
127
|
+
[data-ui='tabs'] > [data-part='list'] {
|
|
128
|
+
align-items: stretch;
|
|
129
|
+
display: flex;
|
|
130
|
+
gap: var(--slotted-tabs-list-gap, var(--_list-gap));
|
|
131
|
+
grid-column: 2;
|
|
132
|
+
grid-row: 1;
|
|
133
|
+
min-inline-size: 0;
|
|
134
|
+
/**
|
|
135
|
+
* A row of tabs that runs past its container is a row whose last tab
|
|
136
|
+
* nobody can reach, and Carbon states the rule for the whole field:
|
|
137
|
+
* horizontal tabs "should not wrap to multiple lines or stack on top of
|
|
138
|
+
* each other; instead, they should scroll horizontally".
|
|
139
|
+
*/
|
|
140
|
+
overflow-x: auto;
|
|
141
|
+
overflow-y: hidden;
|
|
142
|
+
overscroll-behavior-inline: contain;
|
|
143
|
+
/**
|
|
144
|
+
* The platform's scrollbar is the affordance until the family draws its
|
|
145
|
+
* own. It is hidden only once `data-overflowing` says the paging controls
|
|
146
|
+
* are there to replace it, which is also the only case where the family
|
|
147
|
+
* knows a script is running.
|
|
148
|
+
*/
|
|
149
|
+
scrollbar-width: thin;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
[data-ui='tabs'][data-overflowing] > [data-part='list'] {
|
|
153
|
+
scrollbar-width: none;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
[data-ui='tabs'][data-variant='line'][data-track='line'] > [data-part='list'] {
|
|
157
|
+
border-block-end: var(--_track-size) solid
|
|
158
|
+
var(--slotted-tabs-track-color, var(--slotted-border-subtle, ButtonBorder));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* ── A list that stands on somebody else's line ────────────────────────
|
|
163
|
+
*
|
|
164
|
+
* `track="none"` draws no line at all, for the case the family cannot see: a
|
|
165
|
+
* set of tabs sitting on the edge of a card header, a table's own rule or a
|
|
166
|
+
* panel that already draws its top border. Two hairlines a pixel apart read
|
|
167
|
+
* as one thick smudge rather than as two decisions, and no surveyed
|
|
168
|
+
* implementation offers a way out — Cloudscape ships a whole `container`
|
|
169
|
+
* variant for the same case, which is a second family's worth of paint to
|
|
170
|
+
* remove one line.
|
|
171
|
+
*
|
|
172
|
+
* The marker then sits on the item's own edge rather than a track's width
|
|
173
|
+
* below it, so it lands on the line the page already drew. Where that line is
|
|
174
|
+
* is the page's business: the family stops at its own box.
|
|
175
|
+
*/
|
|
176
|
+
[data-ui='tabs'][data-track='none'] > :where([data-part='list']) > [data-part='item']::before,
|
|
177
|
+
[data-ui='tabs'][data-track='none'] > :where([data-part='list']) > [data-part='item']::after {
|
|
178
|
+
inset-block-end: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
[data-ui='tabs'][data-track='none'][data-orientation='vertical']
|
|
182
|
+
> :where([data-part='list'])
|
|
183
|
+
> [data-part='item']::before,
|
|
184
|
+
[data-ui='tabs'][data-track='none'][data-orientation='vertical']
|
|
185
|
+
> :where([data-part='list'])
|
|
186
|
+
> [data-part='item']::after {
|
|
187
|
+
inset-block-end: auto;
|
|
188
|
+
inset-inline-end: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A column of labels reads down its own start edge. Centring them is what the
|
|
193
|
+
* horizontal rule wants — a row of equal shares has a middle — and in a
|
|
194
|
+
* column it produces a ragged left edge nobody can scan.
|
|
195
|
+
*/
|
|
196
|
+
[data-ui='tabs'][data-orientation='vertical']
|
|
197
|
+
> :where([data-part='list'])
|
|
198
|
+
> :where([data-part='item'])
|
|
199
|
+
> [data-part='tab'] {
|
|
200
|
+
justify-content: start;
|
|
201
|
+
text-align: start;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
[data-ui='tabs'][data-orientation='vertical'] > [data-part='list'] {
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
grid-column: 1;
|
|
207
|
+
grid-row: 1;
|
|
208
|
+
overflow-x: hidden;
|
|
209
|
+
overflow-y: auto;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
[data-ui='tabs'][data-orientation='vertical'][data-variant='line'][data-track='line']
|
|
213
|
+
> [data-part='list'] {
|
|
214
|
+
border-block-end: 0;
|
|
215
|
+
border-inline-end: var(--_track-size) solid
|
|
216
|
+
var(--slotted-tabs-track-color, var(--slotted-border-subtle, ButtonBorder));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Equal shares of the line, which is Angular Material's `stretchTabs` and
|
|
221
|
+
* Carbon's grid-aware alignment. A grid rather than `flex: 1`: equal columns
|
|
222
|
+
* are what stop a four-character tab and a twenty-character one from
|
|
223
|
+
* dividing the row by the length of a translated string. A row of equal
|
|
224
|
+
* shares cannot also scroll — there is nothing left to scroll to — so the
|
|
225
|
+
* label is what gives way, and it is the only place a horizontal label ever
|
|
226
|
+
* truncates.
|
|
227
|
+
*/
|
|
228
|
+
[data-ui='tabs'][data-fit='equal'] > [data-part='list'] {
|
|
229
|
+
display: grid;
|
|
230
|
+
grid-auto-columns: minmax(0, 1fr);
|
|
231
|
+
grid-auto-flow: column;
|
|
232
|
+
justify-self: stretch;
|
|
233
|
+
overflow: visible;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Equal shares are shares of the line, so each item takes the one it is given. */
|
|
237
|
+
[data-ui='tabs'][data-fit='equal'] > :where([data-part='list']) > [data-part='item'] {
|
|
238
|
+
min-inline-size: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
[data-ui='tabs'][data-fit='equal']
|
|
242
|
+
> :where([data-part='list'])
|
|
243
|
+
> :where([data-part='item'])
|
|
244
|
+
> :where([data-part='tab'])
|
|
245
|
+
> [data-part='label'] {
|
|
246
|
+
overflow: hidden;
|
|
247
|
+
text-overflow: ellipsis;
|
|
248
|
+
white-space: nowrap;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ── One tab, and the box that holds it ───────────────────────────────── */
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The item is presentational, and it exists for two reasons a `button`
|
|
255
|
+
* cannot answer: a `button` may not contain a `button`, so a dismissible tab
|
|
256
|
+
* needs a box for its second control, and the marker has to span the pair
|
|
257
|
+
* rather than only the label.
|
|
258
|
+
*/
|
|
259
|
+
[data-ui='tabs'] > :where([data-part='list']) > [data-part='item'] {
|
|
260
|
+
align-items: stretch;
|
|
261
|
+
display: grid;
|
|
262
|
+
grid-auto-flow: column;
|
|
263
|
+
/**
|
|
264
|
+
* A tab is as wide as its label. A flex item shrinks below its content
|
|
265
|
+
* unless it is told otherwise, and the first capture showed what that
|
|
266
|
+
* costs: three file names in a narrow list wrapped and printed over each
|
|
267
|
+
* other. The list scrolls instead, which is what `overflow-x` is there for.
|
|
268
|
+
*/
|
|
269
|
+
flex: none;
|
|
270
|
+
position: relative;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
[data-ui='tabs'] > :where([data-part='list']) > :where([data-part='item']) > [data-part='tab'] {
|
|
274
|
+
align-items: center;
|
|
275
|
+
appearance: none;
|
|
276
|
+
background-color: transparent;
|
|
277
|
+
border: 0;
|
|
278
|
+
border-radius: var(--slotted-control-radius, 4px);
|
|
279
|
+
color: var(--slotted-tabs-color, var(--slotted-text-muted, GrayText));
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
display: inline-flex;
|
|
282
|
+
font-family: var(--slotted-control-font-family, ui-sans-serif, system-ui, sans-serif);
|
|
283
|
+
font-size: var(--_font-size);
|
|
284
|
+
font-weight: var(--slotted-control-font-weight, 600);
|
|
285
|
+
gap: var(--_inner-gap);
|
|
286
|
+
justify-content: center;
|
|
287
|
+
letter-spacing: var(--slotted-control-letter-spacing, 0);
|
|
288
|
+
line-height: var(--slotted-control-line-height, 1);
|
|
289
|
+
min-block-size: var(--_height);
|
|
290
|
+
min-inline-size: 0;
|
|
291
|
+
padding-block: 0;
|
|
292
|
+
padding-inline: var(--_padding-inline);
|
|
293
|
+
text-decoration: none;
|
|
294
|
+
transition:
|
|
295
|
+
background-color var(--slotted-control-transition-duration)
|
|
296
|
+
var(--slotted-control-transition-easing),
|
|
297
|
+
color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* A tab that hands the closing control the end of its row gives up its own
|
|
302
|
+
* inset there: the control carries it instead, measured from the mark rather
|
|
303
|
+
* than from the box.
|
|
304
|
+
*/
|
|
305
|
+
[data-ui='tabs']
|
|
306
|
+
> :where([data-part='list'])
|
|
307
|
+
> [data-part='item']:has(> [data-part='dismiss'])
|
|
308
|
+
> [data-part='tab'] {
|
|
309
|
+
padding-inline-end: 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The words. A box of their own, as in `tag`, and for the same three
|
|
314
|
+
* reasons: `text-overflow` needs something to clip, an icon beside the label
|
|
315
|
+
* needs something to be aligned against, and a label in another script needs
|
|
316
|
+
* to read its own way inside a page that reads the other.
|
|
317
|
+
*/
|
|
318
|
+
[data-ui='tabs']
|
|
319
|
+
> :where([data-part='list'])
|
|
320
|
+
> :where([data-part='item'])
|
|
321
|
+
> :where([data-part='tab'])
|
|
322
|
+
> [data-part='label'] {
|
|
323
|
+
min-inline-size: 0;
|
|
324
|
+
unicode-bidi: plaintext;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* What stands beside the words: an icon before them, a count after them.
|
|
329
|
+
* Each is a flex item of the tab, so the tab's inner gap spaces it and the
|
|
330
|
+
* row centres it, and neither shrinks: under `fit="equal"` the label is what
|
|
331
|
+
* gives way. An icon box reads `--slotted-icon-size` from the slot, so it
|
|
332
|
+
* takes the tab's icon step.
|
|
333
|
+
*/
|
|
334
|
+
[data-ui='tabs']
|
|
335
|
+
> :where([data-part='list'])
|
|
336
|
+
> :where([data-part='item'])
|
|
337
|
+
> :where([data-part='tab'])
|
|
338
|
+
> :is([data-part='start'], [data-part='end']) {
|
|
339
|
+
--slotted-icon-size: var(--_icon-size);
|
|
340
|
+
|
|
341
|
+
align-items: center;
|
|
342
|
+
display: inline-flex;
|
|
343
|
+
flex: none;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* A tab that is not selected raises its ink on hover. A selected one does
|
|
348
|
+
* not: it is already at full strength, and a hover that changes nothing is
|
|
349
|
+
* what tells the reader they are on the tab they are looking at.
|
|
350
|
+
*
|
|
351
|
+
* The ink is the whole of the hover on the tab itself. The `line` variant
|
|
352
|
+
* paints no fill: a rounded box standing on the track was the first
|
|
353
|
+
* capture's defect — a button drawn over a row of labels, its corners
|
|
354
|
+
* meeting a line that runs under all of them. What that variant says on
|
|
355
|
+
* hover is where the mark would go, and it says it on the item, below.
|
|
356
|
+
*/
|
|
357
|
+
[data-ui='tabs']
|
|
358
|
+
> :where([data-part='list'])
|
|
359
|
+
> :where([data-part='item'])
|
|
360
|
+
> [data-part='tab']:not(:disabled):not([aria-selected='true']):hover {
|
|
361
|
+
color: var(--slotted-tabs-hover-color, var(--slotted-text, CanvasText));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The `contained` variant is the one that fills. Its tab is a cell in a
|
|
366
|
+
* sunken box rather than a label on a line, and a cell that tints on hover
|
|
367
|
+
* and goes one step further on press is what a segmented control does —
|
|
368
|
+
* the fill has a box to belong to and no line to meet.
|
|
369
|
+
*/
|
|
370
|
+
[data-ui='tabs'][data-variant='contained']
|
|
371
|
+
> :where([data-part='list'])
|
|
372
|
+
> :where([data-part='item'])
|
|
373
|
+
> [data-part='tab']:not(:disabled):not([aria-selected='true']):hover {
|
|
374
|
+
background-color: var(
|
|
375
|
+
--slotted-tabs-hover-background,
|
|
376
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
[data-ui='tabs'][data-variant='contained']
|
|
381
|
+
> :where([data-part='list'])
|
|
382
|
+
> :where([data-part='item'])
|
|
383
|
+
> [data-part='tab']:not(:disabled):active {
|
|
384
|
+
background-color: var(
|
|
385
|
+
--slotted-tabs-active-background,
|
|
386
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* The ring is drawn inside the control, and it is the family's one departure
|
|
392
|
+
* from the library's outward ring: the list is a scroll container, which
|
|
393
|
+
* clips to its padding box, so a ring outside the first or last tab is a ring
|
|
394
|
+
* with a side missing.
|
|
395
|
+
*
|
|
396
|
+
* It is inset by the marker's own width on top of the usual offset. At the
|
|
397
|
+
* usual offset the ring's outer edge met the marker, and a selected tab under
|
|
398
|
+
* focus became one blue shape that said neither of the two things — the
|
|
399
|
+
* capture is what showed it.
|
|
400
|
+
*/
|
|
401
|
+
[data-ui='tabs']
|
|
402
|
+
> :where([data-part='list'])
|
|
403
|
+
> :where([data-part='item'])
|
|
404
|
+
> [data-part='tab']:focus-visible {
|
|
405
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
406
|
+
outline-offset: calc(-1 * (var(--slotted-focus-ring-offset, 2px) + var(--_marker-size)));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* The closing control keeps the library's outward ring. It is shorter than
|
|
411
|
+
* the item and centred in it, so the ring has room inside the list's own box
|
|
412
|
+
* and nothing about it reaches the edge the scroller clips at — and an inset
|
|
413
|
+
* ring on a twenty-four pixel box would be drawn through the mark.
|
|
414
|
+
*/
|
|
415
|
+
[data-ui='tabs']
|
|
416
|
+
> :where([data-part='list'])
|
|
417
|
+
> :where([data-part='item'])
|
|
418
|
+
> [data-part='dismiss']:focus-visible {
|
|
419
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
420
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
[data-ui='tabs']
|
|
424
|
+
> :where([data-part='list'])
|
|
425
|
+
> [data-part='item'][data-selected]
|
|
426
|
+
> [data-part='tab'] {
|
|
427
|
+
color: var(--slotted-tabs-selected-color, var(--slotted-tone-accent-text));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
[data-ui='tabs']
|
|
431
|
+
> :where([data-part='list'])
|
|
432
|
+
> :where([data-part='item'])
|
|
433
|
+
> [data-part='tab']:disabled {
|
|
434
|
+
color: var(--slotted-disabled-foreground, GrayText);
|
|
435
|
+
cursor: not-allowed;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* ── The marker ───────────────────────────────────────────────────────── */
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Drawn on the item, on the edge the list runs along, and present on every
|
|
442
|
+
* item rather than only on the selected one: a mark that appears has nothing
|
|
443
|
+
* to transition from. It is scaled to nothing across the axis it marks, and
|
|
444
|
+
* the transform origin is what the direction of travel decides.
|
|
445
|
+
*
|
|
446
|
+
* It is pulled over the track by exactly the track's own width, so the two
|
|
447
|
+
* lines are one line rather than a hairline with a thicker line beside it.
|
|
448
|
+
*
|
|
449
|
+
* There are two of them in the same place. `::after` is the mark, in the
|
|
450
|
+
* accent, and the one that travels. `::before` is the hover, in a neutral
|
|
451
|
+
* a step darker than the track: the same bar, under the tab the pointer is
|
|
452
|
+
* on, saying where the mark would go. It fades rather than grows — a bar
|
|
453
|
+
* that slid in from one side on every pass of the pointer would be the
|
|
454
|
+
* travelling animation with nothing travelling — and it is never on the
|
|
455
|
+
* selected item, where the mark already is.
|
|
456
|
+
*/
|
|
457
|
+
[data-ui='tabs'] > :where([data-part='list']) > [data-part='item']::before,
|
|
458
|
+
[data-ui='tabs'] > :where([data-part='list']) > [data-part='item']::after {
|
|
459
|
+
border-start-end-radius: var(--slotted-radius-xs, 2px);
|
|
460
|
+
border-start-start-radius: var(--slotted-radius-xs, 2px);
|
|
461
|
+
block-size: var(--_marker-size);
|
|
462
|
+
content: '';
|
|
463
|
+
inset-block-end: calc(-1 * var(--_track-size));
|
|
464
|
+
inset-inline: 0;
|
|
465
|
+
opacity: 0;
|
|
466
|
+
pointer-events: none;
|
|
467
|
+
position: absolute;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
[data-ui='tabs'] > :where([data-part='list']) > [data-part='item']::before {
|
|
471
|
+
/* No system colour as a last resort: unthemed, the bar disappears rather
|
|
472
|
+
than shouts, as the mark beside it already does. */
|
|
473
|
+
background-color: var(--slotted-tabs-hover-marker-color, var(--slotted-border-strong));
|
|
474
|
+
transition:
|
|
475
|
+
background-color var(--slotted-control-transition-duration)
|
|
476
|
+
var(--slotted-control-transition-easing),
|
|
477
|
+
opacity var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
[data-ui='tabs'] > :where([data-part='list']) > [data-part='item']::after {
|
|
481
|
+
background-color: var(--slotted-tabs-marker-color, var(--slotted-tone-accent-solid));
|
|
482
|
+
transform: scale(0, 1);
|
|
483
|
+
transform-origin: var(--_marker-origin) 50%;
|
|
484
|
+
transition:
|
|
485
|
+
opacity var(--slotted-control-transition-duration) var(--slotted-control-transition-easing),
|
|
486
|
+
transform var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
[data-ui='tabs'][data-variant='line']
|
|
490
|
+
> :where([data-part='list'])
|
|
491
|
+
> [data-part='item']:not([data-selected]):has(
|
|
492
|
+
> [data-part='tab']:not(:disabled):hover
|
|
493
|
+
)::before {
|
|
494
|
+
opacity: 1;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Press takes the bar to the label's own ink: the two say one thing. */
|
|
498
|
+
[data-ui='tabs'][data-variant='line']
|
|
499
|
+
> :where([data-part='list'])
|
|
500
|
+
> [data-part='item']:not([data-selected]):has(
|
|
501
|
+
> [data-part='tab']:not(:disabled):active
|
|
502
|
+
)::before {
|
|
503
|
+
background-color: var(--slotted-tabs-active-marker-color, var(--slotted-text));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
[data-ui='tabs'] > :where([data-part='list']) > [data-part='item'][data-selected]::after {
|
|
507
|
+
opacity: 1;
|
|
508
|
+
transform: scale(1, 1);
|
|
509
|
+
transform-origin: var(--_marker-origin-selected) 50%;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Forward is toward the end of the list. The tab being left collapses toward
|
|
514
|
+
* the end and the tab being entered grows from the start, which is one bar
|
|
515
|
+
* travelling. `transform-origin` takes no logical keyword, so the two
|
|
516
|
+
* percentages swap under `:dir(rtl)` — the one place in this stylesheet
|
|
517
|
+
* where a rule is mirrored by hand.
|
|
518
|
+
*/
|
|
519
|
+
[data-ui='tabs'][data-activation-direction='forward'] {
|
|
520
|
+
--_marker-origin: 100%;
|
|
521
|
+
--_marker-origin-selected: 0%;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
[data-ui='tabs'][data-activation-direction='backward'] {
|
|
525
|
+
--_marker-origin: 0%;
|
|
526
|
+
--_marker-origin-selected: 100%;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
[data-ui='tabs'][data-activation-direction='forward']:dir(rtl) {
|
|
530
|
+
--_marker-origin: 0%;
|
|
531
|
+
--_marker-origin-selected: 100%;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
[data-ui='tabs'][data-activation-direction='backward']:dir(rtl) {
|
|
535
|
+
--_marker-origin: 100%;
|
|
536
|
+
--_marker-origin-selected: 0%;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** A quarter turn: the bar stands on the list's own edge and grows in block. */
|
|
540
|
+
[data-ui='tabs'][data-orientation='vertical']
|
|
541
|
+
> :where([data-part='list'])
|
|
542
|
+
> [data-part='item']::before,
|
|
543
|
+
[data-ui='tabs'][data-orientation='vertical']
|
|
544
|
+
> :where([data-part='list'])
|
|
545
|
+
> [data-part='item']::after {
|
|
546
|
+
block-size: auto;
|
|
547
|
+
border-start-end-radius: var(--slotted-radius-xs, 2px);
|
|
548
|
+
border-start-start-radius: 0;
|
|
549
|
+
border-end-end-radius: var(--slotted-radius-xs, 2px);
|
|
550
|
+
inline-size: var(--_marker-size);
|
|
551
|
+
inset-block: 0;
|
|
552
|
+
inset-inline: auto calc(-1 * var(--_track-size));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
[data-ui='tabs'][data-orientation='vertical']
|
|
556
|
+
> :where([data-part='list'])
|
|
557
|
+
> [data-part='item']::after {
|
|
558
|
+
transform: scale(1, 0);
|
|
559
|
+
transform-origin: 50% var(--_marker-origin);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
[data-ui='tabs'][data-orientation='vertical']
|
|
563
|
+
> :where([data-part='list'])
|
|
564
|
+
> [data-part='item'][data-selected]::after {
|
|
565
|
+
transform: scale(1, 1);
|
|
566
|
+
transform-origin: 50% var(--_marker-origin-selected);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* ── Contained ────────────────────────────────────────────────────────── */
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Carbon's contained tabs and Fluent's filled appearance: the list is a
|
|
573
|
+
* sunken track and each tab a surface inside it. The marker stays, and it is
|
|
574
|
+
* the reason this variant survives the `high-contrast` scheme — the theme
|
|
575
|
+
* paints every surface `Canvas` there, so a selection said with a fill alone
|
|
576
|
+
* would be a selection nobody in that scheme can see.
|
|
577
|
+
*/
|
|
578
|
+
/**
|
|
579
|
+
* The track hugs its tabs rather than the line. A sunken bar running the full
|
|
580
|
+
* width with three pills at one end is a box with a hole in it, which is what
|
|
581
|
+
* the first capture showed; the `line` variant keeps the full width, because
|
|
582
|
+
* there the track is the edge of the panel below it.
|
|
583
|
+
*/
|
|
584
|
+
[data-ui='tabs'][data-variant='contained'] > [data-part='list'] {
|
|
585
|
+
background-color: var(--slotted-tabs-contained-background, var(--slotted-surface-sunken));
|
|
586
|
+
border-radius: var(--slotted-radius-md, 6px);
|
|
587
|
+
justify-self: start;
|
|
588
|
+
max-inline-size: 100%;
|
|
589
|
+
padding: var(--slotted-space-1, 0.125rem);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
[data-ui='tabs'][data-variant='contained'] > :where([data-part='list']) > [data-part='item'] {
|
|
593
|
+
border-radius: var(--slotted-radius-sm, 4px);
|
|
594
|
+
transition: background-color var(--slotted-control-transition-duration)
|
|
595
|
+
var(--slotted-control-transition-easing);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
[data-ui='tabs'][data-variant='contained']
|
|
599
|
+
> :where([data-part='list'])
|
|
600
|
+
> [data-part='item'][data-selected] {
|
|
601
|
+
background-color: var(--slotted-tabs-contained-selected-background, var(--slotted-surface));
|
|
602
|
+
box-shadow: var(--slotted-shadow-xs);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Inset from the corners by the radius, so the bar underlines the label
|
|
607
|
+
* instead of tracing the edge of the surface it sits on. Sitting on the
|
|
608
|
+
* edge, it read as a second border of a different colour, and the capture
|
|
609
|
+
* showed a pill with one blue side.
|
|
610
|
+
*/
|
|
611
|
+
[data-ui='tabs'][data-variant='contained']
|
|
612
|
+
> :where([data-part='list'])
|
|
613
|
+
> [data-part='item']::after {
|
|
614
|
+
inset-block-end: var(--slotted-space-1, 0.125rem);
|
|
615
|
+
inset-inline: var(--slotted-radius-sm, 4px);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
[data-ui='tabs'][data-variant='contained'][data-orientation='vertical']
|
|
619
|
+
> :where([data-part='list'])
|
|
620
|
+
> [data-part='item']::after {
|
|
621
|
+
inset-block: var(--slotted-radius-sm, 4px);
|
|
622
|
+
inset-inline: auto var(--slotted-space-1, 0.125rem);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/* ── The dismiss control ──────────────────────────────────────────────── */
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Out of the arrow ring, so the pattern's own model is untouched, and inside
|
|
629
|
+
* the accessibility tree, so it is not a control only a mouse can reach. The
|
|
630
|
+
* keyboard path is `Delete`, which the APG names and which the tab announces
|
|
631
|
+
* through `aria-keyshortcuts`.
|
|
632
|
+
*
|
|
633
|
+
* It is aligned by its mark rather than by its box. The box overhangs the X
|
|
634
|
+
* on every side, so a box set flush against the label leaves a hole where the
|
|
635
|
+
* overhang is; both margins take the overhang back, which puts the mark one
|
|
636
|
+
* inner gap from the last letter and the tab's own inset from the end of the
|
|
637
|
+
* item. The first capture showed the other arrangement: a square the height of
|
|
638
|
+
* the control, hard against the words, with the whole tab's padding on the
|
|
639
|
+
* far side of it.
|
|
640
|
+
*/
|
|
641
|
+
[data-ui='tabs']
|
|
642
|
+
> :where([data-part='list'])
|
|
643
|
+
> :where([data-part='item'])
|
|
644
|
+
> [data-part='dismiss'] {
|
|
645
|
+
--slotted-icon-size: var(--_icon-size);
|
|
646
|
+
|
|
647
|
+
align-items: center;
|
|
648
|
+
align-self: center;
|
|
649
|
+
appearance: none;
|
|
650
|
+
background-color: transparent;
|
|
651
|
+
block-size: var(--_dismiss-size);
|
|
652
|
+
border: 0;
|
|
653
|
+
border-radius: var(--slotted-control-radius, 4px);
|
|
654
|
+
color: var(--slotted-tabs-dismiss-color, var(--slotted-text-muted, GrayText));
|
|
655
|
+
cursor: pointer;
|
|
656
|
+
display: inline-flex;
|
|
657
|
+
flex: none;
|
|
658
|
+
inline-size: var(--_dismiss-size);
|
|
659
|
+
justify-content: center;
|
|
660
|
+
margin-inline-end: calc(var(--_padding-inline) - var(--_dismiss-overhang));
|
|
661
|
+
margin-inline-start: max(0px, calc(var(--_inner-gap) - var(--_dismiss-overhang)));
|
|
662
|
+
padding: 0;
|
|
663
|
+
transition:
|
|
664
|
+
background-color var(--slotted-control-transition-duration)
|
|
665
|
+
var(--slotted-control-transition-easing),
|
|
666
|
+
color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* On a finger the target is the comfortable size in both axes and the box
|
|
670
|
+
is the one a mouse sees: a transparent box centred on the control reaches
|
|
671
|
+
44 pixels, and the ground and the ring stay square. */
|
|
672
|
+
@media (pointer: coarse) {
|
|
673
|
+
[data-ui='tabs']
|
|
674
|
+
> :where([data-part='list'])
|
|
675
|
+
> :where([data-part='item'])
|
|
676
|
+
> [data-part='dismiss'] {
|
|
677
|
+
position: relative;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
[data-ui='tabs']
|
|
681
|
+
> :where([data-part='list'])
|
|
682
|
+
> :where([data-part='item'])
|
|
683
|
+
> [data-part='dismiss']::after {
|
|
684
|
+
content: '';
|
|
685
|
+
inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
|
|
686
|
+
position: absolute;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
[data-ui='tabs']
|
|
691
|
+
> :where([data-part='list'])
|
|
692
|
+
> :where([data-part='item'])
|
|
693
|
+
> [data-part='dismiss']:not(:disabled):hover {
|
|
694
|
+
background-color: var(
|
|
695
|
+
--slotted-tabs-dismiss-hover-background,
|
|
696
|
+
var(--slotted-tone-danger-surface)
|
|
697
|
+
);
|
|
698
|
+
color: var(--slotted-tabs-dismiss-hover-color, var(--slotted-tone-danger-text));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
[data-ui='tabs']
|
|
702
|
+
> :where([data-part='list'])
|
|
703
|
+
> :where([data-part='item'])
|
|
704
|
+
> [data-part='dismiss']:not(:disabled):active {
|
|
705
|
+
background-color: var(
|
|
706
|
+
--slotted-tabs-dismiss-active-background,
|
|
707
|
+
var(--slotted-tone-danger-border)
|
|
708
|
+
);
|
|
709
|
+
color: var(--slotted-tabs-dismiss-active-color, var(--slotted-text-inverse));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
[data-ui='tabs']
|
|
713
|
+
> :where([data-part='list'])
|
|
714
|
+
> :where([data-part='item'])
|
|
715
|
+
> [data-part='dismiss']:disabled {
|
|
716
|
+
color: var(--slotted-disabled-foreground, GrayText);
|
|
717
|
+
cursor: not-allowed;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/* ── The paging controls ──────────────────────────────────────────────── */
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* One per end, in a track of its own. Standing beside the list rather than
|
|
724
|
+
* over it costs two `auto` columns that collapse while the controls are
|
|
725
|
+
* absent, and it keeps the list's own hairline whole: what fades is the
|
|
726
|
+
* list's edge, below, not a gradient painted over it.
|
|
727
|
+
*
|
|
728
|
+
* Each is an inner control, not a button (R2): as tall as a tab, as wide
|
|
729
|
+
* as the glyph plus a step of air on each side, floored at the WCAG 2.2
|
|
730
|
+
* minimum. It was a square the height of the bar before, which put a
|
|
731
|
+
* thirty-four pixel box beside a sixteen pixel chevron.
|
|
732
|
+
*
|
|
733
|
+
* They are out of the tab sequence and out of the accessibility tree: every
|
|
734
|
+
* tab is already reachable through the roving stop, the arrows scroll the
|
|
735
|
+
* list as they move, and Cloudscape's own guidance warns against extra
|
|
736
|
+
* stops before a tab list.
|
|
737
|
+
*/
|
|
738
|
+
[data-ui='tabs'] > [data-part='scroll'] {
|
|
739
|
+
--slotted-icon-size: var(--_icon-size);
|
|
740
|
+
|
|
741
|
+
align-items: center;
|
|
742
|
+
align-self: center;
|
|
743
|
+
appearance: none;
|
|
744
|
+
background-color: transparent;
|
|
745
|
+
border: 0;
|
|
746
|
+
border-radius: var(--slotted-control-radius, 4px);
|
|
747
|
+
color: var(--slotted-tabs-scroll-color, var(--slotted-text-muted, GrayText));
|
|
748
|
+
cursor: pointer;
|
|
749
|
+
display: inline-flex;
|
|
750
|
+
inline-size: max(
|
|
751
|
+
var(--slotted-target-min, 24px),
|
|
752
|
+
min(calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
|
|
753
|
+
);
|
|
754
|
+
justify-content: center;
|
|
755
|
+
min-block-size: var(--_height);
|
|
756
|
+
padding: 0;
|
|
757
|
+
transition:
|
|
758
|
+
background-color var(--slotted-control-transition-duration)
|
|
759
|
+
var(--slotted-control-transition-easing),
|
|
760
|
+
color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* The list fades into the control on the side that has more to show: a
|
|
765
|
+
* sixteen-pixel mask on that edge, so a tab cut by the scroller's edge
|
|
766
|
+
* dissolves toward the chevron instead of ending on a hard line. The side
|
|
767
|
+
* is read from the control itself — a paging control at its end is
|
|
768
|
+
* disabled — so the family needs no second signal for it.
|
|
769
|
+
*/
|
|
770
|
+
[data-ui='tabs']:has(> [data-part='scroll'][data-direction='start']:not(:disabled))
|
|
771
|
+
> [data-part='list'] {
|
|
772
|
+
mask-image: linear-gradient(to right, transparent, black var(--_fade));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
[data-ui='tabs']:has(> [data-part='scroll'][data-direction='end']:not(:disabled))
|
|
776
|
+
> [data-part='list'] {
|
|
777
|
+
mask-image: linear-gradient(to left, transparent, black var(--_fade));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/* The start edge is the right one in a right-to-left document. */
|
|
781
|
+
[data-ui='tabs']:dir(rtl):has(> [data-part='scroll'][data-direction='start']:not(:disabled))
|
|
782
|
+
> [data-part='list'] {
|
|
783
|
+
mask-image: linear-gradient(to left, transparent, black var(--_fade));
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
[data-ui='tabs']:dir(rtl):has(> [data-part='scroll'][data-direction='end']:not(:disabled))
|
|
787
|
+
> [data-part='list'] {
|
|
788
|
+
mask-image: linear-gradient(to right, transparent, black var(--_fade));
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
[data-ui='tabs']:has(> [data-part='scroll'][data-direction='start']:not(:disabled)):has(
|
|
792
|
+
> [data-part='scroll'][data-direction='end']:not(:disabled)
|
|
793
|
+
)
|
|
794
|
+
> [data-part='list'] {
|
|
795
|
+
mask-image: linear-gradient(
|
|
796
|
+
to right,
|
|
797
|
+
transparent,
|
|
798
|
+
black var(--_fade),
|
|
799
|
+
black calc(100% - var(--_fade)),
|
|
800
|
+
transparent
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/* On a finger the control is the comfortable target and square: one value
|
|
805
|
+
for both axes. As wide as its glyph and as tall as the row, it stood 24
|
|
806
|
+
wide and 44 tall beside tabs grown to the comfortable height. */
|
|
807
|
+
@media (pointer: coarse) {
|
|
808
|
+
[data-ui='tabs'] > [data-part='scroll'] {
|
|
809
|
+
min-block-size: max(var(--slotted-target-comfortable, 44px), var(--_height));
|
|
810
|
+
min-inline-size: max(var(--slotted-target-comfortable, 44px), var(--_height));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
[data-ui='tabs'] > [data-part='scroll'][data-direction='start'] {
|
|
815
|
+
grid-column: 1;
|
|
816
|
+
grid-row: 1;
|
|
817
|
+
margin-inline-end: var(--slotted-tabs-actions-gap, var(--slotted-space-4, 0.5rem));
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
[data-ui='tabs'] > [data-part='scroll'][data-direction='end'] {
|
|
821
|
+
grid-column: 3;
|
|
822
|
+
margin-inline-start: var(--slotted-tabs-actions-gap, var(--slotted-space-4, 0.5rem));
|
|
823
|
+
grid-row: 1;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* The glyph is `chevron-right` in a `directional` icon box, so it points
|
|
828
|
+
* along the reading direction and the box mirrors itself in a right-to-left
|
|
829
|
+
* document. The control toward the start turns it half a turn — a turn
|
|
830
|
+
* rather than a second glyph, because a chevron is symmetrical about the
|
|
831
|
+
* axis it is turned across. `rotate` rather than `scale`, which the box's
|
|
832
|
+
* own mirror holds: the two compose, so the start control points back in
|
|
833
|
+
* both directions.
|
|
834
|
+
*/
|
|
835
|
+
[data-ui='tabs'] > [data-part='scroll'][data-direction='start'] > [data-ui='icon'] {
|
|
836
|
+
rotate: 0.5turn;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/* The ground moves under the pointer and nothing else does (R2). */
|
|
840
|
+
[data-ui='tabs'] > [data-part='scroll']:not(:disabled):hover {
|
|
841
|
+
background-color: var(
|
|
842
|
+
--slotted-tabs-hover-background,
|
|
843
|
+
var(--slotted-tone-secondary-subtle-hover)
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
[data-ui='tabs'] > [data-part='scroll']:not(:disabled):active {
|
|
848
|
+
background-color: var(
|
|
849
|
+
--slotted-tabs-active-background,
|
|
850
|
+
var(--slotted-tone-secondary-subtle-active)
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
[data-ui='tabs'] > [data-part='scroll']:focus-visible {
|
|
855
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
856
|
+
outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* At an end there is nowhere to go, and the control is not shown: only the
|
|
861
|
+
* side with more to reveal carries a chevron. It keeps its track — hidden,
|
|
862
|
+
* not removed — so the list never moves under the pointer that is using
|
|
863
|
+
* the other one.
|
|
864
|
+
*/
|
|
865
|
+
[data-ui='tabs'] > [data-part='scroll']:disabled {
|
|
866
|
+
cursor: default;
|
|
867
|
+
visibility: hidden;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
[data-ui='tabs'][data-orientation='vertical'] > [data-part='scroll'] {
|
|
871
|
+
display: none;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/* ── Actions, and the panels ──────────────────────────────────────────── */
|
|
875
|
+
|
|
876
|
+
[data-ui='tabs'] > [data-part='actions'] {
|
|
877
|
+
align-items: center;
|
|
878
|
+
align-self: center;
|
|
879
|
+
display: flex;
|
|
880
|
+
grid-column: 4;
|
|
881
|
+
margin-inline-start: var(--slotted-tabs-actions-gap, var(--slotted-space-4, 0.5rem));
|
|
882
|
+
grid-row: 1;
|
|
883
|
+
min-inline-size: 0;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
[data-ui='tabs'][data-orientation='vertical'] > [data-part='actions'] {
|
|
887
|
+
grid-column: 1;
|
|
888
|
+
grid-row: 2;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* `overflow-wrap: anywhere` and a zero minimum are the pair that answer a
|
|
893
|
+
* hostile string, and the panel needs both. The zero minimum was already
|
|
894
|
+
* here and it sizes the box; it does nothing about what stands in the box,
|
|
895
|
+
* so a seventy-character digest measured 599px inside a 222px panel and
|
|
896
|
+
* carried the whole family out of its stage with it — the panel, the root
|
|
897
|
+
* and the frame around them, in every scheme.
|
|
898
|
+
*
|
|
899
|
+
* It is written here and not on a part the family owns, because a panel
|
|
900
|
+
* holds what the consumer put in it and there is no inner part to reach. A
|
|
901
|
+
* consumer who wants a token kept whole writes `overflow-wrap: normal` on
|
|
902
|
+
* their own element, which is one declaration against a layout that
|
|
903
|
+
* otherwise breaks for every reader on a narrow screen.
|
|
904
|
+
*/
|
|
905
|
+
[data-ui='tabs'] > [data-part='panel'] {
|
|
906
|
+
grid-column: 1 / -1;
|
|
907
|
+
grid-row: 2;
|
|
908
|
+
min-inline-size: 0;
|
|
909
|
+
overflow-wrap: anywhere;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
[data-ui='tabs'][data-orientation='vertical'] > [data-part='panel'] {
|
|
913
|
+
grid-column: 2;
|
|
914
|
+
grid-row: 1 / -1;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* `[hidden]` carries `display: none` from the user-agent sheet at a
|
|
919
|
+
* specificity any rule here would beat, so the attribute is restated rather
|
|
920
|
+
* than relied on. It is the attribute and not a class because a screen
|
|
921
|
+
* reader, find-in-page and `:target` all already understand it.
|
|
922
|
+
*/
|
|
923
|
+
[data-ui='tabs'] > [data-part='panel'][hidden] {
|
|
924
|
+
display: none;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* The panel is in the page's tab sequence, which is where `Tab` from the list
|
|
929
|
+
* goes. Its ring is the library's outward one: the panel holds the consumer's
|
|
930
|
+
* own content and has no padding of its own, so an inset ring is drawn
|
|
931
|
+
* through the first line of it.
|
|
932
|
+
*/
|
|
933
|
+
[data-ui='tabs'] > [data-part='panel']:focus-visible {
|
|
934
|
+
outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
|
|
935
|
+
outline-offset: var(--slotted-focus-ring-offset, 2px);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* A panel arriving fades in. It is the one transition the family draws on the
|
|
940
|
+
* content rather than on a control, and it exists because the panel is the
|
|
941
|
+
* thing that changed: without it, a tab press repaints half the page with no
|
|
942
|
+
* indication that the two states are related. Angular Material slides the
|
|
943
|
+
* panel instead, which needs a clipping viewport that traps horizontal
|
|
944
|
+
* scrolling and fights the list's own scroll.
|
|
945
|
+
*/
|
|
946
|
+
[data-ui='tabs'] > [data-part='panel']:not([hidden]) {
|
|
947
|
+
animation: slotted-tabs-panel-in var(--slotted-duration-short) var(--slotted-easing-enter);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
@keyframes slotted-tabs-panel-in {
|
|
951
|
+
from {
|
|
952
|
+
opacity: 0;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/* ── The family out of action ─────────────────────────────────────────── */
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Every tab carries the native attribute, so the paint follows the element's
|
|
960
|
+
* own `:disabled` and the browser skips it with the arrows. What the root
|
|
961
|
+
* adds is the track: a disabled family should not read as a live one with
|
|
962
|
+
* grey labels.
|
|
963
|
+
*/
|
|
964
|
+
[data-ui='tabs'][data-disabled][data-variant='line'][data-track='line'] > [data-part='list'] {
|
|
965
|
+
border-color: var(--slotted-disabled-border, GrayText);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
[data-ui='tabs'][data-disabled][data-variant='contained'] > [data-part='list'] {
|
|
969
|
+
background-color: var(--slotted-disabled-background, Canvas);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* No last resort behind the token, unlike every rule that paints an element:
|
|
974
|
+
* decoration in an unthemed page should disappear rather than shout, and a
|
|
975
|
+
* marker painted `GrayText` with no theme would be the loudest thing on it.
|
|
976
|
+
*/
|
|
977
|
+
[data-ui='tabs'][data-disabled]
|
|
978
|
+
> :where([data-part='list'])
|
|
979
|
+
> [data-part='item'][data-selected]::after {
|
|
980
|
+
background-color: var(--slotted-disabled-border);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/* ── Forced colours ──────────────────────────────────────────────────── */
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* The theme maps every scheme token to a system colour, so the only rule
|
|
987
|
+
* here is about paint the theme cannot reach: a selected tab in the
|
|
988
|
+
* contained variant has a fill that flattens to `Canvas` along with the
|
|
989
|
+
* track it stands in.
|
|
990
|
+
*/
|
|
991
|
+
@media (forced-colors: active) {
|
|
992
|
+
[data-ui='tabs'][data-variant='contained']
|
|
993
|
+
> :where([data-part='list'])
|
|
994
|
+
> [data-part='item'][data-selected] {
|
|
995
|
+
border: var(--slotted-border-width-thin, 1px) solid Highlight;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|