@threadlabs/looma 0.7.2 → 0.7.4
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/components/ui-badge/ui-badge.html +20 -0
- package/components/ui-button/ui-button.html +41 -16
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/styles/ui-badge.css +22 -0
- package/styles/ui-button.css +27 -17
- package/vanilla/UiBadge.d.ts +1 -0
- package/vanilla/UiBadge.js +4 -2
- package/vanilla/UiButton.d.ts +5 -0
- package/vanilla/UiButton.js +8 -6
- package/vue/UiBadge.d.ts +2 -0
- package/vue/UiBadge.vue +29 -0
- package/vue/UiButton.d.ts +9 -2
- package/vue/UiButton.vue +51 -19
- package/vue/chunks/UiBadge.js +7 -2
- package/vue/chunks/UiButton.js +52 -10
- package/vue/components.css +81 -50
package/package.json
CHANGED
package/styles/ui-badge.css
CHANGED
|
@@ -95,6 +95,24 @@
|
|
|
95
95
|
--_badge-text: var(--ui-info-subtle-text, var(--ui-info-solid));
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/* A tag is flat at the start and points at the end. The point is cut from the box, so it has no
|
|
99
|
+
outline to follow: the edge is the fill. The end padding grows by the point's depth so the
|
|
100
|
+
label never runs into it. */
|
|
101
|
+
:scope[data-ui-badge-state~="shape=tag"] {
|
|
102
|
+
--_badge-point: var(--ui-badge-point, var(--ui-space-2));
|
|
103
|
+
|
|
104
|
+
border-color: transparent;
|
|
105
|
+
border-radius: 0;
|
|
106
|
+
padding-inline-end: calc(var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point));
|
|
107
|
+
clip-path: polygon(0 0, calc(100% - var(--_badge-point)) 0, 100% 50%, calc(100% - var(--_badge-point)) 100%, 0 100%);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:scope[data-ui-badge-state~="shape=tag"]:dir(rtl) {
|
|
111
|
+
padding-inline-end: var(--ui-badge-padding-inline, var(--ui-space-2));
|
|
112
|
+
padding-inline-start: calc(var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point));
|
|
113
|
+
clip-path: polygon(var(--_badge-point) 0, 100% 0, 100% 100%, var(--_badge-point) 100%, 0 50%);
|
|
114
|
+
}
|
|
115
|
+
|
|
98
116
|
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
99
117
|
:scope[hidden] {
|
|
100
118
|
display: none;
|
|
@@ -107,5 +125,9 @@
|
|
|
107
125
|
@supports (text-box-trim: trim-both) {
|
|
108
126
|
}
|
|
109
127
|
|
|
128
|
+
/* A tag is flat at the start and points at the end. The point is cut from the box, so it has no
|
|
129
|
+
outline to follow: the edge is the fill. The end padding grows by the point's depth so the
|
|
130
|
+
label never runs into it. */
|
|
131
|
+
|
|
110
132
|
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
111
133
|
}
|
package/styles/ui-button.css
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
color: var(--ui-button-text, var(--_tone-text));
|
|
42
42
|
font: inherit;
|
|
43
43
|
font-weight: var(--ui-font-medium);
|
|
44
|
+
text-decoration: none;
|
|
44
45
|
line-height: var(--ui-line-height-tight);
|
|
45
46
|
padding: var(--ui-space-2) var(--ui-space-3);
|
|
46
47
|
background-image: var(--ui-raised-highlight);
|
|
@@ -73,13 +74,15 @@
|
|
|
73
74
|
font-size: 1rem;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
:
|
|
77
|
+
/* A link is never :enabled, so interactive states test "not unavailable" instead: for a button that
|
|
78
|
+
is exactly :enabled, at the same specificity, and it also matches a link that can be followed. */
|
|
79
|
+
:scope:hover:not(:disabled, [aria-disabled="true"]) {
|
|
77
80
|
border-color: var(--ui-button-hover-border, var(--_tone-hover));
|
|
78
81
|
background-color: var(--ui-button-hover-surface, oklch(from var(--_tone) l c h / 12%));
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
/* Pressed reads as pressed in: the highlight goes, and the shadow moves inside. */
|
|
82
|
-
:scope:active:
|
|
85
|
+
:scope:active:not(:disabled, [aria-disabled="true"]) {
|
|
83
86
|
background-color: var(--ui-button-active-surface, oklch(from var(--_tone) l c h / 18%));
|
|
84
87
|
background-image: none;
|
|
85
88
|
box-shadow: var(--ui-pressed);
|
|
@@ -92,12 +95,12 @@
|
|
|
92
95
|
box-shadow: var(--ui-button-solid-shadow, var(--ui-raised));
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
:is(:scope[data-ui-button-state~="variant=solid"], :scope[data-ui-button-state~="variant=danger"]):hover:
|
|
98
|
+
:is(:scope[data-ui-button-state~="variant=solid"], :scope[data-ui-button-state~="variant=danger"]):hover:not(:disabled, [aria-disabled="true"]) {
|
|
96
99
|
background-color: var(--_tone-hover);
|
|
97
100
|
border-color: var(--_tone-hover);
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
:is(:scope[data-ui-button-state~="variant=solid"], :scope[data-ui-button-state~="variant=danger"]):active:
|
|
103
|
+
:is(:scope[data-ui-button-state~="variant=solid"], :scope[data-ui-button-state~="variant=danger"]):active:not(:disabled, [aria-disabled="true"]) {
|
|
101
104
|
background-color: var(--_tone-active);
|
|
102
105
|
border-color: var(--_tone-active);
|
|
103
106
|
background-image: none;
|
|
@@ -154,12 +157,12 @@
|
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
/* Tints derive from the text colour, not a surface token, so hover and press show on any background. */
|
|
157
|
-
:scope[data-ui-button-state~="variant=ghost"]:hover:
|
|
160
|
+
:scope[data-ui-button-state~="variant=ghost"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
158
161
|
background-color: var(--ui-button-ghost-hover-surface, oklch(from var(--_tone) l c h / 10%));
|
|
159
162
|
border-color: transparent;
|
|
160
163
|
}
|
|
161
164
|
|
|
162
|
-
:scope[data-ui-button-state~="variant=ghost"]:active:
|
|
165
|
+
:scope[data-ui-button-state~="variant=ghost"]:active:not(:disabled, [aria-disabled="true"]) {
|
|
163
166
|
background-color: var(--ui-button-ghost-active-surface, oklch(from var(--_tone) l c h / 16%));
|
|
164
167
|
border-color: transparent;
|
|
165
168
|
box-shadow: var(--ui-pressed);
|
|
@@ -180,10 +183,10 @@
|
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
/* Disabled drops the tone rather than the shape: every variant keeps its own structure, so a
|
|
183
|
-
disabled outline is still an outline and a disabled ghost
|
|
186
|
+
disabled outline is still an outline and a disabled ghost still has no edge. Intent goes — an
|
|
184
187
|
unavailable action says "unavailable", not "unavailable, and destructive" — and so do the
|
|
185
188
|
highlight and shadow, because nothing that cannot be pressed should look raised. */
|
|
186
|
-
:scope:disabled {
|
|
189
|
+
:scope:is(:disabled, [aria-disabled="true"]) {
|
|
187
190
|
/* Derived from the tone itself, not from a separate disabled palette: faded toward the
|
|
188
191
|
page, which desaturates it at the same time. Toward the page rather than toward white,
|
|
189
192
|
so a dark theme dims where a light one lightens — the same rule, both schemes. */
|
|
@@ -200,14 +203,16 @@
|
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
/* A filled button keeps its fill, faded further so its text stays legible on it. */
|
|
203
|
-
:is(:scope[data-ui-button-state~="variant=solid"], :scope[data-ui-button-state~="variant=danger"]):disabled {
|
|
206
|
+
:is(:scope[data-ui-button-state~="variant=solid"], :scope[data-ui-button-state~="variant=danger"]):is(:disabled, [aria-disabled="true"]) {
|
|
204
207
|
--_tone: color-mix(in oklab, var(--_hue) 34%, var(--ui-surface));
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
/* Ghost has no hover to fall back on, so disabled is the one state it has to state outright:
|
|
208
|
-
it takes a surface
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
it takes a surface rather than reading as plain text. The surface is a wash of the faded tone,
|
|
212
|
+
the way hover washes the live one, so it stays light in every tone. An opaque mix toward the
|
|
213
|
+
hue came out a mid-grey slab for neutral, whose hue is the ink: louder than the enabled button. */
|
|
214
|
+
:scope[data-ui-button-state~="variant=ghost"]:is(:disabled, [aria-disabled="true"]) {
|
|
215
|
+
background-color: oklch(from var(--_tone) l c h / 14%);
|
|
211
216
|
border-color: transparent;
|
|
212
217
|
}
|
|
213
218
|
|
|
@@ -236,19 +241,19 @@
|
|
|
236
241
|
transition: color var(--ui-motion-fast) var(--ui-motion-ease), text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
237
242
|
}
|
|
238
243
|
|
|
239
|
-
:scope[data-ui-button-state~="variant=link"]:hover:
|
|
244
|
+
:scope[data-ui-button-state~="variant=link"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
240
245
|
color: var(--ui-button-link-hover-text, var(--ui-text-primary));
|
|
241
246
|
text-decoration-color: currentColor;
|
|
242
247
|
background: none;
|
|
243
248
|
transform: none;
|
|
244
249
|
}
|
|
245
250
|
|
|
246
|
-
:scope[data-ui-button-state~="variant=link"]:active:
|
|
251
|
+
:scope[data-ui-button-state~="variant=link"]:active:not(:disabled, [aria-disabled="true"]) {
|
|
247
252
|
background: none;
|
|
248
253
|
transform: none;
|
|
249
254
|
}
|
|
250
255
|
|
|
251
|
-
:scope[data-ui-button-state~="variant=link"]:disabled {
|
|
256
|
+
:scope[data-ui-button-state~="variant=link"]:is(:disabled, [aria-disabled="true"]) {
|
|
252
257
|
background: none;
|
|
253
258
|
border-color: transparent;
|
|
254
259
|
color: var(--ui-button-disabled-text, var(--ui-disabled-text));
|
|
@@ -270,6 +275,9 @@
|
|
|
270
275
|
@supports (text-box-trim: trim-both) {
|
|
271
276
|
}
|
|
272
277
|
|
|
278
|
+
/* A link is never :enabled, so interactive states test "not unavailable" instead: for a button that
|
|
279
|
+
is exactly :enabled, at the same specificity, and it also matches a link that can be followed. */
|
|
280
|
+
|
|
273
281
|
/* Pressed reads as pressed in: the highlight goes, and the shadow moves inside. */
|
|
274
282
|
|
|
275
283
|
/* Tone is the colour, variant is the volume. Every pairing works: a danger outline is a
|
|
@@ -280,14 +288,16 @@
|
|
|
280
288
|
/* A soft halo that transitions in with the other box-shadow changes, layered on the raised shadow. */
|
|
281
289
|
|
|
282
290
|
/* Disabled drops the tone rather than the shape: every variant keeps its own structure, so a
|
|
283
|
-
disabled outline is still an outline and a disabled ghost
|
|
291
|
+
disabled outline is still an outline and a disabled ghost still has no edge. Intent goes — an
|
|
284
292
|
unavailable action says "unavailable", not "unavailable, and destructive" — and so do the
|
|
285
293
|
highlight and shadow, because nothing that cannot be pressed should look raised. */
|
|
286
294
|
|
|
287
295
|
/* A filled button keeps its fill, faded further so its text stays legible on it. */
|
|
288
296
|
|
|
289
297
|
/* Ghost has no hover to fall back on, so disabled is the one state it has to state outright:
|
|
290
|
-
it takes a surface
|
|
298
|
+
it takes a surface rather than reading as plain text. The surface is a wash of the faded tone,
|
|
299
|
+
the way hover washes the live one, so it stays light in every tone. An opaque mix toward the
|
|
300
|
+
hue came out a mid-grey slab for neutral, whose hue is the ink: louder than the enabled button. */
|
|
291
301
|
|
|
292
302
|
@media (pointer: coarse) {
|
|
293
303
|
}
|
package/vanilla/UiBadge.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface UiBadgeElement extends HTMLSpanElement {
|
|
|
6
6
|
removeEventListener<K extends keyof UiBadgeEventMap>(type: K, listener: (this: UiBadgeElement, event: UiBadgeEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
|
|
7
7
|
}
|
|
8
8
|
export interface UiBadgeProps {
|
|
9
|
+
shape?: "pill" | "tag" | null;
|
|
9
10
|
tone?: "neutral" | "accent" | "info" | "success" | "warning" | "danger" | null;
|
|
10
11
|
variant?: "solid" | "subtle" | null;
|
|
11
12
|
attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
|
package/vanilla/UiBadge.js
CHANGED
|
@@ -5,8 +5,9 @@ import "../styles/ui-badge.css";
|
|
|
5
5
|
export function createUiBadge(options = {}) {
|
|
6
6
|
const { attributes = {}, children = [], slots = {}, as, ...componentProps } = options;
|
|
7
7
|
const projected = [];
|
|
8
|
-
const prop0 = componentProps["
|
|
9
|
-
const prop1 = componentProps["
|
|
8
|
+
const prop0 = componentProps["shape"] === undefined ? "pill" : componentProps["shape"];
|
|
9
|
+
const prop1 = componentProps["tone"] === undefined ? "neutral" : componentProps["tone"];
|
|
10
|
+
const prop2 = componentProps["variant"] === undefined ? "subtle" : componentProps["variant"];
|
|
10
11
|
const element = document.createElement("span");
|
|
11
12
|
for (const [name, value] of Object.entries(attributes)) {
|
|
12
13
|
if (value === null || value === undefined || value === false) continue;
|
|
@@ -22,6 +23,7 @@ export function createUiBadge(options = {}) {
|
|
|
22
23
|
} else {
|
|
23
24
|
}
|
|
24
25
|
manageGeneratedProps(element, [
|
|
26
|
+
{ name: "shape", attribute: "data-shape", value: componentProps["shape"], default: "pill", type: ["pill","tag"], required: false },
|
|
25
27
|
{ name: "tone", attribute: "data-tone", value: componentProps["tone"], default: "neutral", type: ["neutral","accent","info","success","warning","danger"], required: false },
|
|
26
28
|
{ name: "variant", attribute: "data-variant", value: componentProps["variant"], default: "subtle", type: ["solid","subtle"], required: false },
|
|
27
29
|
]);
|
package/vanilla/UiButton.d.ts
CHANGED
|
@@ -8,12 +8,17 @@ export interface UiButtonElement extends HTMLButtonElement {
|
|
|
8
8
|
export interface UiButtonProps {
|
|
9
9
|
align?: "center" | "start" | null;
|
|
10
10
|
disabled?: boolean | null;
|
|
11
|
+
href?: string | null;
|
|
12
|
+
rel?: string | null;
|
|
11
13
|
size?: "sm" | "md" | "lg" | null;
|
|
12
14
|
stretch?: boolean | null;
|
|
15
|
+
target?: string | null;
|
|
13
16
|
tone?: "accent" | "neutral" | "danger" | "success" | "warning" | "info" | null;
|
|
17
|
+
type?: "button" | "submit" | "reset" | null;
|
|
14
18
|
variant?: "outline" | "solid" | "danger" | "ghost" | "link" | null;
|
|
15
19
|
attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
|
|
16
20
|
children?: readonly (string | Node)[];
|
|
17
21
|
slots?: Readonly<Record<string, readonly (string | Node)[]>>;
|
|
22
|
+
as?: "button" | "a";
|
|
18
23
|
}
|
|
19
24
|
export declare function createUiButton(props?: UiButtonProps): UiButtonElement;
|
package/vanilla/UiButton.js
CHANGED
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
import { manageComponentLifecycle } from "@nextwebwg/html-next/runtime";
|
|
3
3
|
import "../styles/ui-button.css";
|
|
4
4
|
|
|
5
|
-
const definition = {...{"contract":{"tag":"ui-button","props":{"align":{"type":{"enum":["center","start"]},"required":false,"target":{"attribute":"align"},"default":"center"},"disabled":{"type":"boolean","required":false,"target":{"attribute":"disabled"},"default":false},"size":{"type":{"enum":["sm","md","lg"]},"required":false,"target":{"attribute":"size"},"default":"md"},"stretch":{"type":"boolean","required":false,"target":{"attribute":"stretch"},"default":false},"tone":{"type":{"enum":["accent","neutral","danger","success","warning","info"]},"required":false,"target":{"attribute":"tone"},"default":"accent"},"variant":{"type":{"enum":["outline","solid","danger","ghost","link"]},"required":false,"target":{"attribute":"variant"},"default":"outline"}}},"template":{"kind":"element","name":"button","attributes":[{"kind":"literal","name":"type","value":"
|
|
5
|
+
const definition = {...{"contract":{"tag":"ui-button","props":{"align":{"type":{"enum":["center","start"]},"required":false,"target":{"attribute":"align"},"default":"center"},"disabled":{"type":"boolean","required":false,"target":{"attribute":"disabled"},"default":false},"href":{"type":"string","required":false,"target":{"attribute":"href"}},"rel":{"type":"string","required":false,"target":{"attribute":"rel"}},"size":{"type":{"enum":["sm","md","lg"]},"required":false,"target":{"attribute":"size"},"default":"md"},"stretch":{"type":"boolean","required":false,"target":{"attribute":"stretch"},"default":false},"target":{"type":"string","required":false,"target":{"attribute":"target"}},"tone":{"type":{"enum":["accent","neutral","danger","success","warning","info"]},"required":false,"target":{"attribute":"tone"},"default":"accent"},"type":{"type":{"enum":["button","submit","reset"]},"required":false,"target":{"attribute":"type"},"default":"button"},"variant":{"type":{"enum":["outline","solid","danger","ghost","link"]},"required":false,"target":{"attribute":"variant"},"default":"outline"}}},"template":{"kind":"element","name":"button","attributes":[{"kind":"attribute","name":"type","expression":"{ true: type, false: null }[format('%s', not href)]","expressionPlan":{"source":"{ true: type, false: null }[format('%s', not href)]","ast":{"kind":"index","object":{"kind":"object","pairs":[{"key":"true","value":{"kind":"id","name":"type"}},{"key":"false","value":{"kind":"literal","value":null}}]},"index":{"kind":"call","fn":"format","args":[{"kind":"literal","value":"%s"},{"kind":"unary","op":"not","operand":{"kind":"id","name":"href"}}]}},"dependencies":["href","type"]}},{"kind":"attribute","name":"disabled","expression":"{ true: true, false: null }[format('%s', disabled and not href)]","expressionPlan":{"source":"{ true: true, false: null }[format('%s', disabled and not href)]","ast":{"kind":"index","object":{"kind":"object","pairs":[{"key":"true","value":{"kind":"literal","value":true}},{"key":"false","value":{"kind":"literal","value":null}}]},"index":{"kind":"call","fn":"format","args":[{"kind":"literal","value":"%s"},{"kind":"binary","op":"and","left":{"kind":"id","name":"disabled"},"right":{"kind":"unary","op":"not","operand":{"kind":"id","name":"href"}}}]}},"dependencies":["disabled","href"]}},{"kind":"attribute","name":"href","expression":"{ true: null, false: href }[format('%s', disabled)]","expressionPlan":{"source":"{ true: null, false: href }[format('%s', disabled)]","ast":{"kind":"index","object":{"kind":"object","pairs":[{"key":"true","value":{"kind":"literal","value":null}},{"key":"false","value":{"kind":"id","name":"href"}}]},"index":{"kind":"call","fn":"format","args":[{"kind":"literal","value":"%s"},{"kind":"id","name":"disabled"}]}},"dependencies":["disabled","href"]}},{"kind":"attribute","name":"target","expression":"target","expressionPlan":{"source":"target","ast":{"kind":"id","name":"target"},"dependencies":["target"]}},{"kind":"attribute","name":"rel","expression":"rel","expressionPlan":{"source":"rel","ast":{"kind":"id","name":"rel"},"dependencies":["rel"]}},{"kind":"attribute","name":"aria-disabled","expression":"{ true: 'true', false: null }[format('%s', disabled and href)]","expressionPlan":{"source":"{ true: 'true', false: null }[format('%s', disabled and href)]","ast":{"kind":"index","object":{"kind":"object","pairs":[{"key":"true","value":{"kind":"literal","value":"true"}},{"key":"false","value":{"kind":"literal","value":null}}]},"index":{"kind":"call","fn":"format","args":[{"kind":"literal","value":"%s"},{"kind":"binary","op":"and","left":{"kind":"id","name":"disabled"},"right":{"kind":"id","name":"href"}}]}},"dependencies":["disabled","href"]}}],"children":[{"kind":"slot"}]},"declarations":[],"root":{"kind":"native","element":"button","choices":["button","a"]}},source:{file:import.meta.url},css:""};
|
|
6
6
|
|
|
7
7
|
export function createUiButton(options = {}) {
|
|
8
8
|
const { attributes = {}, children = [], slots = {}, as, ...componentProps } = options;
|
|
9
9
|
const projected = [];
|
|
10
|
-
const element = document.createElement("button");
|
|
10
|
+
const element = document.createElement(as ?? "button");
|
|
11
11
|
for (const [name, value] of Object.entries(attributes)) {
|
|
12
12
|
if (value === null || value === undefined || value === false) continue;
|
|
13
13
|
element.setAttribute(name, value === true ? "" : String(value));
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const value0 = componentProps["target"] === undefined ? undefined : componentProps["target"];
|
|
16
|
+
if (value0 === null || value0 === undefined) element.removeAttribute("target");
|
|
17
|
+
else element.setAttribute("target", String(value0));
|
|
18
|
+
const value1 = componentProps["rel"] === undefined ? undefined : componentProps["rel"];
|
|
19
|
+
if (value1 === null || value1 === undefined) element.removeAttribute("rel");
|
|
20
|
+
else element.setAttribute("rel", String(value1));
|
|
19
21
|
element.setAttribute("data-component", "ui-button");
|
|
20
22
|
if (children.length > 0) {
|
|
21
23
|
for (const child of children) {
|
package/vue/UiBadge.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
|
+
shape?: 'pill' | 'tag';
|
|
2
3
|
tone?: 'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger';
|
|
3
4
|
variant?: 'solid' | 'subtle';
|
|
4
5
|
};
|
|
@@ -7,6 +8,7 @@ type __VLS_Slots = {} & {
|
|
|
7
8
|
default?: (props: typeof __VLS_1) => any;
|
|
8
9
|
};
|
|
9
10
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
11
|
+
shape: "pill" | "tag";
|
|
10
12
|
tone: "neutral" | "accent" | "info" | "success" | "warning" | "danger";
|
|
11
13
|
variant: "solid" | "subtle";
|
|
12
14
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
package/vue/UiBadge.vue
CHANGED
|
@@ -6,10 +6,12 @@ defineOptions({ inheritAttrs: false })
|
|
|
6
6
|
|
|
7
7
|
const props = withDefaults(
|
|
8
8
|
defineProps<{
|
|
9
|
+
shape?: 'pill' | 'tag'
|
|
9
10
|
tone?: 'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger'
|
|
10
11
|
variant?: 'solid' | 'subtle'
|
|
11
12
|
}>(),
|
|
12
13
|
{
|
|
14
|
+
shape: 'pill',
|
|
13
15
|
tone: 'neutral',
|
|
14
16
|
variant: 'subtle',
|
|
15
17
|
},
|
|
@@ -20,6 +22,7 @@ const hostState = computed(() =>
|
|
|
20
22
|
[
|
|
21
23
|
props.tone && `tone tone=${props.tone}`,
|
|
22
24
|
props.variant && `variant variant=${props.variant}`,
|
|
25
|
+
props.shape && `shape shape=${props.shape}`,
|
|
23
26
|
].filter(Boolean).join(' ')
|
|
24
27
|
)
|
|
25
28
|
</script>
|
|
@@ -129,6 +132,32 @@ const hostState = computed(() =>
|
|
|
129
132
|
--_badge-text: var(--ui-info-subtle-text, var(--ui-info-solid));
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
/* A tag is flat at the start and points at the end. The point is cut from the box, so it has no
|
|
136
|
+
outline to follow: the edge is the fill. The end padding grows by the point's depth so the
|
|
137
|
+
label never runs into it. */
|
|
138
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=tag"] {
|
|
139
|
+
--_badge-point: var(--ui-badge-point, var(--ui-space-2));
|
|
140
|
+
|
|
141
|
+
border-color: transparent;
|
|
142
|
+
border-radius: 0;
|
|
143
|
+
padding-inline-end: calc(var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point));
|
|
144
|
+
clip-path: polygon(
|
|
145
|
+
0 0,
|
|
146
|
+
calc(100% - var(--_badge-point)) 0,
|
|
147
|
+
100% 50%,
|
|
148
|
+
calc(100% - var(--_badge-point)) 100%,
|
|
149
|
+
0 100%
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
[data-component~="ui-badge"][data-ui-badge-state~="shape=tag"]:dir(rtl) {
|
|
154
|
+
padding-inline-end: var(--ui-badge-padding-inline, var(--ui-space-2));
|
|
155
|
+
padding-inline-start: calc(
|
|
156
|
+
var(--ui-badge-padding-inline, var(--ui-space-2)) + var(--_badge-point)
|
|
157
|
+
);
|
|
158
|
+
clip-path: polygon(var(--_badge-point) 0, 100% 0, 100% 100%, var(--_badge-point) 100%, 0 50%);
|
|
159
|
+
}
|
|
160
|
+
|
|
132
161
|
/* A component that sets its display still honours the hidden attribute on its root. */
|
|
133
162
|
[data-component~="ui-badge"][hidden] {
|
|
134
163
|
display: none;
|
package/vue/UiButton.d.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
|
+
as?: 'button' | 'a';
|
|
2
3
|
align?: 'center' | 'start';
|
|
3
4
|
disabled?: boolean;
|
|
5
|
+
href?: string;
|
|
6
|
+
rel?: string;
|
|
4
7
|
size?: 'sm' | 'md' | 'lg';
|
|
5
8
|
stretch?: boolean;
|
|
9
|
+
target?: string;
|
|
6
10
|
tone?: 'accent' | 'neutral' | 'danger' | 'success' | 'warning' | 'info';
|
|
11
|
+
type?: 'button' | 'submit' | 'reset';
|
|
7
12
|
variant?: 'outline' | 'solid' | 'danger' | 'ghost' | 'link';
|
|
8
13
|
};
|
|
9
|
-
declare var
|
|
14
|
+
declare var __VLS_8: {};
|
|
10
15
|
type __VLS_Slots = {} & {
|
|
11
|
-
default?: (props: typeof
|
|
16
|
+
default?: (props: typeof __VLS_8) => any;
|
|
12
17
|
};
|
|
13
18
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
19
|
+
type: "button" | "submit" | "reset";
|
|
14
20
|
tone: "accent" | "neutral" | "danger" | "success" | "warning" | "info";
|
|
15
21
|
variant: "outline" | "solid" | "danger" | "ghost" | "link";
|
|
22
|
+
as: "button" | "a";
|
|
16
23
|
align: "center" | "start";
|
|
17
24
|
disabled: boolean;
|
|
18
25
|
size: "sm" | "md" | "lg";
|
package/vue/UiButton.vue
CHANGED
|
@@ -6,19 +6,26 @@ defineOptions({ inheritAttrs: false })
|
|
|
6
6
|
|
|
7
7
|
const props = withDefaults(
|
|
8
8
|
defineProps<{
|
|
9
|
+
as?: 'button' | 'a'
|
|
9
10
|
align?: 'center' | 'start'
|
|
10
11
|
disabled?: boolean
|
|
12
|
+
href?: string
|
|
13
|
+
rel?: string
|
|
11
14
|
size?: 'sm' | 'md' | 'lg'
|
|
12
15
|
stretch?: boolean
|
|
16
|
+
target?: string
|
|
13
17
|
tone?: 'accent' | 'neutral' | 'danger' | 'success' | 'warning' | 'info'
|
|
18
|
+
type?: 'button' | 'submit' | 'reset'
|
|
14
19
|
variant?: 'outline' | 'solid' | 'danger' | 'ghost' | 'link'
|
|
15
20
|
}>(),
|
|
16
21
|
{
|
|
22
|
+
as: 'button',
|
|
17
23
|
align: 'center',
|
|
18
24
|
disabled: false,
|
|
19
25
|
size: 'md',
|
|
20
26
|
stretch: false,
|
|
21
27
|
tone: 'accent',
|
|
28
|
+
type: 'button',
|
|
22
29
|
variant: 'outline',
|
|
23
30
|
},
|
|
24
31
|
)
|
|
@@ -33,18 +40,38 @@ const hostState = computed(() =>
|
|
|
33
40
|
props.tone && `tone tone=${props.tone}`,
|
|
34
41
|
].filter(Boolean).join(' ')
|
|
35
42
|
)
|
|
43
|
+
|
|
44
|
+
function text(value: unknown): string {
|
|
45
|
+
if (Array.isArray(value)) return value.map(text).join(' ')
|
|
46
|
+
return value === null || value === undefined || typeof value === 'object' ? '' : String(value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function attribute(value: unknown): any {
|
|
50
|
+
if (
|
|
51
|
+
value === null || value === undefined || value === false
|
|
52
|
+
|| (typeof value === 'object' && !Array.isArray(value))
|
|
53
|
+
) return undefined
|
|
54
|
+
return value === true ? '' : text(value)
|
|
55
|
+
}
|
|
36
56
|
</script>
|
|
37
57
|
|
|
38
58
|
<template>
|
|
39
|
-
<
|
|
59
|
+
<component
|
|
60
|
+
:is="as"
|
|
40
61
|
data-component="ui-button"
|
|
41
|
-
type="button"
|
|
42
62
|
v-bind="$attrs"
|
|
43
|
-
:
|
|
63
|
+
:type="attribute(({ true: type, false: null })[`${!href}`])"
|
|
64
|
+
:disabled="attribute(({ true: true, false: null })[`${disabled && !href}`])"
|
|
65
|
+
:href="attribute(({ true: null, false: href })[`${disabled}`])"
|
|
66
|
+
:target="target"
|
|
67
|
+
:rel="rel"
|
|
68
|
+
:aria-disabled="typeof (({ true: 'true', false: null })[`${Boolean(disabled && href)}`]) === 'boolean'
|
|
69
|
+
? String(({ true: 'true', false: null })[`${Boolean(disabled && href)}`])
|
|
70
|
+
: attribute(({ true: 'true', false: null })[`${Boolean(disabled && href)}`])"
|
|
44
71
|
:data-ui-button-state="hostState || undefined"
|
|
45
72
|
>
|
|
46
73
|
<slot />
|
|
47
|
-
</
|
|
74
|
+
</component>
|
|
48
75
|
</template>
|
|
49
76
|
|
|
50
77
|
<style scoped>
|
|
@@ -89,6 +116,7 @@ const hostState = computed(() =>
|
|
|
89
116
|
color: var(--ui-button-text, var(--_tone-text));
|
|
90
117
|
font: inherit;
|
|
91
118
|
font-weight: var(--ui-font-medium);
|
|
119
|
+
text-decoration: none;
|
|
92
120
|
line-height: var(--ui-line-height-tight);
|
|
93
121
|
padding: var(--ui-space-2) var(--ui-space-3);
|
|
94
122
|
background-image: var(--ui-raised-highlight);
|
|
@@ -121,13 +149,15 @@ const hostState = computed(() =>
|
|
|
121
149
|
font-size: 1rem;
|
|
122
150
|
}
|
|
123
151
|
|
|
124
|
-
|
|
152
|
+
/* A link is never :enabled, so interactive states test "not unavailable" instead: for a button that
|
|
153
|
+
is exactly :enabled, at the same specificity, and it also matches a link that can be followed. */
|
|
154
|
+
[data-component~="ui-button"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
125
155
|
border-color: var(--ui-button-hover-border, var(--_tone-hover));
|
|
126
156
|
background-color: var(--ui-button-hover-surface, oklch(from var(--_tone) l c h / 12%));
|
|
127
157
|
}
|
|
128
158
|
|
|
129
159
|
/* Pressed reads as pressed in: the highlight goes, and the shadow moves inside. */
|
|
130
|
-
[data-component~="ui-button"]:active:
|
|
160
|
+
[data-component~="ui-button"]:active:not(:disabled, [aria-disabled="true"]) {
|
|
131
161
|
background-color: var(--ui-button-active-surface, oklch(from var(--_tone) l c h / 18%));
|
|
132
162
|
background-image: none;
|
|
133
163
|
box-shadow: var(--ui-pressed);
|
|
@@ -141,12 +171,12 @@ const hostState = computed(() =>
|
|
|
141
171
|
box-shadow: var(--ui-button-solid-shadow, var(--ui-raised));
|
|
142
172
|
}
|
|
143
173
|
|
|
144
|
-
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"], [data-component~="ui-button"][data-ui-button-state~="variant=danger"]):hover:
|
|
174
|
+
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"], [data-component~="ui-button"][data-ui-button-state~="variant=danger"]):hover:not(:disabled, [aria-disabled="true"]) {
|
|
145
175
|
background-color: var(--_tone-hover);
|
|
146
176
|
border-color: var(--_tone-hover);
|
|
147
177
|
}
|
|
148
178
|
|
|
149
|
-
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"], [data-component~="ui-button"][data-ui-button-state~="variant=danger"]):active:
|
|
179
|
+
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"], [data-component~="ui-button"][data-ui-button-state~="variant=danger"]):active:not(:disabled, [aria-disabled="true"]) {
|
|
150
180
|
background-color: var(--_tone-active);
|
|
151
181
|
border-color: var(--_tone-active);
|
|
152
182
|
background-image: none;
|
|
@@ -204,12 +234,12 @@ const hostState = computed(() =>
|
|
|
204
234
|
}
|
|
205
235
|
|
|
206
236
|
/* Tints derive from the text colour, not a surface token, so hover and press show on any background. */
|
|
207
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"]:hover:
|
|
237
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
208
238
|
background-color: var(--ui-button-ghost-hover-surface, oklch(from var(--_tone) l c h / 10%));
|
|
209
239
|
border-color: transparent;
|
|
210
240
|
}
|
|
211
241
|
|
|
212
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"]:active:
|
|
242
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"]:active:not(:disabled, [aria-disabled="true"]) {
|
|
213
243
|
background-color: var(--ui-button-ghost-active-surface, oklch(from var(--_tone) l c h / 16%));
|
|
214
244
|
border-color: transparent;
|
|
215
245
|
box-shadow: var(--ui-pressed);
|
|
@@ -230,10 +260,10 @@ const hostState = computed(() =>
|
|
|
230
260
|
}
|
|
231
261
|
|
|
232
262
|
/* Disabled drops the tone rather than the shape: every variant keeps its own structure, so a
|
|
233
|
-
disabled outline is still an outline and a disabled ghost
|
|
263
|
+
disabled outline is still an outline and a disabled ghost still has no edge. Intent goes — an
|
|
234
264
|
unavailable action says "unavailable", not "unavailable, and destructive" — and so do the
|
|
235
265
|
highlight and shadow, because nothing that cannot be pressed should look raised. */
|
|
236
|
-
[data-component~="ui-button"]:disabled {
|
|
266
|
+
[data-component~="ui-button"]:is(:disabled, [aria-disabled="true"]) {
|
|
237
267
|
/* Derived from the tone itself, not from a separate disabled palette: faded toward the
|
|
238
268
|
page, which desaturates it at the same time. Toward the page rather than toward white,
|
|
239
269
|
so a dark theme dims where a light one lightens — the same rule, both schemes. */
|
|
@@ -250,14 +280,16 @@ const hostState = computed(() =>
|
|
|
250
280
|
}
|
|
251
281
|
|
|
252
282
|
/* A filled button keeps its fill, faded further so its text stays legible on it. */
|
|
253
|
-
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"], [data-component~="ui-button"][data-ui-button-state~="variant=danger"]):disabled {
|
|
283
|
+
:is([data-component~="ui-button"][data-ui-button-state~="variant=solid"], [data-component~="ui-button"][data-ui-button-state~="variant=danger"]):is(:disabled, [aria-disabled="true"]) {
|
|
254
284
|
--_tone: color-mix(in oklab, var(--_hue) 34%, var(--ui-surface));
|
|
255
285
|
}
|
|
256
286
|
|
|
257
287
|
/* Ghost has no hover to fall back on, so disabled is the one state it has to state outright:
|
|
258
|
-
it takes a surface
|
|
259
|
-
|
|
260
|
-
|
|
288
|
+
it takes a surface rather than reading as plain text. The surface is a wash of the faded tone,
|
|
289
|
+
the way hover washes the live one, so it stays light in every tone. An opaque mix toward the
|
|
290
|
+
hue came out a mid-grey slab for neutral, whose hue is the ink: louder than the enabled button. */
|
|
291
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=ghost"]:is(:disabled, [aria-disabled="true"]) {
|
|
292
|
+
background-color: oklch(from var(--_tone) l c h / 14%);
|
|
261
293
|
border-color: transparent;
|
|
262
294
|
}
|
|
263
295
|
|
|
@@ -288,19 +320,19 @@ const hostState = computed(() =>
|
|
|
288
320
|
text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
|
|
289
321
|
}
|
|
290
322
|
|
|
291
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"]:hover:
|
|
323
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"]:hover:not(:disabled, [aria-disabled="true"]) {
|
|
292
324
|
color: var(--ui-button-link-hover-text, var(--ui-text-primary));
|
|
293
325
|
text-decoration-color: currentColor;
|
|
294
326
|
background: none;
|
|
295
327
|
transform: none;
|
|
296
328
|
}
|
|
297
329
|
|
|
298
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"]:active:
|
|
330
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"]:active:not(:disabled, [aria-disabled="true"]) {
|
|
299
331
|
background: none;
|
|
300
332
|
transform: none;
|
|
301
333
|
}
|
|
302
334
|
|
|
303
|
-
[data-component~="ui-button"][data-ui-button-state~="variant=link"]:disabled {
|
|
335
|
+
[data-component~="ui-button"][data-ui-button-state~="variant=link"]:is(:disabled, [aria-disabled="true"]) {
|
|
304
336
|
background: none;
|
|
305
337
|
border-color: transparent;
|
|
306
338
|
color: var(--ui-button-disabled-text, var(--ui-disabled-text));
|
package/vue/chunks/UiBadge.js
CHANGED
|
@@ -8,17 +8,22 @@ var UiBadge_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE
|
|
|
8
8
|
inheritAttrs: false,
|
|
9
9
|
__name: "UiBadge",
|
|
10
10
|
props: {
|
|
11
|
+
shape: { default: "pill" },
|
|
11
12
|
tone: { default: "neutral" },
|
|
12
13
|
variant: { default: "subtle" }
|
|
13
14
|
},
|
|
14
15
|
setup(__props) {
|
|
15
16
|
const props = __props;
|
|
16
17
|
/** The values the styles' :host-state() rules test. */
|
|
17
|
-
const hostState = computed(() => [
|
|
18
|
+
const hostState = computed(() => [
|
|
19
|
+
props.tone && `tone tone=${props.tone}`,
|
|
20
|
+
props.variant && `variant variant=${props.variant}`,
|
|
21
|
+
props.shape && `shape shape=${props.shape}`
|
|
22
|
+
].filter(Boolean).join(" "));
|
|
18
23
|
return (_ctx, _cache) => {
|
|
19
24
|
return openBlock(), createElementBlock("span", mergeProps({ "data-component": "ui-badge" }, _ctx.$attrs, { "data-ui-badge-state": hostState.value || void 0 }), [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 16, _hoisted_1);
|
|
20
25
|
};
|
|
21
26
|
}
|
|
22
|
-
}), [["__scopeId", "data-v-
|
|
27
|
+
}), [["__scopeId", "data-v-c0ffc4d8"]]);
|
|
23
28
|
//#endregion
|
|
24
29
|
export { UiBadge_default as t };
|