@gratiaos/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +243 -0
- package/README.md +170 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useMissingScrew.d.ts +40 -0
- package/dist/hooks/useMissingScrew.d.ts.map +1 -0
- package/dist/hooks/useMissingScrew.js +76 -0
- package/dist/hooks/useMissingScrew.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/pad/Button.d.ts +10 -0
- package/dist/pad/Button.d.ts.map +1 -0
- package/dist/pad/Button.js +7 -0
- package/dist/pad/Button.js.map +1 -0
- package/dist/pad/Card.d.ts +8 -0
- package/dist/pad/Card.d.ts.map +1 -0
- package/dist/pad/Card.js +3 -0
- package/dist/pad/Card.js.map +1 -0
- package/dist/primitives/badge.d.ts +66 -0
- package/dist/primitives/badge.d.ts.map +1 -0
- package/dist/primitives/badge.js +23 -0
- package/dist/primitives/badge.js.map +1 -0
- package/dist/primitives/button.d.ts +60 -0
- package/dist/primitives/button.d.ts.map +1 -0
- package/dist/primitives/button.js +39 -0
- package/dist/primitives/button.js.map +1 -0
- package/dist/primitives/card.d.ts +54 -0
- package/dist/primitives/card.d.ts.map +1 -0
- package/dist/primitives/card.js +11 -0
- package/dist/primitives/card.js.map +1 -0
- package/dist/primitives/field.d.ts +107 -0
- package/dist/primitives/field.d.ts.map +1 -0
- package/dist/primitives/field.js +154 -0
- package/dist/primitives/field.js.map +1 -0
- package/dist/primitives/pill.d.ts +59 -0
- package/dist/primitives/pill.d.ts.map +1 -0
- package/dist/primitives/pill.js +26 -0
- package/dist/primitives/pill.js.map +1 -0
- package/dist/primitives/slot.d.ts +7 -0
- package/dist/primitives/slot.d.ts.map +1 -0
- package/dist/primitives/slot.js +9 -0
- package/dist/primitives/slot.js.map +1 -0
- package/dist/primitives/toast.d.ts +125 -0
- package/dist/primitives/toast.d.ts.map +1 -0
- package/dist/primitives/toast.js +462 -0
- package/dist/primitives/toast.js.map +1 -0
- package/package.json +87 -0
- package/styles/badge.css +175 -0
- package/styles/base.css +7 -0
- package/styles/button.css +257 -0
- package/styles/card.css +195 -0
- package/styles/field.css +195 -0
- package/styles/pad.css +140 -0
- package/styles/pill.css +167 -0
- package/styles/theme.css +492 -0
- package/styles/toast.css +286 -0
package/styles/badge.css
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
Garden UI — Badge skin (opt‑in)
|
|
3
|
+
Whisper: "small truths, softly visible." 🌬️
|
|
4
|
+
|
|
5
|
+
Purpose
|
|
6
|
+
• Selector-only skin for the headless Badge primitive.
|
|
7
|
+
• Driven entirely by Garden tokens; no hardcoded hex.
|
|
8
|
+
|
|
9
|
+
Data API
|
|
10
|
+
• [data-ui='badge'] — root
|
|
11
|
+
• [data-variant='soft|solid|outline|subtle'] — visual weight
|
|
12
|
+
• [data-tone='subtle|accent|positive|warning|danger']
|
|
13
|
+
• [data-size='sm|md'] — size (spacing handled in TS via baseClasses)
|
|
14
|
+
• [data-depth='inherit|0|1|2|3'] — shadow depth passthrough (optional)
|
|
15
|
+
|
|
16
|
+
A11y
|
|
17
|
+
• Icon slots are presentational; TS hides them with `aria-hidden`.
|
|
18
|
+
• The text content is the accessible name (no duplicate announcements).
|
|
19
|
+
|
|
20
|
+
Notes
|
|
21
|
+
• Keep motion calm; no default hover animations here.
|
|
22
|
+
• Subtle uses neutral surface tokens for low-stakes metadata.
|
|
23
|
+
• Variants map tone to token palettes; tones override accent.
|
|
24
|
+
──────────────────────────────────────────────────────────── */
|
|
25
|
+
@layer components {
|
|
26
|
+
[data-ui='badge'] {
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 0.25rem; /* tighter than pill */
|
|
30
|
+
border-radius: 0.375rem; /* squarer than pill; tokenizable later */
|
|
31
|
+
line-height: 1;
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
border: 1px solid transparent;
|
|
34
|
+
font-weight: 500;
|
|
35
|
+
user-select: none;
|
|
36
|
+
font-size: var(--text-2xs, 0.6875rem); /* 11px fallback */
|
|
37
|
+
padding: 0.125rem 0.375rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Size hooks (spacing is mostly in TS baseClasses; expose here for skins) */
|
|
41
|
+
[data-ui='badge'][data-size='sm'] {
|
|
42
|
+
/* defaults already set above */
|
|
43
|
+
}
|
|
44
|
+
[data-ui='badge'][data-size='md'] {
|
|
45
|
+
font-size: var(--text-xs);
|
|
46
|
+
padding: 0.25rem 0.5rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Depth passthrough — opt‑in shadow (badges are usually flat) */
|
|
50
|
+
[data-ui='badge'][data-depth='inherit'] {
|
|
51
|
+
box-shadow: var(--depth-shadow, var(--shadow-depth-1));
|
|
52
|
+
}
|
|
53
|
+
[data-ui='badge'][data-depth='0'] { box-shadow: var(--shadow-depth-0); }
|
|
54
|
+
[data-ui='badge'][data-depth='1'] { box-shadow: var(--shadow-depth-1); }
|
|
55
|
+
[data-ui='badge'][data-depth='2'] { box-shadow: var(--shadow-depth-2); }
|
|
56
|
+
[data-ui='badge'][data-depth='3'] { box-shadow: var(--shadow-depth-3); }
|
|
57
|
+
|
|
58
|
+
[data-ui='badge'] [data-slot~='icon'] {
|
|
59
|
+
display: inline-flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
flex-shrink: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Tone → focus ring color variable (used by :focus-visible) */
|
|
65
|
+
[data-ui='badge'][data-tone='subtle'] { --ring-accent: color-mix(in oklab, var(--color-text) 80%, white 20%); }
|
|
66
|
+
[data-ui='badge'][data-tone='accent'] { --ring-accent: color-mix(in oklab, var(--color-accent) 85%, white 15%); }
|
|
67
|
+
[data-ui='badge'][data-tone='positive']{ --ring-accent: color-mix(in oklab, var(--color-positive) 85%, white 15%); }
|
|
68
|
+
[data-ui='badge'][data-tone='warning'] { --ring-accent: color-mix(in oklab, var(--color-warning) 85%, white 15%); }
|
|
69
|
+
[data-ui='badge'][data-tone='danger'] { --ring-accent: color-mix(in oklab, var(--color-danger) 85%, white 15%); }
|
|
70
|
+
|
|
71
|
+
/* Focus-visible — gentle tokenized outline for interactive/focusable badges */
|
|
72
|
+
[data-ui='badge']:focus-visible {
|
|
73
|
+
outline: 2px solid var(--ring-accent, color-mix(in oklab, var(--color-accent) 85%, white 15%));
|
|
74
|
+
outline-offset: 2px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Variants — default tone maps to accent */
|
|
78
|
+
[data-ui='badge'][data-variant='solid'] {
|
|
79
|
+
background: var(--color-accent);
|
|
80
|
+
color: var(--color-on-accent);
|
|
81
|
+
}
|
|
82
|
+
[data-ui='badge'][data-variant='soft'] {
|
|
83
|
+
background: color-mix(in oklab, var(--color-accent) 18%, transparent);
|
|
84
|
+
color: var(--color-accent);
|
|
85
|
+
border-color: color-mix(in oklab, var(--color-accent) 38%, transparent);
|
|
86
|
+
}
|
|
87
|
+
[data-ui='badge'][data-variant='outline'] {
|
|
88
|
+
background: transparent;
|
|
89
|
+
color: var(--color-accent);
|
|
90
|
+
border-color: var(--color-accent);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Tones — override accent mapping */
|
|
94
|
+
[data-ui='badge'][data-tone='positive'][data-variant='solid'] {
|
|
95
|
+
background: var(--color-positive);
|
|
96
|
+
color: var(--color-on-accent);
|
|
97
|
+
}
|
|
98
|
+
[data-ui='badge'][data-tone='warning'][data-variant='solid'] {
|
|
99
|
+
background: var(--color-warning);
|
|
100
|
+
color: var(--color-on-accent);
|
|
101
|
+
}
|
|
102
|
+
[data-ui='badge'][data-tone='danger'][data-variant='solid'] {
|
|
103
|
+
background: var(--color-danger);
|
|
104
|
+
color: var(--color-on-accent);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
[data-ui='badge'][data-tone='positive'][data-variant='soft'] {
|
|
108
|
+
background: color-mix(in oklab, var(--color-positive) 18%, transparent);
|
|
109
|
+
color: var(--color-positive);
|
|
110
|
+
border-color: color-mix(in oklab, var(--color-positive) 38%, transparent);
|
|
111
|
+
}
|
|
112
|
+
[data-ui='badge'][data-tone='warning'][data-variant='soft'] {
|
|
113
|
+
background: color-mix(in oklab, var(--color-warning) 18%, transparent);
|
|
114
|
+
color: var(--color-warning);
|
|
115
|
+
border-color: color-mix(in oklab, var(--color-warning) 38%, transparent);
|
|
116
|
+
}
|
|
117
|
+
[data-ui='badge'][data-tone='danger'][data-variant='soft'] {
|
|
118
|
+
background: color-mix(in oklab, var(--color-danger) 18%, transparent);
|
|
119
|
+
color: var(--color-danger);
|
|
120
|
+
border-color: color-mix(in oklab, var(--color-danger) 38%, transparent);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
[data-ui='badge'][data-tone='positive'][data-variant='outline'] {
|
|
124
|
+
color: var(--color-positive);
|
|
125
|
+
border-color: var(--color-positive);
|
|
126
|
+
}
|
|
127
|
+
[data-ui='badge'][data-tone='warning'][data-variant='outline'] {
|
|
128
|
+
color: var(--color-warning);
|
|
129
|
+
border-color: var(--color-warning);
|
|
130
|
+
}
|
|
131
|
+
[data-ui='badge'][data-tone='danger'][data-variant='outline'] {
|
|
132
|
+
color: var(--color-danger);
|
|
133
|
+
border-color: var(--color-danger);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Subtle — neutral surface, gentle border; text uses neutral color token */
|
|
137
|
+
[data-ui='badge'][data-variant='subtle'] {
|
|
138
|
+
background: var(--color-fill-subtle);
|
|
139
|
+
color: var(--color-text);
|
|
140
|
+
border-color: color-mix(in oklab, var(--color-text) 12%, transparent);
|
|
141
|
+
}
|
|
142
|
+
/* Optional: interactive hover/active soften (opt‑in or semantic elements) */
|
|
143
|
+
[data-ui='badge'][data-interactive='true'],
|
|
144
|
+
button[data-ui='badge'],
|
|
145
|
+
a[data-ui='badge'] {
|
|
146
|
+
transition: filter var(--duration-snug, 160ms) var(--ease-soft, ease);
|
|
147
|
+
}
|
|
148
|
+
[data-ui='badge'][data-interactive='true']:hover,
|
|
149
|
+
button[data-ui='badge']:hover,
|
|
150
|
+
a[data-ui='badge']:hover {
|
|
151
|
+
filter: brightness(1.01);
|
|
152
|
+
}
|
|
153
|
+
[data-ui='badge'][data-interactive='true']:active,
|
|
154
|
+
button[data-ui='badge']:active,
|
|
155
|
+
a[data-ui='badge']:active {
|
|
156
|
+
filter: brightness(0.99);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@media (prefers-reduced-motion: reduce) {
|
|
160
|
+
[data-ui='badge'][data-interactive='true'],
|
|
161
|
+
button[data-ui='badge'],
|
|
162
|
+
a[data-ui='badge'] {
|
|
163
|
+
transition: none;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/* Disabled state — dim + no soften; supports native and ARIA patterns */
|
|
167
|
+
[data-ui='badge'][aria-disabled='true'],
|
|
168
|
+
button[data-ui='badge'][disabled],
|
|
169
|
+
a[data-ui='badge'][aria-disabled='true'] {
|
|
170
|
+
opacity: 0.55;
|
|
171
|
+
filter: none !important;
|
|
172
|
+
cursor: not-allowed;
|
|
173
|
+
pointer-events: none;
|
|
174
|
+
}
|
|
175
|
+
}
|
package/styles/base.css
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
Garden UI — Button skin (opt‑in)
|
|
3
|
+
Whisper: "press should feel like choosing, not forcing." 🌬️
|
|
4
|
+
|
|
5
|
+
Purpose
|
|
6
|
+
• Selector-only skin for the headless Button primitive.
|
|
7
|
+
• Uses Garden tokens; no hardcoded hex.
|
|
8
|
+
|
|
9
|
+
Data API
|
|
10
|
+
• [data-ui='button'] — root
|
|
11
|
+
• [data-variant='solid|outline|ghost|subtle']
|
|
12
|
+
• [data-tone='default|accent|positive|warning|danger']
|
|
13
|
+
• [data-density='snug|cozy'] — spacing/size
|
|
14
|
+
• [data-state='idle|loading|disabled'] (plus [disabled])
|
|
15
|
+
• [data-loading-mode='inline|blocking'] when loading
|
|
16
|
+
• [data-slot='icon leading|icon trailing|spinner|overlay'] for adornments, progress & blocking
|
|
17
|
+
|
|
18
|
+
A11y & Motion
|
|
19
|
+
• Focus-visible ring uses --ring-accent (fallback mixes accent against white).
|
|
20
|
+
• Hover lift is tiny (0.5px); no animations here, so reduced motion is implicit.
|
|
21
|
+
• Controls remain tabbable and legible across light/dark themes.
|
|
22
|
+
• Icon slots are presentational; TS marks them aria-hidden so screen readers read the label once.
|
|
23
|
+
|
|
24
|
+
Theming notes
|
|
25
|
+
• Surface/background: --color-elev; text: --color-text; borders: --color-border.
|
|
26
|
+
• Accents: --color-accent|positive|warning|danger; on-accent: --color-on-accent.
|
|
27
|
+
• Radius uses --radius-pill.
|
|
28
|
+
|
|
29
|
+
Future ideas
|
|
30
|
+
• Roomy density; progress stripe for loading; pressed-depth tokens.
|
|
31
|
+
──────────────────────────────────────────────────────────── */
|
|
32
|
+
|
|
33
|
+
@layer components {
|
|
34
|
+
/* Base */
|
|
35
|
+
[data-ui='button'] {
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
gap: 0.5rem;
|
|
40
|
+
border-radius: var(--radius-pill);
|
|
41
|
+
border: 1px solid var(--color-border);
|
|
42
|
+
font-weight: 500;
|
|
43
|
+
line-height: 1;
|
|
44
|
+
transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.05s ease;
|
|
45
|
+
-webkit-tap-highlight-color: transparent;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Density */
|
|
50
|
+
[data-ui='button'][data-density='snug'] {
|
|
51
|
+
padding: 0.375rem 0.75rem;
|
|
52
|
+
font-size: var(--text-sm);
|
|
53
|
+
}
|
|
54
|
+
[data-ui='button'][data-density='cozy'] {
|
|
55
|
+
padding: 0.5rem 0.875rem;
|
|
56
|
+
font-size: var(--text-sm);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Parts */
|
|
60
|
+
[data-ui='button'] [data-slot~='icon'] {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
}
|
|
63
|
+
[data-ui='button'] [data-slot='icon leading'] {
|
|
64
|
+
margin-inline-end: 0.25rem;
|
|
65
|
+
}
|
|
66
|
+
[data-ui='button'] [data-slot='icon trailing'] {
|
|
67
|
+
margin-inline-start: 0.25rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Tone → focus ring color variable (used by :focus-visible) */
|
|
71
|
+
[data-ui='button'][data-tone='default'] { --ring-accent: color-mix(in oklab, var(--color-text) 80%, white 20%); }
|
|
72
|
+
[data-ui='button'][data-tone='accent'] { --ring-accent: color-mix(in oklab, var(--color-accent) 85%, white 15%); }
|
|
73
|
+
[data-ui='button'][data-tone='positive']{ --ring-accent: color-mix(in oklab, var(--color-positive) 85%, white 15%); }
|
|
74
|
+
[data-ui='button'][data-tone='warning'] { --ring-accent: color-mix(in oklab, var(--color-warning) 85%, white 15%); }
|
|
75
|
+
[data-ui='button'][data-tone='danger'] { --ring-accent: color-mix(in oklab, var(--color-danger) 85%, white 15%); }
|
|
76
|
+
|
|
77
|
+
/* Spinner — inline, token-aware, respects reduced motion */
|
|
78
|
+
[data-ui='button'] [data-slot='spinner'] {
|
|
79
|
+
display: inline-block;
|
|
80
|
+
width: 0.875rem; /* 14px */
|
|
81
|
+
height: 0.875rem; /* 14px */
|
|
82
|
+
box-sizing: border-box; /* ensures width/height include border (fixes box-model lint) */
|
|
83
|
+
margin-inline-start: 0.25rem;
|
|
84
|
+
border-radius: 9999px;
|
|
85
|
+
border: 2px solid currentColor;
|
|
86
|
+
border-top-color: transparent; /* creates the arc */
|
|
87
|
+
animation: ui-spin var(--duration-snug, 0.9s) linear infinite;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@keyframes ui-spin {
|
|
91
|
+
to {
|
|
92
|
+
transform: rotate(360deg);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@media (prefers-reduced-motion: reduce) {
|
|
97
|
+
[data-ui='button'] [data-slot='spinner'] {
|
|
98
|
+
animation: none;
|
|
99
|
+
/* show a static dot when motion is reduced */
|
|
100
|
+
border-color: currentColor;
|
|
101
|
+
border-top-color: currentColor;
|
|
102
|
+
opacity: 0.8;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Blocking overlay — covers the button and centers a spinner */
|
|
107
|
+
[data-ui='button'][data-loading-mode='blocking'] {
|
|
108
|
+
position: relative; /* create containing block for overlay */
|
|
109
|
+
}
|
|
110
|
+
[data-ui='button'][data-loading-mode='blocking'] [data-slot='overlay'] {
|
|
111
|
+
position: absolute;
|
|
112
|
+
inset: -1px; /* cover border too */
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
border-radius: inherit;
|
|
117
|
+
pointer-events: none; /* overlay does not steal clicks */
|
|
118
|
+
/* Gentle dim across variants (token-mixed) */
|
|
119
|
+
background: var(--button-overlay-dim, color-mix(in oklab, var(--color-elev) 82%, var(--color-text) 18%));
|
|
120
|
+
}
|
|
121
|
+
[data-ui='button'][data-loading-mode='blocking'] [data-slot='overlay'] [data-slot='spinner'] {
|
|
122
|
+
width: 1rem; /* 16px */
|
|
123
|
+
height: 1rem; /* 16px */
|
|
124
|
+
box-sizing: border-box; /* ensures width/height include border (fixes box-model lint) */
|
|
125
|
+
margin: 0; /* centered, no extra gap */
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Variants — SOLID (filled surface, on-accent text for non-default tones) */
|
|
129
|
+
[data-ui='button'][data-variant='solid'] {
|
|
130
|
+
border-color: transparent;
|
|
131
|
+
color: var(--color-on-accent);
|
|
132
|
+
}
|
|
133
|
+
[data-ui='button'][data-variant='solid'][data-tone='default'] {
|
|
134
|
+
background: var(--color-elev);
|
|
135
|
+
color: var(--color-text);
|
|
136
|
+
border-color: var(--color-border);
|
|
137
|
+
}
|
|
138
|
+
[data-ui='button'][data-variant='solid'][data-tone='accent'] {
|
|
139
|
+
background: var(--color-accent);
|
|
140
|
+
}
|
|
141
|
+
[data-ui='button'][data-variant='solid'][data-tone='positive'] {
|
|
142
|
+
background: var(--color-positive);
|
|
143
|
+
}
|
|
144
|
+
[data-ui='button'][data-variant='solid'][data-tone='warning'] {
|
|
145
|
+
background: var(--color-warning);
|
|
146
|
+
color: var(--color-on-accent);
|
|
147
|
+
}
|
|
148
|
+
[data-ui='button'][data-variant='solid'][data-tone='danger'] {
|
|
149
|
+
background: var(--color-danger);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Variants — OUTLINE (transparent fill, colored border/text) */
|
|
153
|
+
[data-ui='button'][data-variant='outline'] {
|
|
154
|
+
background: transparent;
|
|
155
|
+
}
|
|
156
|
+
[data-ui='button'][data-variant='outline'][data-tone='default'] {
|
|
157
|
+
color: var(--color-text);
|
|
158
|
+
}
|
|
159
|
+
[data-ui='button'][data-variant='outline'][data-tone='accent'] {
|
|
160
|
+
color: var(--color-accent);
|
|
161
|
+
border-color: var(--color-accent);
|
|
162
|
+
}
|
|
163
|
+
[data-ui='button'][data-variant='outline'][data-tone='positive'] {
|
|
164
|
+
color: var(--color-positive);
|
|
165
|
+
border-color: var(--color-positive);
|
|
166
|
+
}
|
|
167
|
+
[data-ui='button'][data-variant='outline'][data-tone='warning'] {
|
|
168
|
+
color: var(--color-warning);
|
|
169
|
+
border-color: var(--color-warning);
|
|
170
|
+
}
|
|
171
|
+
[data-ui='button'][data-variant='outline'][data-tone='danger'] {
|
|
172
|
+
color: var(--color-danger);
|
|
173
|
+
border-color: var(--color-danger);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Variants — GHOST (no border, transparent fill; subtle hover wash on tone) */
|
|
177
|
+
[data-ui='button'][data-variant='ghost'] {
|
|
178
|
+
background: transparent;
|
|
179
|
+
border-color: transparent;
|
|
180
|
+
}
|
|
181
|
+
[data-ui='button'][data-variant='ghost'][data-tone='default'] {
|
|
182
|
+
color: var(--color-text);
|
|
183
|
+
}
|
|
184
|
+
[data-ui='button'][data-variant='ghost'][data-tone='accent'] {
|
|
185
|
+
color: var(--color-accent);
|
|
186
|
+
}
|
|
187
|
+
[data-ui='button'][data-variant='ghost'][data-tone='positive'] {
|
|
188
|
+
color: var(--color-positive);
|
|
189
|
+
}
|
|
190
|
+
[data-ui='button'][data-variant='ghost'][data-tone='warning'] {
|
|
191
|
+
color: var(--color-warning);
|
|
192
|
+
}
|
|
193
|
+
[data-ui='button'][data-variant='ghost'][data-tone='danger'] {
|
|
194
|
+
color: var(--color-danger);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Variants — SUBTLE (neutral surface, gentle border; good for low-stakes actions) */
|
|
198
|
+
[data-ui='button'][data-variant='subtle'] {
|
|
199
|
+
background: var(--color-fill-subtle);
|
|
200
|
+
color: var(--color-text);
|
|
201
|
+
border-color: color-mix(in oklab, var(--color-text) 12%, transparent);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Hover — slight lift + color wash (whisper: "make intent feel light") */
|
|
205
|
+
@media (hover: hover) {
|
|
206
|
+
[data-ui='button']:not([disabled])[data-state='idle']:hover {
|
|
207
|
+
transform: translateY(-0.5px);
|
|
208
|
+
}
|
|
209
|
+
[data-ui='button'][data-variant='outline'][data-tone='accent']:hover {
|
|
210
|
+
background: color-mix(in oklab, var(--color-accent) 12%, transparent);
|
|
211
|
+
}
|
|
212
|
+
[data-ui='button'][data-variant='outline'][data-tone='positive']:hover {
|
|
213
|
+
background: color-mix(in oklab, var(--color-positive) 12%, transparent);
|
|
214
|
+
}
|
|
215
|
+
[data-ui='button'][data-variant='outline'][data-tone='warning']:hover {
|
|
216
|
+
background: color-mix(in oklab, var(--color-warning) 12%, transparent);
|
|
217
|
+
}
|
|
218
|
+
[data-ui='button'][data-variant='outline'][data-tone='danger']:hover {
|
|
219
|
+
background: color-mix(in oklab, var(--color-danger) 12%, transparent);
|
|
220
|
+
}
|
|
221
|
+
[data-ui='button'][data-variant='ghost'][data-tone='accent']:hover {
|
|
222
|
+
background: color-mix(in oklab, var(--color-accent) 10%, transparent);
|
|
223
|
+
}
|
|
224
|
+
[data-ui='button'][data-variant='ghost'][data-tone='positive']:hover {
|
|
225
|
+
background: color-mix(in oklab, var(--color-positive) 10%, transparent);
|
|
226
|
+
}
|
|
227
|
+
[data-ui='button'][data-variant='ghost'][data-tone='warning']:hover {
|
|
228
|
+
background: color-mix(in oklab, var(--color-warning) 10%, transparent);
|
|
229
|
+
}
|
|
230
|
+
[data-ui='button'][data-variant='ghost'][data-tone='danger']:hover {
|
|
231
|
+
background: color-mix(in oklab, var(--color-danger) 10%, transparent);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Active — cancel hover lift, add gentle press */
|
|
236
|
+
[data-ui='button']:not([disabled])[data-state='idle']:active {
|
|
237
|
+
transform: translateY(0);
|
|
238
|
+
filter: brightness(0.98);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* Focus — :focus-visible ring uses --ring-accent (fallback blends accent for contrast) */
|
|
242
|
+
[data-ui='button']:focus-visible {
|
|
243
|
+
outline: 2px solid var(--ring-accent, color-mix(in oklab, var(--color-accent) 85%, white 15%));
|
|
244
|
+
outline-offset: 2px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* States — loading (progress), disabled (no pointer events to avoid double submits) */
|
|
248
|
+
[data-ui='button'][data-state='loading'] {
|
|
249
|
+
cursor: progress;
|
|
250
|
+
}
|
|
251
|
+
[data-ui='button'][data-state='disabled'],
|
|
252
|
+
[data-ui='button'][disabled] {
|
|
253
|
+
opacity: 0.6;
|
|
254
|
+
cursor: not-allowed;
|
|
255
|
+
pointer-events: none;
|
|
256
|
+
}
|
|
257
|
+
}
|
package/styles/card.css
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
Garden UI — Card skin (opt‑in)
|
|
3
|
+
Whisper: "surfaces should invite, not insist." 🌬️
|
|
4
|
+
|
|
5
|
+
Purpose
|
|
6
|
+
• Selector-only skin for the headless Card primitive.
|
|
7
|
+
• Driven by Garden tokens; no hardcoded hex.
|
|
8
|
+
|
|
9
|
+
Data API
|
|
10
|
+
• [data-ui='card'] — root
|
|
11
|
+
• [data-variant='plain|elev|glow|outline|inset|ghost'] — depth/outline semantics
|
|
12
|
+
• [data-padding='none|sm|md|lg'] — inner spacing
|
|
13
|
+
• [data-tone='subtle|accent|positive|warning|danger'] — ring/outline tone (optional)
|
|
14
|
+
• [data-depth='inherit|0|1|2|3'] — shadow depth passthrough
|
|
15
|
+
|
|
16
|
+
A11y
|
|
17
|
+
• Card is presentational by default; semantics come from the element you render.
|
|
18
|
+
• If interactive (link/button), ensure a visible focus style (see [data-interactive]).
|
|
19
|
+
|
|
20
|
+
Notes
|
|
21
|
+
• Base uses --color-elev with border + depth token; 'plain' swaps to --color-surface.
|
|
22
|
+
• 'glow' draws an accent outline using --ring-accent (with safe fallback).
|
|
23
|
+
• Keep motion subtle; any reveal/hover effects belong in the app layer.
|
|
24
|
+
• Tokens: --surface, --elev, --text, --border, --sheet-radius, --shadow-depth-*, --ring-accent (optional).
|
|
25
|
+
──────────────────────────────────────────────────────────── */
|
|
26
|
+
@layer components {
|
|
27
|
+
/* Base — tokenized surface, border, and depth */
|
|
28
|
+
[data-ui='card'] {
|
|
29
|
+
border-radius: var(--sheet-radius, var(--radius-2xl));
|
|
30
|
+
border: 1px solid var(--border);
|
|
31
|
+
background: var(--elev);
|
|
32
|
+
box-shadow: var(--shadow-depth-2, 0 6px 18px -6px rgb(0 0 0 / 0.2));
|
|
33
|
+
color: var(--text);
|
|
34
|
+
position: relative; /* allow ::before grain overlay */
|
|
35
|
+
isolation: isolate; /* keep overlays local */
|
|
36
|
+
overflow: clip; /* prevent bleed on rounded corners */
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Variants — plain / elev / glow / outline / inset / ghost */
|
|
40
|
+
[data-ui='card'][data-variant='plain'] {
|
|
41
|
+
background: var(--surface);
|
|
42
|
+
box-shadow: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[data-ui='card'][data-variant='elev'] {
|
|
46
|
+
/* same as base; kept for clarity */
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[data-ui='card'][data-variant='glow'] {
|
|
50
|
+
outline: 2px solid var(--ring-accent, color-mix(in oklab, var(--color-accent) 85%, white 15%));
|
|
51
|
+
outline-offset: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* outline — clear surface with crisp border, no shadow */
|
|
55
|
+
[data-ui='card'][data-variant='outline'] {
|
|
56
|
+
background: transparent;
|
|
57
|
+
border-color: color-mix(in oklab, var(--border) 75%, transparent);
|
|
58
|
+
box-shadow: none;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* inset — “well” feel via subtle inner lines */
|
|
62
|
+
[data-ui='card'][data-variant='inset'] {
|
|
63
|
+
background: var(--surface);
|
|
64
|
+
/* remove outer depth, create a gentle inner well */
|
|
65
|
+
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.06), inset 0 -1px 0 rgb(0 0 0 / 0.08);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ghost — barely-there surface; awakens on hover/focus if interactive */
|
|
69
|
+
[data-ui='card'][data-variant='ghost'] {
|
|
70
|
+
background: transparent;
|
|
71
|
+
border-color: transparent;
|
|
72
|
+
box-shadow: none;
|
|
73
|
+
}
|
|
74
|
+
[data-ui='card'][data-variant='ghost'][data-interactive='true']:hover,
|
|
75
|
+
[data-ui='card'][data-variant='ghost'][data-interactive='true']:focus-visible {
|
|
76
|
+
background: color-mix(in oklab, var(--surface) 65%, transparent);
|
|
77
|
+
border-color: color-mix(in oklab, var(--border) 85%, transparent);
|
|
78
|
+
box-shadow: var(--shadow-depth-1, 0 4px 14px -6px rgb(0 0 0 / 0.22));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Ambient grain overlay — scales with --grain-strength (0..1)
|
|
82
|
+
Uses an inline SVG fractal noise tile; blended via multiply.
|
|
83
|
+
Disabled automatically when --grain-strength = 0.
|
|
84
|
+
*/
|
|
85
|
+
[data-ui='card']::before {
|
|
86
|
+
content: '';
|
|
87
|
+
position: absolute;
|
|
88
|
+
inset: 0;
|
|
89
|
+
border-radius: inherit;
|
|
90
|
+
pointer-events: none;
|
|
91
|
+
z-index: 0; /* behind content (before → content → after) */
|
|
92
|
+
opacity: clamp(0, var(--grain-strength, 0), 0.35);
|
|
93
|
+
/* size controlled by --grain-size (px), defaults to 140px */
|
|
94
|
+
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='1'/></svg>");
|
|
95
|
+
background-size: var(--grain-size, 140px) var(--grain-size, 140px);
|
|
96
|
+
background-repeat: repeat;
|
|
97
|
+
mix-blend-mode: multiply;
|
|
98
|
+
transition: opacity var(--duration-snug, 160ms) var(--ease-soft, ease);
|
|
99
|
+
}
|
|
100
|
+
/* Slight lift on hover for interactive cards */
|
|
101
|
+
[data-ui='card'][data-interactive='true']:hover::before,
|
|
102
|
+
[data-ui='card'][data-interactive='true']:focus-visible::before {
|
|
103
|
+
opacity: clamp(0, calc(var(--grain-strength, 0) * 1.15), 0.42);
|
|
104
|
+
}
|
|
105
|
+
/* Skip grain for ghost variant (transparent surface) */
|
|
106
|
+
[data-ui='card'][data-variant='ghost']::before {
|
|
107
|
+
opacity: 0 !important;
|
|
108
|
+
}
|
|
109
|
+
/* Also skip grain for outline variant (transparent surface) */
|
|
110
|
+
[data-ui='card'][data-variant='outline']::before {
|
|
111
|
+
opacity: 0 !important;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Padding scale — none | sm | md | lg */
|
|
115
|
+
[data-ui='card'][data-padding='none'] {
|
|
116
|
+
padding: 0;
|
|
117
|
+
}
|
|
118
|
+
[data-ui='card'][data-padding='sm'] {
|
|
119
|
+
padding: 0.75rem;
|
|
120
|
+
}
|
|
121
|
+
[data-ui='card'][data-padding='md'] {
|
|
122
|
+
padding: 1rem;
|
|
123
|
+
}
|
|
124
|
+
[data-ui='card'][data-padding='lg'] {
|
|
125
|
+
padding: 1.5rem;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Depth passthrough — opt-in shadow control (syncs with Pad depth) */
|
|
129
|
+
[data-ui='card']:not([data-variant='plain'])[data-depth='inherit'] {
|
|
130
|
+
/* Use container-provided depth variable when available */
|
|
131
|
+
box-shadow: var(--depth-shadow, var(--shadow-depth-2));
|
|
132
|
+
}
|
|
133
|
+
[data-ui='card']:not([data-variant='plain'])[data-depth='0'] {
|
|
134
|
+
box-shadow: var(--shadow-depth-0);
|
|
135
|
+
}
|
|
136
|
+
[data-ui='card']:not([data-variant='plain'])[data-depth='1'] {
|
|
137
|
+
box-shadow: var(--shadow-depth-1);
|
|
138
|
+
}
|
|
139
|
+
[data-ui='card']:not([data-variant='plain'])[data-depth='2'] {
|
|
140
|
+
box-shadow: var(--shadow-depth-2);
|
|
141
|
+
}
|
|
142
|
+
[data-ui='card']:not([data-variant='plain'])[data-depth='3'] {
|
|
143
|
+
box-shadow: var(--shadow-depth-3);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Tone → focus/outline ring color variable (used by :focus-visible & glow) */
|
|
147
|
+
[data-ui='card'][data-tone='subtle'] {
|
|
148
|
+
--ring-accent: color-mix(in oklab, var(--color-text) 80%, white 20%);
|
|
149
|
+
}
|
|
150
|
+
[data-ui='card'][data-tone='accent'] {
|
|
151
|
+
--ring-accent: color-mix(in oklab, var(--color-accent) 85%, white 15%);
|
|
152
|
+
}
|
|
153
|
+
[data-ui='card'][data-tone='positive'] {
|
|
154
|
+
--ring-accent: color-mix(in oklab, var(--color-positive) 85%, white 15%);
|
|
155
|
+
}
|
|
156
|
+
[data-ui='card'][data-tone='warning'] {
|
|
157
|
+
--ring-accent: color-mix(in oklab, var(--color-warning) 85%, white 15%);
|
|
158
|
+
}
|
|
159
|
+
[data-ui='card'][data-tone='danger'] {
|
|
160
|
+
--ring-accent: color-mix(in oklab, var(--color-danger) 85%, white 15%);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Optional: interactive hover/active soften (opt-in via data-interactive) */
|
|
164
|
+
[data-ui='card'][data-interactive='true'] {
|
|
165
|
+
transition: filter var(--duration-snug, 160ms) var(--ease-soft, ease), box-shadow var(--duration-snug, 160ms) var(--ease-soft, ease);
|
|
166
|
+
}
|
|
167
|
+
[data-ui='card'][data-interactive='true']:hover {
|
|
168
|
+
filter: brightness(1.02);
|
|
169
|
+
}
|
|
170
|
+
[data-ui='card'][data-interactive='true']:active {
|
|
171
|
+
filter: brightness(0.98);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@media (prefers-reduced-motion: reduce) {
|
|
175
|
+
[data-ui='card'][data-interactive='true'] {
|
|
176
|
+
transition: none;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Optional: interactive focus (opt-in via data-interactive) */
|
|
181
|
+
[data-ui='card'][data-interactive='true']:focus-visible {
|
|
182
|
+
outline: 2px solid var(--ring-accent, color-mix(in oklab, var(--color-accent) 85%, white 15%));
|
|
183
|
+
outline-offset: 2px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Disabled — dim + block interaction (opt-in interactive only) */
|
|
187
|
+
[data-ui='card'][data-interactive='true'][aria-disabled='true'],
|
|
188
|
+
a[data-ui='card'][aria-disabled='true'],
|
|
189
|
+
button[data-ui='card'][disabled] {
|
|
190
|
+
opacity: 0.55;
|
|
191
|
+
filter: none !important;
|
|
192
|
+
cursor: not-allowed;
|
|
193
|
+
pointer-events: none;
|
|
194
|
+
}
|
|
195
|
+
}
|