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,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Form layout and supporting text.
|
|
3
|
+
*
|
|
4
|
+
* Individual controls keep their own files; .choice label layout lives in
|
|
5
|
+
* choice.css; .form-actions lives in form-actions.css.
|
|
6
|
+
*
|
|
7
|
+
* Public API: .field, .field-label, .field-help, .field-error, .field-group,
|
|
8
|
+
* .required-mark.
|
|
9
|
+
*/
|
|
10
|
+
.field {
|
|
11
|
+
display: grid;
|
|
12
|
+
gap: var(--space-1);
|
|
13
|
+
color: var(--text);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.field-group {
|
|
17
|
+
min-inline-size: 0;
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: var(--space-3);
|
|
20
|
+
border: var(--border-width) solid var(--border);
|
|
21
|
+
border-radius: var(--radius);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.field-group + .field-group {
|
|
25
|
+
margin-block-start: var(--space-4);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.field-label,
|
|
29
|
+
.field-group > legend {
|
|
30
|
+
color: var(--text);
|
|
31
|
+
font-weight: var(--font-weight-strong);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.field-group > legend {
|
|
35
|
+
padding-block: 0;
|
|
36
|
+
padding-inline: var(--fieldset-legend-padding-inline, var(--space-1));
|
|
37
|
+
font-size: var(--font-size-xs);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.field-help {
|
|
41
|
+
color: var(--text-muted);
|
|
42
|
+
font-size: var(--font-size-sm);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.field-error {
|
|
46
|
+
display: none;
|
|
47
|
+
color: var(--danger);
|
|
48
|
+
font-size: var(--font-size-sm);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.required-mark {
|
|
52
|
+
color: var(--danger);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.field.danger .field-help {
|
|
56
|
+
color: var(--danger);
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* Forms — import order matters: switch.css must follow choice.css so its
|
|
2
|
+
:not([role="switch"]) exclusion in choice.css still applies.
|
|
3
|
+
validation.css is part of the default forms bundle, but custom builds may
|
|
4
|
+
omit it when validation styling is app-owned. */
|
|
5
|
+
@import "./form.css";
|
|
6
|
+
@import "./control.css";
|
|
7
|
+
@import "./input-icon.css";
|
|
8
|
+
@import "./choice.css";
|
|
9
|
+
@import "./choice-card.css";
|
|
10
|
+
@import "./select.css";
|
|
11
|
+
@import "./custom-select.css";
|
|
12
|
+
@import "./native.css";
|
|
13
|
+
@import "./switch.css";
|
|
14
|
+
@import "./validation.css";
|
|
15
|
+
@import "./form-actions.css";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Input Icon — leading decorative icon inside a relative wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Icon-library agnostic: selects the wrapper's first child rather than a
|
|
5
|
+
* specific tag, so it works with an <i> webfont icon, an inline <svg>, or a
|
|
6
|
+
* <span>. Sizes with --variant-icon-size (variants.css) so .sm/.lg pick up
|
|
7
|
+
* the same icon scale used elsewhere (alert.css); the input's start padding
|
|
8
|
+
* grows to match so text never runs under the icon.
|
|
9
|
+
*/
|
|
10
|
+
.input-icon {
|
|
11
|
+
--input-icon-size: var(--variant-icon-size, 1.25rem);
|
|
12
|
+
|
|
13
|
+
display: block;
|
|
14
|
+
position: relative;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.input-icon > :first-child {
|
|
18
|
+
position: absolute;
|
|
19
|
+
inset-block-start: 50%;
|
|
20
|
+
inset-inline-start: var(--space-3);
|
|
21
|
+
transform: translateY(-50%);
|
|
22
|
+
font-size: var(--input-icon-size);
|
|
23
|
+
color: var(--text-muted);
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* font-size only sizes text-metric icons (webfont <i>, <span>). An inline
|
|
28
|
+
<svg> with just a viewBox ignores it, so give it explicit dimensions. */
|
|
29
|
+
.input-icon > svg:first-child {
|
|
30
|
+
inline-size: var(--input-icon-size);
|
|
31
|
+
block-size: var(--input-icon-size);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.input-icon > .input {
|
|
35
|
+
padding-inline-start: calc(var(--space-3) * 2 + var(--input-icon-size));
|
|
36
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Native-only control polish: file and range inputs that defeat most custom
|
|
3
|
+
* styling, so the framework stays out of their way and just keeps them readable
|
|
4
|
+
* and theme-aware.
|
|
5
|
+
*/
|
|
6
|
+
/* Intent boundary — inherited intents do not tint this component.
|
|
7
|
+
Local intent classes still win because :where() has zero specificity. */
|
|
8
|
+
:where(.range) {
|
|
9
|
+
/* @sync intent-boundary */
|
|
10
|
+
--intent: initial;
|
|
11
|
+
--intent-fg: initial;
|
|
12
|
+
--ui-bg: initial;
|
|
13
|
+
--ui-fg: initial;
|
|
14
|
+
--ui-border: initial;
|
|
15
|
+
--ui-hover-bg: initial;
|
|
16
|
+
/* @sync intent-boundary */
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.file {
|
|
20
|
+
color: var(--text-muted);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.file::file-selector-button {
|
|
24
|
+
margin-inline-end: var(--space-3);
|
|
25
|
+
padding-block: var(--space-1);
|
|
26
|
+
padding-inline: var(--space-2);
|
|
27
|
+
border: var(--border-width) solid var(--border);
|
|
28
|
+
border-radius: var(--radius-sm);
|
|
29
|
+
background: var(--surface-subtle);
|
|
30
|
+
color: var(--text);
|
|
31
|
+
font: inherit;
|
|
32
|
+
font-size: var(--font-size-sm);
|
|
33
|
+
font-weight: var(--font-weight-medium);
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition:
|
|
36
|
+
background-color var(--duration),
|
|
37
|
+
border-color var(--duration),
|
|
38
|
+
color var(--duration);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.file:disabled::file-selector-button {
|
|
42
|
+
cursor: not-allowed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@media (hover: hover) {
|
|
46
|
+
.file:not(:disabled)::file-selector-button:hover {
|
|
47
|
+
background: var(--border);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.range {
|
|
52
|
+
inline-size: 100%;
|
|
53
|
+
accent-color: var(--intent, var(--primary));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.file:focus-visible,
|
|
57
|
+
.range:focus-visible {
|
|
58
|
+
outline: var(--focus-outline);
|
|
59
|
+
outline-offset: var(--focus-outline-offset);
|
|
60
|
+
box-shadow: var(--focus-ring-shadow);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@media (forced-colors: active) {
|
|
64
|
+
.file:focus-visible,
|
|
65
|
+
.range:focus-visible {
|
|
66
|
+
box-shadow: none;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Select — chevron and appearance layer on top of the shared text-control
|
|
3
|
+
* recipe.
|
|
4
|
+
*
|
|
5
|
+
* Base geometry and interaction states come from control.css (select shares
|
|
6
|
+
* the --control-* tokens). This file only removes the native arrow and draws
|
|
7
|
+
* the CSS chevron, so the picker (appearance: base-select) enhancement in
|
|
8
|
+
* custom-select.css can be opted out independently. Add .native to a specific
|
|
9
|
+
* select to opt out of the enhancement.
|
|
10
|
+
*/
|
|
11
|
+
.select:is(:not([multiple], [size]), :not([multiple])[size="1"]) {
|
|
12
|
+
appearance: none;
|
|
13
|
+
padding-inline-end: calc(var(--space-3) + var(--space-6));
|
|
14
|
+
background-image: var(--icon-chevron-image);
|
|
15
|
+
background-position: center right var(--space-3);
|
|
16
|
+
background-size: 1rem auto;
|
|
17
|
+
background-repeat: no-repeat;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* The chevron sits on the inline-end side; flip it in RTL so it follows
|
|
21
|
+
the padding-inline-end space reserved for it. */
|
|
22
|
+
.select:is(:not([multiple], [size]), :not([multiple])[size="1"]):dir(rtl) {
|
|
23
|
+
background-position: center left var(--space-3);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Both forced-colors and prefers-contrast hand the arrow back to the
|
|
27
|
+
browser instead of guessing a higher-contrast recolor of our own icon —
|
|
28
|
+
simpler than maintaining contrast variants, and native rendering already
|
|
29
|
+
matches whatever contrast mode the user picked. */
|
|
30
|
+
@media (forced-colors: active), (prefers-contrast: more) {
|
|
31
|
+
.select:is(:not([multiple], [size]), :not([multiple])[size="1"]) {
|
|
32
|
+
appearance: auto;
|
|
33
|
+
background-image: none;
|
|
34
|
+
padding-inline-end: var(--space-3);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Switch controls for checkbox inputs with switch semantics.
|
|
3
|
+
*
|
|
4
|
+
* The switch remains an input control, not a decorative toggle. Checked, hover,
|
|
5
|
+
* focus-visible, and disabled states stay clear.
|
|
6
|
+
*/
|
|
7
|
+
/* Intent boundary — inherited intents do not tint this component.
|
|
8
|
+
Local intent classes still win because :where() has zero specificity. */
|
|
9
|
+
:where(.switch) {
|
|
10
|
+
/* @sync intent-boundary */
|
|
11
|
+
--intent: initial;
|
|
12
|
+
--intent-fg: initial;
|
|
13
|
+
--ui-bg: initial;
|
|
14
|
+
--ui-fg: initial;
|
|
15
|
+
--ui-border: initial;
|
|
16
|
+
--ui-hover-bg: initial;
|
|
17
|
+
/* @sync intent-boundary */
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.switch {
|
|
21
|
+
--switch-bg: var(--border);
|
|
22
|
+
--switch-border: var(--border);
|
|
23
|
+
--switch-knob: var(--surface-raised);
|
|
24
|
+
--switch-width: calc(var(--control-size) * 1.16);
|
|
25
|
+
--switch-block-size: calc(var(--control-size) * 0.63);
|
|
26
|
+
--switch-knob-margin: calc(var(--switch-block-size) * 0.12);
|
|
27
|
+
--switch-knob-size: calc(var(--switch-block-size) - 2 * var(--switch-knob-margin));
|
|
28
|
+
--switch-travel: calc(
|
|
29
|
+
var(--switch-width) -
|
|
30
|
+
var(--switch-knob-size) -
|
|
31
|
+
2 *
|
|
32
|
+
var(--border-width) -
|
|
33
|
+
2 *
|
|
34
|
+
var(--switch-knob-margin)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
position: relative;
|
|
38
|
+
display: inline-grid;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: start;
|
|
41
|
+
inline-size: var(--switch-width);
|
|
42
|
+
block-size: var(--switch-block-size);
|
|
43
|
+
margin: 0;
|
|
44
|
+
border: var(--border-width) solid var(--form-invalid-border, var(--switch-border));
|
|
45
|
+
border-radius: var(--radius-full);
|
|
46
|
+
background: var(--switch-bg);
|
|
47
|
+
appearance: none;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.switch::before {
|
|
52
|
+
content: "";
|
|
53
|
+
inline-size: var(--switch-knob-size);
|
|
54
|
+
block-size: var(--switch-knob-size);
|
|
55
|
+
margin-inline: var(--switch-knob-margin);
|
|
56
|
+
border-radius: var(--radius-full);
|
|
57
|
+
background: var(--switch-knob);
|
|
58
|
+
box-shadow: var(--shadow);
|
|
59
|
+
transition:
|
|
60
|
+
transform var(--duration),
|
|
61
|
+
background-color var(--duration),
|
|
62
|
+
box-shadow var(--duration);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media (hover: hover) {
|
|
66
|
+
.switch:hover:not(:disabled) {
|
|
67
|
+
--switch-border: var(--intent, var(--primary));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.switch:checked {
|
|
72
|
+
--switch-bg: var(--intent, var(--primary));
|
|
73
|
+
--switch-border: var(--intent, var(--primary));
|
|
74
|
+
--switch-knob: var(--intent-fg, var(--primary-fg));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.switch:checked::before {
|
|
78
|
+
transform: translateX(var(--switch-travel));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* The knob travels toward inline-end; in RTL that is leftward, so the
|
|
82
|
+
translateX sign flips. translateX is physical, not logical. */
|
|
83
|
+
.switch:checked:dir(rtl)::before {
|
|
84
|
+
transform: translateX(calc(var(--switch-travel) * -1));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.switch:focus-visible {
|
|
88
|
+
--switch-border: var(--focus);
|
|
89
|
+
outline: var(--focus-outline);
|
|
90
|
+
outline-offset: var(--focus-outline-offset);
|
|
91
|
+
box-shadow: var(--focus-ring-shadow);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.switch:disabled {
|
|
95
|
+
--switch-bg: var(--surface-subtle);
|
|
96
|
+
--switch-border: var(--border);
|
|
97
|
+
--switch-knob: var(--text-subtle);
|
|
98
|
+
opacity: 1;
|
|
99
|
+
cursor: not-allowed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@media (forced-colors: active) {
|
|
103
|
+
/* --switch-knob must be set on the element, not on ::before: a custom
|
|
104
|
+
property declared on the pseudo would shadow the :checked override. */
|
|
105
|
+
.switch {
|
|
106
|
+
--switch-border: ButtonText;
|
|
107
|
+
--switch-knob: ButtonText;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.switch::before {
|
|
111
|
+
box-shadow: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.switch:checked {
|
|
115
|
+
--switch-bg: Highlight;
|
|
116
|
+
--switch-border: Highlight;
|
|
117
|
+
--switch-knob: HighlightText;
|
|
118
|
+
forced-color-adjust: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.switch:disabled {
|
|
122
|
+
--switch-bg: Canvas;
|
|
123
|
+
--switch-border: GrayText;
|
|
124
|
+
--switch-knob: GrayText;
|
|
125
|
+
forced-color-adjust: auto;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Form validation states.
|
|
3
|
+
*
|
|
4
|
+
* Imported by default through forms/index.css. Custom builds may omit this
|
|
5
|
+
* file when an app wants to own validation styling completely.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.field.danger {
|
|
9
|
+
--form-invalid-border: var(--danger);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.field.danger > .field-error {
|
|
13
|
+
display: block;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@supports selector(:has(*)) {
|
|
17
|
+
.field:has([aria-invalid="true"]) > .field-error,
|
|
18
|
+
.needs-validation.was-validated .field:has(:invalid) > .field-error {
|
|
19
|
+
display: block;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
:where(.input, .textarea, .select, .check, .radio, .switch)[aria-invalid="true"],
|
|
24
|
+
.needs-validation.was-validated :is(.input, .textarea, .select, .check, .radio, .switch):invalid {
|
|
25
|
+
--form-invalid-border: var(--danger);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Override checked/indeterminate background so both border and fill signal
|
|
29
|
+
danger instead of showing a red border around a primary-colored control. */
|
|
30
|
+
.check[aria-invalid="true"]:is(:checked, :indeterminate),
|
|
31
|
+
.radio[aria-invalid="true"]:checked {
|
|
32
|
+
--choice-bg: var(--danger);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.switch[aria-invalid="true"]:checked {
|
|
36
|
+
--switch-bg: var(--danger);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Choice cards hide their native input, so invalid state must surface on the
|
|
40
|
+
card itself. Sets the same --form-invalid-border hook the card's border
|
|
41
|
+
consumes. */
|
|
42
|
+
@supports selector(:has(*)) {
|
|
43
|
+
.choice-card:has([aria-invalid="true"]),
|
|
44
|
+
.needs-validation.was-validated .choice-card:has(:invalid) {
|
|
45
|
+
--form-invalid-border: var(--danger);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@supports selector(:user-invalid) {
|
|
50
|
+
.needs-validation :is(.input, .textarea, .select, .check, .radio, .switch):user-invalid {
|
|
51
|
+
--form-invalid-border: var(--danger);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@supports selector(:has(:user-invalid)) {
|
|
56
|
+
.needs-validation .field:has(:user-invalid) > .field-error {
|
|
57
|
+
display: block;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.needs-validation .choice-card:has(:user-invalid) {
|
|
61
|
+
--form-invalid-border: var(--danger);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/css/grid.css
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
.grid {
|
|
2
|
+
display: grid;
|
|
3
|
+
gap: var(--gap);
|
|
4
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* Query-container wrapper that opts .grid-2/3/4/6 into responsive
|
|
8
|
+
* collapse. Wrap a .grid-2/3/4/6 inside .container-query to make the
|
|
9
|
+
* column count collapse on narrow wrappers. Without this wrapper,
|
|
10
|
+
* .grid-2/3/4/6 stay at their full column count. */
|
|
11
|
+
/* Fixed-column grids at their full count by default.
|
|
12
|
+
* .grid-2: 2 cols
|
|
13
|
+
* .grid-3: 3 cols
|
|
14
|
+
* .grid-4: 4 cols
|
|
15
|
+
* .grid-6: 6 cols
|
|
16
|
+
* When placed inside .container-query, the column count collapses
|
|
17
|
+
* on narrow wrappers. The intermediate 2-col step applies to
|
|
18
|
+
* .grid-3/.grid-4; .grid-6 starts at 2 columns because it is meant
|
|
19
|
+
* for compact items that do not need a full single-column stack.
|
|
20
|
+
* .grid-2: 1 col -> 2 cols
|
|
21
|
+
* .grid-3: 1 col -> 2 cols -> 3 cols
|
|
22
|
+
* .grid-4: 1 col -> 2 cols -> 4 cols
|
|
23
|
+
* .grid-6: 2 cols -> 3 cols -> 6 cols */
|
|
24
|
+
.grid-2,
|
|
25
|
+
.grid-3,
|
|
26
|
+
.grid-4,
|
|
27
|
+
.grid-6 {
|
|
28
|
+
display: grid;
|
|
29
|
+
gap: var(--gap);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.grid-2 {
|
|
33
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
34
|
+
}
|
|
35
|
+
.grid-3 {
|
|
36
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
37
|
+
}
|
|
38
|
+
.grid-4 {
|
|
39
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
40
|
+
}
|
|
41
|
+
.grid-6 {
|
|
42
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@container (width < 28rem) {
|
|
46
|
+
.container-query .grid-2,
|
|
47
|
+
.container-query .grid-3,
|
|
48
|
+
.container-query .grid-4 {
|
|
49
|
+
grid-template-columns: 1fr;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.container-query .grid-6 {
|
|
53
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@container (28rem <= width < 48rem) {
|
|
58
|
+
.container-query .grid-3,
|
|
59
|
+
.container-query .grid-4 {
|
|
60
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@container (28rem <= width < 64rem) {
|
|
65
|
+
.container-query .grid-6 {
|
|
66
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@supports not (container-type: inline-size) {
|
|
71
|
+
@media (max-width: 63.999rem) {
|
|
72
|
+
.container-query .grid-6 {
|
|
73
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@media (max-width: 47.999rem) {
|
|
78
|
+
.container-query .grid-3,
|
|
79
|
+
.container-query .grid-4 {
|
|
80
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@media (max-width: 27.999rem) {
|
|
85
|
+
.container-query .grid-2,
|
|
86
|
+
.container-query .grid-3,
|
|
87
|
+
.container-query .grid-4 {
|
|
88
|
+
grid-template-columns: 1fr;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.container-query .grid-6 {
|
|
92
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Intent classes do not style elements directly.
|
|
3
|
+
* They only expose --intent and --intent-fg.
|
|
4
|
+
* Components and variants decide how to consume them. Each component that
|
|
5
|
+
* consumes --intent or --ui-* must open a local intent boundary in a :where()
|
|
6
|
+
* block that resets --intent / --intent-fg / --ui-* to initial. Parent intents
|
|
7
|
+
* do not leak into nested components. Local intent classes (.primary, .danger,
|
|
8
|
+
* etc.) still apply because they have higher specificity than :where().
|
|
9
|
+
*/
|
|
10
|
+
.primary {
|
|
11
|
+
--intent: var(--primary);
|
|
12
|
+
--intent-fg: var(--primary-fg);
|
|
13
|
+
}
|
|
14
|
+
.secondary {
|
|
15
|
+
--intent: var(--secondary);
|
|
16
|
+
--intent-fg: var(--secondary-fg);
|
|
17
|
+
}
|
|
18
|
+
.success {
|
|
19
|
+
--intent: var(--success);
|
|
20
|
+
--intent-fg: var(--success-fg);
|
|
21
|
+
}
|
|
22
|
+
.warning {
|
|
23
|
+
--intent: var(--warning);
|
|
24
|
+
--intent-fg: var(--warning-fg);
|
|
25
|
+
}
|
|
26
|
+
.danger {
|
|
27
|
+
--intent: var(--danger);
|
|
28
|
+
--intent-fg: var(--danger-fg);
|
|
29
|
+
}
|
|
30
|
+
.neutral {
|
|
31
|
+
--intent: var(--neutral);
|
|
32
|
+
--intent-fg: var(--neutral-fg);
|
|
33
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.container-query {
|
|
2
|
+
container-type: inline-size;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.stack {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: var(--gap);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.stack > * {
|
|
12
|
+
margin: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.cluster {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-wrap: wrap;
|
|
18
|
+
align-items: var(--cluster-align, center);
|
|
19
|
+
justify-content: var(--cluster-justify, flex-start);
|
|
20
|
+
gap: var(--gap);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.center {
|
|
24
|
+
box-sizing: content-box;
|
|
25
|
+
max-inline-size: var(--center-size, 72rem);
|
|
26
|
+
margin-inline: auto;
|
|
27
|
+
padding-inline: var(--center-pad, 1rem);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.frame {
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
aspect-ratio: var(--frame-ratio, 16 / 9);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.frame > :where(img, video, iframe, canvas, svg) {
|
|
36
|
+
inline-size: 100%;
|
|
37
|
+
block-size: 100%;
|
|
38
|
+
object-fit: cover;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.app-shell {
|
|
42
|
+
min-block-size: var(--viewport-block);
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Media object: fixed-width leading element (avatar, icon, image) + flexible
|
|
48
|
+
trailing content. Replaces the repeated "auto minmax(0, 1fr)" pattern in
|
|
49
|
+
author cards, comments, meta rows, and similar compositions. */
|
|
50
|
+
.media {
|
|
51
|
+
display: grid;
|
|
52
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
53
|
+
gap: var(--gap);
|
|
54
|
+
align-items: start;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Sidebar layout: content + aside using grid. Use this when the aside is
|
|
58
|
+
sticky or the content is multi-section. The aside width is tunable with
|
|
59
|
+
--sidebar-layout-size. */
|
|
60
|
+
.sidebar-layout {
|
|
61
|
+
display: grid;
|
|
62
|
+
gap: var(--gap);
|
|
63
|
+
grid-template-columns: minmax(0, 1fr);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@container (width >= 64rem) {
|
|
67
|
+
.container-query .sidebar-layout {
|
|
68
|
+
grid-template-columns: minmax(0, 1fr) var(--sidebar-layout-size, 18rem);
|
|
69
|
+
align-items: start;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@supports not (container-type: inline-size) {
|
|
74
|
+
@media (min-width: 64rem) {
|
|
75
|
+
.container-query .sidebar-layout {
|
|
76
|
+
grid-template-columns: minmax(0, 1fr) var(--sidebar-layout-size, 18rem);
|
|
77
|
+
align-items: start;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Additional layout primitives not included in the core bundle.
|
|
3
|
+
*
|
|
4
|
+
* Import this file explicitly when a project needs these patterns.
|
|
5
|
+
*/
|
|
6
|
+
.switcher {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
gap: var(--gap);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.switcher > * {
|
|
13
|
+
flex-basis: calc((var(--switcher-threshold, 40rem) - 100%) * 999);
|
|
14
|
+
flex-grow: 1;
|
|
15
|
+
min-inline-size: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Sticky, frosted top bar shell for app/dashboard shells (persistent
|
|
20
|
+
* sidebar + topbar layouts). Owns positioning, stacking, and the frosted
|
|
21
|
+
* background only — row content (search, breadcrumb, actions) stays local
|
|
22
|
+
* to the page.
|
|
23
|
+
*
|
|
24
|
+
* Not for marketing/editorial site headers — those are one-off visual
|
|
25
|
+
* identity per page; see docs/patterns.md's Header Navigation section.
|
|
26
|
+
* This is for the structural app-shell topbar, which recurs unchanged
|
|
27
|
+
* across app UIs regardless of brand.
|
|
28
|
+
*/
|
|
29
|
+
.topbar {
|
|
30
|
+
position: sticky;
|
|
31
|
+
inset-block-start: 0;
|
|
32
|
+
z-index: var(--z-sticky);
|
|
33
|
+
isolation: isolate;
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: var(--gap);
|
|
37
|
+
padding-block: var(--space-3);
|
|
38
|
+
padding-inline: var(--space-4);
|
|
39
|
+
border-block-end: var(--border-width) solid var(--border);
|
|
40
|
+
background: var(--surface);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Both conditions are required: the translucent background contains var(),
|
|
44
|
+
so a browser with backdrop-filter but not color-mix (e.g. Safari < 16.2)
|
|
45
|
+
would accept the declaration at parse time, fail it at computed-value
|
|
46
|
+
time, and lose the background entirely instead of keeping the opaque
|
|
47
|
+
fallback above. */
|
|
48
|
+
@supports ((backdrop-filter: blur(1rem)) or (-webkit-backdrop-filter: blur(1rem))) and
|
|
49
|
+
(background: color-mix(in oklch, red 50%, transparent)) {
|
|
50
|
+
.topbar {
|
|
51
|
+
background: color-mix(in oklch, var(--surface) 88%, transparent);
|
|
52
|
+
backdrop-filter: blur(0.75rem);
|
|
53
|
+
-webkit-backdrop-filter: blur(0.75rem);
|
|
54
|
+
}
|
|
55
|
+
}
|