actual-css 0.1.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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/actual.css +4929 -0
- package/dist/actual.js +3 -0
- package/dist/actual.js.map +31 -0
- package/dist/actual.min.css +1 -0
- package/docs/README.md +26 -0
- package/docs/components.md +860 -0
- package/docs/design-notes/accessibility-decisions.md +32 -0
- package/docs/design-notes/browser-support.md +93 -0
- package/docs/design-notes/cascade-layer.md +52 -0
- package/docs/design-notes/css-anchor.md +33 -0
- package/docs/design-notes/naming.md +37 -0
- package/docs/design-notes/previous-work.md +5 -0
- package/docs/design-notes/references.md +324 -0
- package/docs/forms.md +1265 -0
- package/docs/javascript.md +426 -0
- package/docs/layout.md +480 -0
- package/docs/mental-model.md +73 -0
- package/docs/patterns.md +415 -0
- package/docs/reserved.md +7 -0
- package/docs/tokens.md +455 -0
- package/docs/typography.md +159 -0
- package/docs/ui.md +937 -0
- package/docs/utilities.md +384 -0
- package/llms.txt +17 -0
- package/package.json +101 -0
- package/src/css/actual.css +17 -0
- package/src/css/actual.full.css +2 -0
- package/src/css/actual.layer.css +3 -0
- package/src/css/base.css +70 -0
- package/src/css/components/accordion.css +97 -0
- package/src/css/components/actions.css +21 -0
- package/src/css/components/alert.css +137 -0
- package/src/css/components/avatar.css +199 -0
- package/src/css/components/badge.css +143 -0
- package/src/css/components/breadcrumb.css +56 -0
- package/src/css/components/busy.css +44 -0
- package/src/css/components/button.css +216 -0
- package/src/css/components/card.css +140 -0
- package/src/css/components/drawer.css +122 -0
- package/src/css/components/flyout.css +243 -0
- package/src/css/components/index.css +41 -0
- package/src/css/components/join.css +106 -0
- package/src/css/components/key.css +25 -0
- package/src/css/components/meter.css +81 -0
- package/src/css/components/modal.css +162 -0
- package/src/css/components/navbar.css +98 -0
- package/src/css/components/overline.css +49 -0
- package/src/css/components/pagination.css +30 -0
- package/src/css/components/progress.css +126 -0
- package/src/css/components/scrollspy.css +27 -0
- package/src/css/components/skeleton.css +67 -0
- package/src/css/components/spinner.css +60 -0
- package/src/css/components/status-bar.css +82 -0
- package/src/css/components/surface.css +48 -0
- package/src/css/components/tab.css +128 -0
- package/src/css/components/table.css +64 -0
- package/src/css/components/tooltip.css +107 -0
- package/src/css/dialog-fallback.css +56 -0
- package/src/css/focus.css +13 -0
- package/src/css/forms/choice-card.css +158 -0
- package/src/css/forms/choice.css +196 -0
- package/src/css/forms/control.css +106 -0
- package/src/css/forms/custom-select.css +93 -0
- package/src/css/forms/form-actions.css +43 -0
- package/src/css/forms/form.css +57 -0
- package/src/css/forms/index.css +15 -0
- package/src/css/forms/input-icon.css +36 -0
- package/src/css/forms/native.css +68 -0
- package/src/css/forms/select.css +36 -0
- package/src/css/forms/switch.css +127 -0
- package/src/css/forms/validation.css +63 -0
- package/src/css/grid.css +95 -0
- package/src/css/intents.css +33 -0
- package/src/css/layout.css +80 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +55 -0
- package/src/css/optional/scroll-snap.css +51 -0
- package/src/css/optional/scroller.css +51 -0
- package/src/css/optional/typography-fluid.css +48 -0
- package/src/css/optional/utilities-extra.css +289 -0
- package/src/css/print.css +47 -0
- package/src/css/prose.css +187 -0
- package/src/css/reset.css +56 -0
- package/src/css/theme.css +186 -0
- package/src/css/themes/corporate.css +60 -0
- package/src/css/themes/cyberpunk.css +60 -0
- package/src/css/themes/dark.css +32 -0
- package/src/css/themes/dim.css +33 -0
- package/src/css/themes/edge.css +143 -0
- package/src/css/themes/forest.css +65 -0
- package/src/css/themes/gradient.css +97 -0
- package/src/css/themes/index.css +43 -0
- package/src/css/themes/lavender.css +66 -0
- package/src/css/themes/mono.css +60 -0
- package/src/css/themes/neon.css +66 -0
- package/src/css/themes/ocean.css +60 -0
- package/src/css/themes/petrol.css +64 -0
- package/src/css/themes/spruce.css +64 -0
- package/src/css/themes/square.css +68 -0
- package/src/css/themes/sunset.css +66 -0
- package/src/css/tokens.css +122 -0
- package/src/css/utilities.css +162 -0
- package/src/css/variants.css +118 -0
- package/src/js/command.js +165 -0
- package/src/js/context-menu.js +291 -0
- package/src/js/dialog-fallback.js +163 -0
- package/src/js/dialog.js +503 -0
- package/src/js/enhance.js +208 -0
- package/src/js/events.js +22 -0
- package/src/js/filter.js +122 -0
- package/src/js/floating.js +462 -0
- package/src/js/flyout.js +187 -0
- package/src/js/focus.js +35 -0
- package/src/js/index.js +26 -0
- package/src/js/input.js +47 -0
- package/src/js/keys.js +30 -0
- package/src/js/mask.js +184 -0
- package/src/js/menu.js +88 -0
- package/src/js/password.js +98 -0
- package/src/js/scrollspy.js +121 -0
- package/src/js/selectors.js +59 -0
- package/src/js/status.js +158 -0
- package/src/js/surface.js +358 -0
- package/src/js/tab.js +173 -0
- package/src/js/tooltip.js +374 -0
- package/src/js/validation.js +419 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Buttons for actions and button-like links.
|
|
3
|
+
*
|
|
4
|
+
* .btn is primarily for actionable elements: <button>, <a href>,
|
|
5
|
+
* input[type="button"|"submit"|"reset"].
|
|
6
|
+
*
|
|
7
|
+
* Keeps shared intent, variant, and size behavior with a solid default.
|
|
8
|
+
*
|
|
9
|
+
* Disabled state uses the native disabled attribute for buttons and inputs.
|
|
10
|
+
* Anchors have no native disabled state, so a.btn uses aria-disabled="true"
|
|
11
|
+
* for disabled styling and activation guards.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/* Intent boundary — inherited intents do not tint this component.
|
|
15
|
+
Local intent classes still win because :where() has zero specificity. */
|
|
16
|
+
:where(.btn) {
|
|
17
|
+
/* @sync intent-boundary */
|
|
18
|
+
--intent: initial;
|
|
19
|
+
--intent-fg: initial;
|
|
20
|
+
--ui-bg: initial;
|
|
21
|
+
--ui-fg: initial;
|
|
22
|
+
--ui-border: initial;
|
|
23
|
+
--ui-hover-bg: initial;
|
|
24
|
+
/* @sync intent-boundary */
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.btn {
|
|
28
|
+
--btn-font-size: var(--control-font-size);
|
|
29
|
+
--btn-min-size: var(--control-size);
|
|
30
|
+
--btn-pad-x: var(--control-pad-x, 1em);
|
|
31
|
+
--btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
|
|
32
|
+
--btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
|
|
33
|
+
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
34
|
+
--btn-radius: var(--radius);
|
|
35
|
+
--btn-focus-color: var(--intent, var(--focus));
|
|
36
|
+
--btn-focus-outline-width: calc(var(--border-width) * 2);
|
|
37
|
+
--btn-hover-bg: var(--ui-hover-bg, var(--btn-bg));
|
|
38
|
+
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
39
|
+
|
|
40
|
+
position: relative;
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
gap: 0.5em;
|
|
45
|
+
min-block-size: var(--btn-min-size);
|
|
46
|
+
padding-block: var(--space-2);
|
|
47
|
+
padding-block: max(var(--space-1), calc((var(--btn-min-size) - 1lh) / 2));
|
|
48
|
+
padding-inline: var(--btn-pad-x);
|
|
49
|
+
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
50
|
+
border-block-end-color: var(
|
|
51
|
+
--btn-border-block-end-color,
|
|
52
|
+
var(--btn-border-color, var(--btn-border))
|
|
53
|
+
);
|
|
54
|
+
border-block-end-width: var(--btn-border-block-end-width, var(--border-width));
|
|
55
|
+
border-radius: var(--btn-radius);
|
|
56
|
+
background: var(--btn-bg);
|
|
57
|
+
color: var(--btn-fg);
|
|
58
|
+
font: inherit;
|
|
59
|
+
font-size: var(--btn-font-size);
|
|
60
|
+
font-weight: var(--font-weight-strong);
|
|
61
|
+
line-height: 1;
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
transition:
|
|
65
|
+
background-color var(--duration),
|
|
66
|
+
border-color var(--duration),
|
|
67
|
+
color var(--duration),
|
|
68
|
+
box-shadow var(--duration),
|
|
69
|
+
transform var(--duration);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
73
|
+
.btn {
|
|
74
|
+
--btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* .solid/.soft/.outline are shared via variants.css. The baseline --btn-*
|
|
79
|
+
fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
|
|
80
|
+
.btn.ghost {
|
|
81
|
+
--ui-bg: transparent;
|
|
82
|
+
--ui-fg: var(--intent, var(--text));
|
|
83
|
+
--ui-border: transparent;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Solid/soft get the overlay darkening on hover; outline/ghost/link stay flat.
|
|
87
|
+
Routed through a variable instead of a :not(.outline, .ghost, .link)
|
|
88
|
+
exclusion so the hover rule below only has to state the interactive
|
|
89
|
+
element list once. */
|
|
90
|
+
.btn.outline,
|
|
91
|
+
.btn.ghost,
|
|
92
|
+
.btn.link {
|
|
93
|
+
--btn-hover-overlay: transparent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is shared with
|
|
97
|
+
.badge in variants.css; the overlay darkening for solid/soft is .btn-specific. */
|
|
98
|
+
@media (hover: hover) {
|
|
99
|
+
.btn:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
100
|
+
background: var(--btn-hover-bg);
|
|
101
|
+
box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.btn:not(:where(:disabled, [aria-disabled="true"])):active {
|
|
106
|
+
transform: translateY(1px);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.btn.link {
|
|
110
|
+
--ui-bg: transparent;
|
|
111
|
+
--ui-fg: var(--intent, var(--text));
|
|
112
|
+
--ui-border: transparent;
|
|
113
|
+
--btn-radius: var(--radius-sm);
|
|
114
|
+
--btn-border-block-end-color: transparent;
|
|
115
|
+
--btn-border-block-end-width: var(--border-width);
|
|
116
|
+
|
|
117
|
+
min-block-size: auto;
|
|
118
|
+
padding-inline: 0;
|
|
119
|
+
border-color: transparent;
|
|
120
|
+
text-decoration: underline;
|
|
121
|
+
text-underline-offset: 0.18em;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* .btn.circle needs no local rule: the .circle utility (utilities.css) loads
|
|
125
|
+
after components and overrides border-radius at equal specificity. */
|
|
126
|
+
|
|
127
|
+
@media (hover: hover) {
|
|
128
|
+
.btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
129
|
+
background: transparent;
|
|
130
|
+
color: var(--intent, var(--text));
|
|
131
|
+
text-decoration-thickness: 0.12em;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.btn:focus-visible {
|
|
136
|
+
outline: var(--btn-focus-outline-width) solid var(--btn-focus-color);
|
|
137
|
+
outline-offset: var(--focus-outline-offset);
|
|
138
|
+
box-shadow: 0 0 0 3px var(--btn-focus-ring-color, var(--focus-ring));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.btn.link:focus-visible {
|
|
142
|
+
outline-color: currentColor;
|
|
143
|
+
outline-offset: 0.2em;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.btn:disabled {
|
|
147
|
+
cursor: not-allowed;
|
|
148
|
+
opacity: var(--disabled-opacity);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Anchors have no native disabled state.
|
|
152
|
+
a.btn[aria-disabled="true"] only provides disabled styling and blocks pointer
|
|
153
|
+
activation. For a truly disabled link, remove href or prevent activation in
|
|
154
|
+
app code. */
|
|
155
|
+
a.btn[aria-disabled="true"] {
|
|
156
|
+
cursor: not-allowed;
|
|
157
|
+
opacity: var(--disabled-opacity);
|
|
158
|
+
pointer-events: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Busy */
|
|
162
|
+
.btn[aria-busy="true"]:not(:disabled) {
|
|
163
|
+
cursor: progress;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.btn[aria-busy="true"] > .spinner {
|
|
167
|
+
--spinner-size: 1em;
|
|
168
|
+
--spinner-width: 0.15em;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Toggle */
|
|
172
|
+
/* State must outrank the shared .outline/.ghost hover recipes: a pressed
|
|
173
|
+
button keeps its intent background and foreground while hovered. */
|
|
174
|
+
.btn:is([aria-pressed="true"], [aria-current]:not([aria-current="false"])):not(.link) {
|
|
175
|
+
--btn-bg: var(--intent, var(--primary));
|
|
176
|
+
--btn-fg: var(--intent-fg, var(--primary-fg));
|
|
177
|
+
--btn-border: var(--intent, var(--primary));
|
|
178
|
+
--ui-hover-bg: var(--btn-bg);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@media (forced-colors: active) {
|
|
182
|
+
.btn {
|
|
183
|
+
border-color: ButtonText;
|
|
184
|
+
background: ButtonFace;
|
|
185
|
+
color: ButtonText;
|
|
186
|
+
box-shadow: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.btn.link {
|
|
190
|
+
border-color: transparent;
|
|
191
|
+
background: Canvas;
|
|
192
|
+
color: LinkText;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.btn:is([aria-pressed="true"], [aria-current]:not([aria-current="false"])):not(.link) {
|
|
196
|
+
border-color: Highlight;
|
|
197
|
+
background: Highlight;
|
|
198
|
+
color: HighlightText;
|
|
199
|
+
forced-color-adjust: none;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@media (hover: hover) {
|
|
203
|
+
.btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
|
|
204
|
+
outline: var(--border-width) solid ButtonText;
|
|
205
|
+
outline-offset: 2px;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.btn:disabled,
|
|
210
|
+
a.btn[aria-disabled="true"] {
|
|
211
|
+
border-color: GrayText;
|
|
212
|
+
background: Canvas;
|
|
213
|
+
color: GrayText;
|
|
214
|
+
forced-color-adjust: auto;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Cards provide a neutral raised surface for grouped content.
|
|
3
|
+
*
|
|
4
|
+
* Surface, border, radius, padding, density, and elevation stay local here.
|
|
5
|
+
* Direct header/footer children are structural slots. Page layout remains the
|
|
6
|
+
* job of markup and layout primitives outside the card.
|
|
7
|
+
*/
|
|
8
|
+
/* Intent boundary — inherited intents do not tint this component.
|
|
9
|
+
Local intent classes still win because :where() has zero specificity. */
|
|
10
|
+
:where(.card) {
|
|
11
|
+
/* @sync intent-boundary */
|
|
12
|
+
--intent: initial;
|
|
13
|
+
--intent-fg: initial;
|
|
14
|
+
--ui-bg: initial;
|
|
15
|
+
--ui-fg: initial;
|
|
16
|
+
--ui-border: initial;
|
|
17
|
+
--ui-hover-bg: initial;
|
|
18
|
+
/* @sync intent-boundary */
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.card {
|
|
22
|
+
--card-pad: var(--space-4);
|
|
23
|
+
--card-radius: var(--radius-lg);
|
|
24
|
+
--card-max-inline-size: none;
|
|
25
|
+
|
|
26
|
+
max-inline-size: var(--card-max-inline-size);
|
|
27
|
+
padding: var(--card-pad);
|
|
28
|
+
background: var(--ui-bg, var(--surface));
|
|
29
|
+
color: var(--ui-fg, var(--text));
|
|
30
|
+
border: var(--border-width) solid var(--ui-border, var(--border));
|
|
31
|
+
border-radius: var(--card-radius);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.card > * {
|
|
35
|
+
min-inline-size: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.card > :where(header, footer) {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-wrap: wrap;
|
|
41
|
+
align-items: var(--items-align, center);
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
gap: var(--space-3);
|
|
44
|
+
min-inline-size: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.card > :where(header, footer) > * {
|
|
48
|
+
min-inline-size: 0;
|
|
49
|
+
margin-block: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.card > header {
|
|
53
|
+
align-items: var(--items-align, flex-start);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.card > :first-child {
|
|
57
|
+
margin-block-start: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.card > :last-child {
|
|
61
|
+
margin-block-end: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.card.raised {
|
|
65
|
+
box-shadow: var(--shadow);
|
|
66
|
+
}
|
|
67
|
+
.card.subtle {
|
|
68
|
+
background: var(--surface-subtle);
|
|
69
|
+
}
|
|
70
|
+
.card.inverted {
|
|
71
|
+
background: var(--surface-solid);
|
|
72
|
+
color: var(--surface);
|
|
73
|
+
border-color: transparent;
|
|
74
|
+
--heading: var(--surface);
|
|
75
|
+
--busy-overlay-bg: var(--surface-solid);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.card.compact {
|
|
79
|
+
--card-pad: var(--space-2);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Full-bleed children */
|
|
83
|
+
.card > .bleed {
|
|
84
|
+
margin-inline: calc(var(--card-pad) * -1);
|
|
85
|
+
max-inline-size: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.card > .bleed:first-child {
|
|
89
|
+
margin-block-start: calc(var(--card-pad) * -1);
|
|
90
|
+
border-start-start-radius: inherit;
|
|
91
|
+
border-start-end-radius: inherit;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.card > .bleed:last-child {
|
|
95
|
+
margin-block-end: calc(var(--card-pad) * -1);
|
|
96
|
+
border-end-start-radius: inherit;
|
|
97
|
+
border-end-end-radius: inherit;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Media bleed */
|
|
101
|
+
.card > :where(img, picture, video, canvas, svg).bleed {
|
|
102
|
+
display: block;
|
|
103
|
+
inline-size: calc(100% + var(--card-pad) * 2);
|
|
104
|
+
max-inline-size: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.card > picture.bleed {
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.card > picture.bleed > img {
|
|
112
|
+
display: block;
|
|
113
|
+
inline-size: 100%;
|
|
114
|
+
block-size: auto;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Padded structural bleed */
|
|
118
|
+
.card > :where(header, footer, figure).bleed {
|
|
119
|
+
padding: var(--card-pad);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media (forced-colors: active) {
|
|
123
|
+
.card {
|
|
124
|
+
border-color: ButtonText;
|
|
125
|
+
background: Canvas;
|
|
126
|
+
color: CanvasText;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.card.raised {
|
|
130
|
+
box-shadow: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.card.inverted {
|
|
134
|
+
--heading: CanvasText;
|
|
135
|
+
|
|
136
|
+
border-color: ButtonText;
|
|
137
|
+
background: Canvas;
|
|
138
|
+
color: CanvasText;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Drawer — modal side-sheet built on the native dialog element.
|
|
3
|
+
*
|
|
4
|
+
* Top-layer rendering, ::backdrop, Escape, and modal focus come from the
|
|
5
|
+
* native dialog. CSS owns progressive enter/exit motion where browsers support
|
|
6
|
+
* discrete display/overlay transitions. The drawer is a layout shell only —
|
|
7
|
+
* navigation content inside uses .nav-link and .accordion, which carry their
|
|
8
|
+
* own link/summary styling.
|
|
9
|
+
*/
|
|
10
|
+
dialog.drawer {
|
|
11
|
+
--drawer-size: 20rem;
|
|
12
|
+
--drawer-pad: var(--space-4);
|
|
13
|
+
--drawer-closed-translate: -100%;
|
|
14
|
+
|
|
15
|
+
inline-size: min(calc(var(--viewport-inline) * 0.9), var(--drawer-size));
|
|
16
|
+
block-size: var(--viewport-block);
|
|
17
|
+
max-inline-size: none;
|
|
18
|
+
max-block-size: var(--viewport-block);
|
|
19
|
+
margin: 0;
|
|
20
|
+
padding: var(--drawer-pad);
|
|
21
|
+
border: 0;
|
|
22
|
+
border-inline-end: var(--border-width) solid var(--border);
|
|
23
|
+
border-radius: 0;
|
|
24
|
+
background: var(--surface-raised);
|
|
25
|
+
color: var(--text);
|
|
26
|
+
box-shadow: var(--shadow);
|
|
27
|
+
opacity: 0;
|
|
28
|
+
transform: translateX(var(--drawer-closed-translate));
|
|
29
|
+
transition:
|
|
30
|
+
opacity var(--duration-slow),
|
|
31
|
+
transform var(--duration-slow);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
dialog.drawer[open] {
|
|
35
|
+
display: grid;
|
|
36
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
37
|
+
gap: var(--space-4);
|
|
38
|
+
opacity: 1;
|
|
39
|
+
transform: translateX(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Backdrop color sourced from components/surface.css so it matches .modal
|
|
43
|
+
and .surface-backdrop across the dialog family. */
|
|
44
|
+
dialog.drawer::backdrop {
|
|
45
|
+
background: var(--backdrop-fill);
|
|
46
|
+
opacity: 0;
|
|
47
|
+
transition: opacity var(--duration-slow);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
dialog.drawer[data-side="end"] {
|
|
51
|
+
--drawer-closed-translate: 100%;
|
|
52
|
+
|
|
53
|
+
margin-inline-start: auto;
|
|
54
|
+
margin-inline-end: 0;
|
|
55
|
+
border-inline-end: 0;
|
|
56
|
+
border-inline-start: var(--border-width) solid var(--border);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
dialog.drawer[open]::backdrop {
|
|
60
|
+
opacity: 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@starting-style {
|
|
64
|
+
dialog.drawer[open] {
|
|
65
|
+
opacity: 0;
|
|
66
|
+
transform: translateX(var(--drawer-closed-translate));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
dialog.drawer[open]::backdrop {
|
|
70
|
+
opacity: 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Scrollable navigation region inside the drawer. */
|
|
75
|
+
dialog.drawer nav {
|
|
76
|
+
min-block-size: 0;
|
|
77
|
+
overflow-y: auto;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@supports (transition-behavior: allow-discrete) {
|
|
81
|
+
dialog.drawer {
|
|
82
|
+
transition:
|
|
83
|
+
opacity var(--duration-slow),
|
|
84
|
+
transform var(--duration-slow),
|
|
85
|
+
overlay var(--duration-slow) allow-discrete,
|
|
86
|
+
display var(--duration-slow) allow-discrete;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
dialog.drawer::backdrop {
|
|
90
|
+
transition:
|
|
91
|
+
opacity var(--duration-slow),
|
|
92
|
+
overlay var(--duration-slow) allow-discrete,
|
|
93
|
+
display var(--duration-slow) allow-discrete;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* RTL: swap closed transform directions so the drawer slides from the correct
|
|
98
|
+
inline side. */
|
|
99
|
+
[dir="rtl"] dialog.drawer {
|
|
100
|
+
--drawer-closed-translate: 100%;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
[dir="rtl"] dialog.drawer[data-side="end"] {
|
|
104
|
+
--drawer-closed-translate: -100%;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@media (forced-colors: active) {
|
|
108
|
+
dialog.drawer {
|
|
109
|
+
border-inline-end-color: ButtonText;
|
|
110
|
+
background: Canvas;
|
|
111
|
+
color: CanvasText;
|
|
112
|
+
box-shadow: none;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
dialog.drawer[data-side="end"] {
|
|
116
|
+
border-inline-start-color: ButtonText;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
dialog.drawer::backdrop {
|
|
120
|
+
background: Canvas;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Flyout — floating panel for actions and links.
|
|
3
|
+
*
|
|
4
|
+
* JavaScript owns positioning (position: fixed + left/top), state
|
|
5
|
+
* (hidden + .is-open), focus, and keyboard behavior. CSS owns the surfaced
|
|
6
|
+
* panel appearance, item styles, and animation.
|
|
7
|
+
*
|
|
8
|
+
* Runtime markers (managed by surface.js): .is-open marks an open flyout,
|
|
9
|
+
* .is-sheet marks "auto" resolved to bottom-sheet presentation on coarse
|
|
10
|
+
* pointers. Authors opt into mobile behavior with
|
|
11
|
+
* [data-flyout-mobile="sheet|auto|none|anchored"].
|
|
12
|
+
* --flyout-trigger-width is set by surface.js while an open flyout is positioned.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
.flyout-trigger {
|
|
16
|
+
position: relative;
|
|
17
|
+
display: inline-block;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Stretch: for a flyout trigger that must span its container — e.g. the
|
|
21
|
+
last row of a full-width sidebar nav list. Stretches both the
|
|
22
|
+
positioning wrapper and its trigger control. Named for the effect
|
|
23
|
+
(stretch to fill), not the display value, to avoid reading like a
|
|
24
|
+
generic display-block utility. */
|
|
25
|
+
.flyout-trigger.stretch {
|
|
26
|
+
display: block;
|
|
27
|
+
inline-size: 100%;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.flyout-trigger.stretch > [aria-haspopup="menu"] {
|
|
31
|
+
inline-size: 100%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.flyout {
|
|
35
|
+
z-index: var(--z-menu);
|
|
36
|
+
/* No-JS fallback: below the trigger inside the relative .flyout-trigger.
|
|
37
|
+
surface.js switches an open flyout to position: fixed with measured
|
|
38
|
+
coordinates; this only covers a flyout revealed without the runtime. */
|
|
39
|
+
position: absolute;
|
|
40
|
+
inset-block-start: calc(100% + var(--space-1));
|
|
41
|
+
inset-inline-start: 0;
|
|
42
|
+
display: grid;
|
|
43
|
+
gap: var(--space-1);
|
|
44
|
+
inline-size: var(--flyout-inline-size, max-content);
|
|
45
|
+
min-inline-size: min(
|
|
46
|
+
var(--flyout-trigger-width, 0px),
|
|
47
|
+
min(var(--flyout-max-inline-size, 20rem), calc(var(--viewport-inline) - var(--space-6)))
|
|
48
|
+
);
|
|
49
|
+
max-inline-size: min(
|
|
50
|
+
var(--flyout-max-inline-size, 20rem),
|
|
51
|
+
calc(var(--viewport-inline) - var(--space-6))
|
|
52
|
+
);
|
|
53
|
+
max-block-size: min(24rem, var(--available-height, calc(var(--viewport-block) - var(--space-6))));
|
|
54
|
+
overflow: auto;
|
|
55
|
+
margin: 0;
|
|
56
|
+
padding: var(--space-1);
|
|
57
|
+
border: var(--border-width) solid var(--border);
|
|
58
|
+
border-radius: var(--radius);
|
|
59
|
+
background: var(--surface-raised);
|
|
60
|
+
box-shadow: var(--shadow-popout);
|
|
61
|
+
list-style: none;
|
|
62
|
+
opacity: 1;
|
|
63
|
+
scale: 1;
|
|
64
|
+
transition:
|
|
65
|
+
opacity var(--duration-fast, var(--duration)) var(--ease, ease),
|
|
66
|
+
scale var(--duration-fast, var(--duration)) var(--ease, ease),
|
|
67
|
+
translate var(--duration-fast, var(--duration)) var(--ease, ease);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.flyout.is-open:not(.is-sheet) {
|
|
71
|
+
inset: auto;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@starting-style {
|
|
75
|
+
.flyout.is-open {
|
|
76
|
+
opacity: 0;
|
|
77
|
+
scale: 0.98;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Bottom-sheet presentation. surface.js toggles .is-sheet when the flyout is
|
|
82
|
+
opened on coarse pointer / narrow viewports. */
|
|
83
|
+
.flyout.is-sheet {
|
|
84
|
+
position: fixed;
|
|
85
|
+
inset-inline: 0;
|
|
86
|
+
inset-block-start: auto;
|
|
87
|
+
inset-block-end: 0;
|
|
88
|
+
inline-size: auto;
|
|
89
|
+
min-inline-size: 0;
|
|
90
|
+
max-inline-size: none;
|
|
91
|
+
max-block-size: min(calc(var(--viewport-block) * 0.75), 32rem);
|
|
92
|
+
overflow: auto;
|
|
93
|
+
grid-template-columns: 1fr;
|
|
94
|
+
padding: max(var(--space-3), env(safe-area-inset-top)) var(--space-3)
|
|
95
|
+
max(var(--space-3), env(safe-area-inset-bottom));
|
|
96
|
+
border-end-start-radius: 0;
|
|
97
|
+
border-end-end-radius: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.flyout[hidden] {
|
|
101
|
+
display: none;
|
|
102
|
+
opacity: 0;
|
|
103
|
+
scale: 0.98;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.flyout.is-sheet[hidden] {
|
|
107
|
+
scale: 1;
|
|
108
|
+
translate: 0 var(--space-3);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@starting-style {
|
|
112
|
+
.flyout.is-sheet.is-open {
|
|
113
|
+
scale: 1;
|
|
114
|
+
translate: 0 var(--space-3);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@supports (transition-behavior: allow-discrete) {
|
|
119
|
+
.flyout {
|
|
120
|
+
transition:
|
|
121
|
+
opacity var(--duration-fast, var(--duration)) var(--ease, ease),
|
|
122
|
+
scale var(--duration-fast, var(--duration)) var(--ease, ease),
|
|
123
|
+
translate var(--duration-fast, var(--duration)) var(--ease, ease),
|
|
124
|
+
display var(--duration-fast, var(--duration)) allow-discrete;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.flyout > li {
|
|
129
|
+
margin: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.flyout > :where(section, footer) {
|
|
133
|
+
min-inline-size: 0;
|
|
134
|
+
padding: var(--space-2);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.flyout > section > :where(h1, h2, h3, h4, h5, h6) {
|
|
138
|
+
margin: 0;
|
|
139
|
+
color: var(--text-muted);
|
|
140
|
+
font-size: var(--font-size-xs);
|
|
141
|
+
font-weight: var(--font-weight-strong);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.flyout > section > ul {
|
|
145
|
+
display: grid;
|
|
146
|
+
gap: var(--space-1);
|
|
147
|
+
margin: var(--space-1) 0 0;
|
|
148
|
+
padding: 0;
|
|
149
|
+
list-style: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.flyout > section > ul > li {
|
|
153
|
+
margin: 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Item contract: a button, link, or menuitem placed directly in the flyout
|
|
157
|
+
or directly in any of its <li> (bare list, or grouped section > ul > li).
|
|
158
|
+
menu.js mirrors this contract in MENU_ITEM_SELECTOR — keep them in sync.
|
|
159
|
+
:where(li) keeps specificity at a single class (0,1,0) so consumer
|
|
160
|
+
overrides behave the same as a plain .flyout > rule. */
|
|
161
|
+
:is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]) {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
justify-content: flex-start;
|
|
165
|
+
gap: var(--space-2);
|
|
166
|
+
inline-size: 100%;
|
|
167
|
+
min-block-size: var(--control-size);
|
|
168
|
+
padding-inline: var(--space-2);
|
|
169
|
+
border: 0;
|
|
170
|
+
border-radius: var(--radius-sm);
|
|
171
|
+
background: none;
|
|
172
|
+
color: var(--text);
|
|
173
|
+
font: inherit;
|
|
174
|
+
font-weight: var(--font-weight);
|
|
175
|
+
text-decoration: none;
|
|
176
|
+
text-align: start;
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
:is(.flyout.is-sheet, .flyout.is-sheet :where(li)) > :where(button, a, [role="menuitem"]) {
|
|
182
|
+
min-block-size: var(--control-size-lg);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
:is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]):focus-visible {
|
|
186
|
+
background: var(--surface-subtle);
|
|
187
|
+
outline: var(--focus-outline);
|
|
188
|
+
outline-offset: var(--focus-outline-offset);
|
|
189
|
+
box-shadow: var(--focus-ring-shadow);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
:is(.flyout, .flyout :where(li))
|
|
193
|
+
> :where(button, a, [role="menuitem"]):is(:disabled, [aria-disabled="true"]) {
|
|
194
|
+
cursor: not-allowed;
|
|
195
|
+
opacity: var(--disabled-opacity);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
:is(.flyout, .flyout :where(li)) > :where(a, [role="menuitem"])[aria-disabled="true"] {
|
|
199
|
+
pointer-events: none;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@media (hover: hover) {
|
|
203
|
+
:is(.flyout, .flyout :where(li))
|
|
204
|
+
> :where(button, a, [role="menuitem"]):hover:not(
|
|
205
|
+
:where(:focus-visible, :disabled, [aria-disabled="true"])
|
|
206
|
+
) {
|
|
207
|
+
background: var(--surface-subtle);
|
|
208
|
+
outline: none;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.flyout > hr,
|
|
213
|
+
.flyout > li > hr {
|
|
214
|
+
inline-size: 100%;
|
|
215
|
+
margin: 0;
|
|
216
|
+
margin-block: var(--space-1);
|
|
217
|
+
border: 0;
|
|
218
|
+
border-block-start: var(--border-width) solid var(--border);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@media (forced-colors: active) {
|
|
222
|
+
.flyout {
|
|
223
|
+
border-color: ButtonText;
|
|
224
|
+
background: Canvas;
|
|
225
|
+
color: CanvasText;
|
|
226
|
+
box-shadow: none;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
:is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]):focus-visible {
|
|
230
|
+
background: Canvas;
|
|
231
|
+
color: CanvasText;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@media (hover: hover) {
|
|
235
|
+
:is(.flyout, .flyout :where(li))
|
|
236
|
+
> :where(button, a, [role="menuitem"]):hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
237
|
+
outline: var(--border-width) solid ButtonText;
|
|
238
|
+
outline-offset: -1px;
|
|
239
|
+
background: Canvas;
|
|
240
|
+
color: CanvasText;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|