@antadesign/anta 0.3.5 → 0.3.7
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/dist/anta_helpers.d.ts +10 -0
- package/dist/anta_helpers.js +8 -0
- package/dist/components/Card.d.ts +86 -0
- package/dist/components/Card.js +72 -0
- package/dist/components/Dialog.d.ts +92 -0
- package/dist/components/Dialog.js +71 -0
- package/dist/components/Input.d.ts +3 -0
- package/dist/components/InputAutocomplete.d.ts +69 -0
- package/dist/components/InputAutocomplete.js +95 -0
- package/dist/components/InputDate.js +1 -2
- package/dist/components/Menu.d.ts +4 -1
- package/dist/components/Menu.js +2 -1
- package/dist/components/MenuItem.d.ts +9 -1
- package/dist/components/MenuItem.js +2 -1
- package/dist/components/Select.d.ts +5 -2
- package/dist/components/Select.js +11 -34
- package/dist/components/SelectFaceted.d.ts +7 -1
- package/dist/components/SelectFaceted.js +12 -11
- package/dist/components/select-options.d.ts +14 -0
- package/dist/components/select-options.js +31 -0
- package/dist/elements/a-card.css +211 -0
- package/dist/elements/a-card.d.ts +10 -0
- package/dist/elements/a-card.js +213 -0
- package/dist/elements/a-checkbox.css +1 -1
- package/dist/elements/a-dialog.css +1 -0
- package/dist/elements/a-dialog.d.ts +26 -0
- package/dist/elements/a-dialog.js +386 -0
- package/dist/elements/a-expander.js +2 -2
- package/dist/elements/a-input-time.js +2 -1
- package/dist/elements/a-input.d.ts +2 -0
- package/dist/elements/a-input.js +34 -1
- package/dist/elements/a-menu.d.ts +5 -2
- package/dist/elements/a-menu.js +52 -15
- package/dist/elements/a-radio.css +1 -1
- package/dist/elements/a-tooltip.d.ts +17 -0
- package/dist/elements/a-tooltip.js +28 -0
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.js +3 -0
- package/dist/general_types.d.ts +45 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/jsx-runtime.d.ts +2 -1
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
|
|
2
2
|
import { useState, useId } from "../jsx-runtime";
|
|
3
|
+
import { ISOLATE_HINT } from "../anta_helpers";
|
|
4
|
+
import { normalizeOpt, matchQueryRegex, matchesQuery, highlight } from "./select-options";
|
|
3
5
|
import { Input } from "./Input";
|
|
4
6
|
import { Icon } from "./Icon";
|
|
5
7
|
import { Menu } from "./Menu";
|
|
@@ -8,8 +10,6 @@ import { MenuGroup } from "./MenuGroup";
|
|
|
8
10
|
import { MenuSeparator } from "./MenuSeparator";
|
|
9
11
|
import { Tooltip } from "./Tooltip";
|
|
10
12
|
import styles from "./Select.module.css";
|
|
11
|
-
const IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad/i.test(navigator.userAgent || "");
|
|
12
|
-
const normalize = (o) => typeof o === "string" ? { value: o, label: o } : o;
|
|
13
13
|
function optionsWithSelection(options, values) {
|
|
14
14
|
const set = new Set(values == null ? [] : Array.isArray(values) ? values : [values]);
|
|
15
15
|
const state = (on, total) => on === 0 ? "none" : on === total ? "all" : "some";
|
|
@@ -28,7 +28,7 @@ function optionsWithSelection(options, values) {
|
|
|
28
28
|
const total = children.reduce((n, c) => n + c.total, 0);
|
|
29
29
|
return { node: { ...g, options: children.map((c) => c.node), selectionState: state(on, total) }, on, total };
|
|
30
30
|
}
|
|
31
|
-
const o =
|
|
31
|
+
const o = normalizeOpt(raw);
|
|
32
32
|
const selected = set.has(o.value);
|
|
33
33
|
return { node: { ...o, selected }, on: selected ? 1 : 0, total: 1 };
|
|
34
34
|
});
|
|
@@ -54,7 +54,7 @@ const Select = (props) => {
|
|
|
54
54
|
disabled,
|
|
55
55
|
toneSelected,
|
|
56
56
|
filter,
|
|
57
|
-
selectAll,
|
|
57
|
+
selectAll = true,
|
|
58
58
|
selectAllLabel = "Select all",
|
|
59
59
|
clearable,
|
|
60
60
|
clearLabel = "Clear",
|
|
@@ -85,7 +85,7 @@ const Select = (props) => {
|
|
|
85
85
|
if (typeof raw !== "string" && isSubmenu(raw)) collectLeaves(raw.submenu, raw.label, disabled2 || !!raw.disabled, out);
|
|
86
86
|
else if (typeof raw !== "string" && isGroup(raw)) collectLeaves(raw.options, raw.label, disabled2 || !!raw.disabled, out);
|
|
87
87
|
else {
|
|
88
|
-
const o =
|
|
88
|
+
const o = normalizeOpt(raw);
|
|
89
89
|
out.push({ opt: o, disabled: disabled2 || !!o.disabled, group });
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -101,28 +101,11 @@ const Select = (props) => {
|
|
|
101
101
|
const byValue = new Map(allLeaves.map((l) => [l.opt.value, l.opt]));
|
|
102
102
|
const filtering = filter !== void 0 && filter !== false;
|
|
103
103
|
const q = query.trim();
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const matches = (o) => typeof filter === "function" ? filter(o, q) : !queryRe || [o.value, o.label ?? "", o.hint ?? ""].some((s) => queryRe.test(s));
|
|
104
|
+
const queryRe = filtering && typeof filter !== "function" ? matchQueryRegex(q) : null;
|
|
105
|
+
const matches = (o) => typeof filter === "function" ? filter(o, q) : matchesQuery(o, queryRe);
|
|
107
106
|
const prune = typeof filter === "function" || filtering && !!q;
|
|
108
107
|
const visibleLeaves = prune ? allLeaves.filter((l) => matches(l.opt)) : allLeaves;
|
|
109
108
|
const flattening = filtering && !!q;
|
|
110
|
-
const highlight = (text) => {
|
|
111
|
-
if (!queryRe) return text;
|
|
112
|
-
const re = new RegExp(queryRe.source, "gi");
|
|
113
|
-
const out = [];
|
|
114
|
-
let last = 0;
|
|
115
|
-
let m;
|
|
116
|
-
while ((m = re.exec(text)) !== null) {
|
|
117
|
-
if (m.index > last) out.push(text.slice(last, m.index));
|
|
118
|
-
out.push(/* @__PURE__ */ jsx("b", { children: m[0] }, out.length));
|
|
119
|
-
last = m.index + m[0].length;
|
|
120
|
-
if (m[0].length === 0) re.lastIndex++;
|
|
121
|
-
}
|
|
122
|
-
if (out.length === 0) return text;
|
|
123
|
-
if (last < text.length) out.push(text.slice(last));
|
|
124
|
-
return out;
|
|
125
|
-
};
|
|
126
109
|
const selectedValues = Array.isArray(currentRaw) ? currentRaw : currentRaw != null ? [currentRaw] : [];
|
|
127
110
|
const isSelected = (v) => selectedValues.includes(v);
|
|
128
111
|
const selectedOptions = selectedValues.map((v) => byValue.get(v)).filter(Boolean);
|
|
@@ -171,7 +154,7 @@ const Select = (props) => {
|
|
|
171
154
|
const custom = renderOption?.(o, optState);
|
|
172
155
|
const customMark = renderIndicator?.(optState);
|
|
173
156
|
const ariaSelectable = !multiple && mark === "none" ? { role: "menuitemradio", "aria-checked": isSelected(o.value) ? "true" : "false" } : void 0;
|
|
174
|
-
const isolateHint = multiple && selectAll && !disabled2 ?
|
|
157
|
+
const isolateHint = multiple && selectAll && !disabled2 ? ISOLATE_HINT : void 0;
|
|
175
158
|
const tip = o.tooltip ?? isolateHint;
|
|
176
159
|
const hintOnly = o.tooltip == null && isolateHint != null;
|
|
177
160
|
return /* @__PURE__ */ jsxs(
|
|
@@ -181,8 +164,8 @@ const Select = (props) => {
|
|
|
181
164
|
selectionIndicator: menuItemIndicator,
|
|
182
165
|
...ariaSelectable,
|
|
183
166
|
indicator: customMark ?? void 0,
|
|
184
|
-
label: custom ? void 0 :
|
|
185
|
-
hint: custom ? void 0 :
|
|
167
|
+
label: custom ? void 0 : highlight(o.label ?? o.value, queryRe),
|
|
168
|
+
hint: custom ? void 0 : o.hint ? highlight(o.hint, queryRe) : o.hint,
|
|
186
169
|
icon: custom ? void 0 : o.icon,
|
|
187
170
|
tone: o.tone,
|
|
188
171
|
toneSelected,
|
|
@@ -227,7 +210,7 @@ const Select = (props) => {
|
|
|
227
210
|
if (inner.length) out.push(/* @__PURE__ */ jsx(MenuGroup, { label: raw.label, children: inner }, `grp-${i}-${raw.label}`));
|
|
228
211
|
return;
|
|
229
212
|
}
|
|
230
|
-
const o =
|
|
213
|
+
const o = normalizeOpt(raw);
|
|
231
214
|
if (typeof filter === "function" && !matches(o)) return;
|
|
232
215
|
out.push(renderOptionRow(o, disabled2 || !!o.disabled));
|
|
233
216
|
});
|
|
@@ -271,12 +254,6 @@ const Select = (props) => {
|
|
|
271
254
|
round,
|
|
272
255
|
"aria-haspopup": "menu",
|
|
273
256
|
"aria-expanded": open ? "true" : "false",
|
|
274
|
-
onKeyDown: (e) => {
|
|
275
|
-
if (!open && (e.key === "Enter" || e.key === " " || e.key === "ArrowDown")) {
|
|
276
|
-
e.preventDefault();
|
|
277
|
-
e.currentTarget.click();
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
257
|
trailing: (
|
|
281
258
|
// Chevron rotates down → up when open (spacing + rotation in the CSS
|
|
282
259
|
// module, matching the Input "Select dropdown" example).
|
|
@@ -36,7 +36,9 @@ export interface SelectFacetMultiple extends FacetBase {
|
|
|
36
36
|
options: SelectItem[];
|
|
37
37
|
/** Add a search field atop this facet's flyout that filters its options. */
|
|
38
38
|
filter?: FacetFilter;
|
|
39
|
-
/**
|
|
39
|
+
/** A "Select all" row that toggles every option. On by default — set `false`
|
|
40
|
+
* to drop it.
|
|
41
|
+
* @defaultValue true */
|
|
40
42
|
selectAll?: boolean;
|
|
41
43
|
/** Label for the `selectAll` row.
|
|
42
44
|
* @defaultValue Select all */
|
|
@@ -129,6 +131,10 @@ export interface SelectFacetedProps extends Omit<BaseProps, 'children'> {
|
|
|
129
131
|
priority?: 'primary' | 'secondary';
|
|
130
132
|
/** Disable the whole control. */
|
|
131
133
|
disabled?: boolean;
|
|
134
|
+
/** Tone applied to a selected option row in the facet flyouts (label, selected
|
|
135
|
+
* tint, and the check / checkbox indicator). A named tone or a custom CSS
|
|
136
|
+
* colour, matching `Select`'s `toneSelected`. Defaults to a neutral selection. */
|
|
137
|
+
toneSelected?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical' | (string & {});
|
|
132
138
|
/** Add a single search field to the top of the root menu that flattens every
|
|
133
139
|
* `single` / `multiple` facet's options into one list and searches across them
|
|
134
140
|
* — typing "alice" surfaces it under both an Assignee and an Owner facet, each
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
|
|
2
2
|
import { useState, useMemo } from "../jsx-runtime";
|
|
3
|
-
import { nativeStateChange } from "../anta_helpers";
|
|
3
|
+
import { nativeStateChange, ISOLATE_HINT } from "../anta_helpers";
|
|
4
|
+
import { normalizeOpt, matchQueryRegex, matchesQuery } from "./select-options";
|
|
4
5
|
import { Button } from "./Button";
|
|
5
6
|
import { Menu } from "./Menu";
|
|
6
7
|
import { MenuItem } from "./MenuItem";
|
|
@@ -8,8 +9,8 @@ import { MenuGroup } from "./MenuGroup";
|
|
|
8
9
|
import { MenuSeparator } from "./MenuSeparator";
|
|
9
10
|
import { Tag } from "./Tag";
|
|
10
11
|
import { Input } from "./Input";
|
|
12
|
+
import { Tooltip } from "./Tooltip";
|
|
11
13
|
import styles from "./SelectFaceted.module.css";
|
|
12
|
-
const normalizeOpt = (o) => typeof o === "string" ? { value: o, label: o } : o;
|
|
13
14
|
const leavesOf = (items) => {
|
|
14
15
|
const out = [];
|
|
15
16
|
for (const it of items) {
|
|
@@ -22,13 +23,7 @@ const leavesOf = (items) => {
|
|
|
22
23
|
return out;
|
|
23
24
|
};
|
|
24
25
|
const isEmpty = (v) => v == null || v === "" || Array.isArray(v) && v.length === 0;
|
|
25
|
-
const
|
|
26
|
-
const defaultMatch = (o, query) => {
|
|
27
|
-
const q = query.trim();
|
|
28
|
-
if (!q) return true;
|
|
29
|
-
const re = new RegExp(q.split(/\s+/).map(escapeRe).join("\\s+"), "i");
|
|
30
|
-
return [o.value, o.label, o.hint].some((v) => typeof v === "string" && re.test(v));
|
|
31
|
-
};
|
|
26
|
+
const defaultMatch = (o, query) => matchesQuery(o, matchQueryRegex(query));
|
|
32
27
|
const SelectFaceted = (props) => {
|
|
33
28
|
const {
|
|
34
29
|
facets,
|
|
@@ -40,6 +35,7 @@ const SelectFaceted = (props) => {
|
|
|
40
35
|
size,
|
|
41
36
|
priority = "secondary",
|
|
42
37
|
disabled,
|
|
38
|
+
toneSelected,
|
|
43
39
|
searchable,
|
|
44
40
|
searchPlaceholder = "Filter\u2026",
|
|
45
41
|
clearable = true,
|
|
@@ -125,6 +121,7 @@ const SelectFaceted = (props) => {
|
|
|
125
121
|
label: opt.label ?? opt.value,
|
|
126
122
|
hint: opt.hint,
|
|
127
123
|
tone: opt.tone,
|
|
124
|
+
toneSelected,
|
|
128
125
|
disabled: opt.disabled,
|
|
129
126
|
"data-menu-open": ""
|
|
130
127
|
};
|
|
@@ -142,13 +139,17 @@ const SelectFaceted = (props) => {
|
|
|
142
139
|
);
|
|
143
140
|
}
|
|
144
141
|
const arr = current[facet.key] ?? [];
|
|
142
|
+
const isolable = facet.selectAll !== false && !opt.disabled;
|
|
143
|
+
const tip = opt.tooltip ?? (isolable ? ISOLATE_HINT : void 0);
|
|
144
|
+
const hintOnly = opt.tooltip == null && isolable;
|
|
145
145
|
return /* @__PURE__ */ jsx(
|
|
146
146
|
MenuItem,
|
|
147
147
|
{
|
|
148
148
|
...shared,
|
|
149
149
|
selectionIndicator: "checkbox",
|
|
150
150
|
selected: arr.includes(opt.value),
|
|
151
|
-
onSelect: () => setFacet(facet, arr.includes(opt.value) ? arr.filter((v) => v !== opt.value) : [...arr, opt.value])
|
|
151
|
+
onSelect: (e) => isolable && e?.altKey ? setFacet(facet, [opt.value]) : setFacet(facet, arr.includes(opt.value) ? arr.filter((v) => v !== opt.value) : [...arr, opt.value]),
|
|
152
|
+
children: tip && /* @__PURE__ */ jsx(Tooltip, { ...hintOnly ? { follow: true, delay: 700 } : {}, children: tip })
|
|
152
153
|
},
|
|
153
154
|
`${keyPrefix}${opt.value}`
|
|
154
155
|
);
|
|
@@ -161,7 +162,7 @@ const SelectFaceted = (props) => {
|
|
|
161
162
|
const allOn = enabled.length > 0 && enabled.every((v) => arr.includes(v));
|
|
162
163
|
const someOn = enabled.some((v) => arr.includes(v));
|
|
163
164
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
164
|
-
facet.selectAll && leaves.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
165
|
+
facet.selectAll !== false && leaves.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
165
166
|
/* @__PURE__ */ jsx(
|
|
166
167
|
MenuItem,
|
|
167
168
|
{
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SelectOption } from './Select';
|
|
2
|
+
/** Coerce a bare string option to `{ value, label }`; pass an object through. */
|
|
3
|
+
export declare const normalizeOpt: (o: string | SelectOption) => SelectOption;
|
|
4
|
+
/** The built-in filter regex: case-insensitive, with each run of whitespace in the
|
|
5
|
+
* query matching any gap (`\s+`). Returns null for an empty query — the signal to
|
|
6
|
+
* match everything and to skip match-highlighting. */
|
|
7
|
+
export declare function matchQueryRegex(query: string): RegExp | null;
|
|
8
|
+
/** Whether an option matches the built-in query regex, testing value / label /
|
|
9
|
+
* hint. A null regex (empty query) matches everything. */
|
|
10
|
+
export declare const matchesQuery: (o: SelectOption, queryRe: RegExp | null) => boolean;
|
|
11
|
+
/** Bold the matched substring(s) of `text` for display (built-in matcher only).
|
|
12
|
+
* A null regex returns the text unchanged. The regex is re-created global so
|
|
13
|
+
* `exec` walks every match; the zero-width guard stops an empty match looping. */
|
|
14
|
+
export declare const highlight: (text: string, queryRe: RegExp | null) => React.ReactNode;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx } from "@antadesign/anta/jsx-runtime";
|
|
2
|
+
const normalizeOpt = (o) => typeof o === "string" ? { value: o, label: o } : o;
|
|
3
|
+
function matchQueryRegex(query) {
|
|
4
|
+
const q = query.trim();
|
|
5
|
+
if (!q) return null;
|
|
6
|
+
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7
|
+
return new RegExp(q.split(/\s+/).map(escape).join("\\s+"), "i");
|
|
8
|
+
}
|
|
9
|
+
const matchesQuery = (o, queryRe) => !queryRe || [o.value, o.label ?? "", o.hint ?? ""].some((s) => queryRe.test(s));
|
|
10
|
+
const highlight = (text, queryRe) => {
|
|
11
|
+
if (!queryRe) return text;
|
|
12
|
+
const re = new RegExp(queryRe.source, "gi");
|
|
13
|
+
const out = [];
|
|
14
|
+
let last = 0;
|
|
15
|
+
let m;
|
|
16
|
+
while ((m = re.exec(text)) !== null) {
|
|
17
|
+
if (m.index > last) out.push(text.slice(last, m.index));
|
|
18
|
+
out.push(/* @__PURE__ */ jsx("b", { children: m[0] }, out.length));
|
|
19
|
+
last = m.index + m[0].length;
|
|
20
|
+
if (m[0].length === 0) re.lastIndex++;
|
|
21
|
+
}
|
|
22
|
+
if (out.length === 0) return text;
|
|
23
|
+
if (last < text.length) out.push(text.slice(last));
|
|
24
|
+
return out;
|
|
25
|
+
};
|
|
26
|
+
export {
|
|
27
|
+
highlight,
|
|
28
|
+
matchQueryRegex,
|
|
29
|
+
matchesQuery,
|
|
30
|
+
normalizeOpt
|
|
31
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
@layer anta {
|
|
2
|
+
/* Host + token definitions for <a-card>. The visible box lives here (not in the
|
|
3
|
+
shadow) so plain consumer CSS overrides it and it paints before the element
|
|
4
|
+
upgrades; the shadow <style> owns layout + region typography and only
|
|
5
|
+
REFERENCES these --card-* tokens with literal fallbacks. Named tones use
|
|
6
|
+
Anta's theme-aware role tokens, so dark mode is mostly free (same as
|
|
7
|
+
<a-expander> / <a-tag>); only the custom-tone branch needs a .dark re-tune. */
|
|
8
|
+
|
|
9
|
+
a-card {
|
|
10
|
+
/* Text roles: title = the strong foreground, text = the body (a step softer). */
|
|
11
|
+
--card-title: var(--text-1);
|
|
12
|
+
--card-text: var(--text-2);
|
|
13
|
+
|
|
14
|
+
/* Surface pairs per priority. `primary` (the default) is a clean sheet —
|
|
15
|
+
bg-1 in light, bg-4 in dark (see the `.dark` neutral rule), and
|
|
16
|
+
bg-4-{tone} for a toned card; `secondary` is a subtle fill (bg-2);
|
|
17
|
+
`tertiary` is a frosted panel (the primary surface, semi-transparent, +
|
|
18
|
+
a backdrop blur — see below). */
|
|
19
|
+
--card-bg-secondary: var(--bg-2);
|
|
20
|
+
--card-bg-primary: var(--bg-1);
|
|
21
|
+
|
|
22
|
+
/* `primary` is the default surface. */
|
|
23
|
+
--card-bg: var(--card-bg-primary);
|
|
24
|
+
/* Neutral edge hairline: text-2 at 10% (via --card-text). Toned cards override
|
|
25
|
+
this with their border-4-{tone} in the tone rules below. Flat across
|
|
26
|
+
priorities. */
|
|
27
|
+
--card-border: color-mix(in oklch, var(--card-text) 10%, transparent);
|
|
28
|
+
|
|
29
|
+
/* Selected ring (a 2px inset stroke) — the tone's strongest border. */
|
|
30
|
+
--card-selected-ring: var(--border-1);
|
|
31
|
+
|
|
32
|
+
/* Closed-surface hover lift for link cards: an oklch lightness bump that keeps
|
|
33
|
+
hue + chroma, so it works for every priority and tone in light and dark
|
|
34
|
+
(a transparent tertiary surface keeps zero alpha). Tune the amount here. */
|
|
35
|
+
--card-bg-hover-lift: 0.01;
|
|
36
|
+
|
|
37
|
+
--card-radius: 8px;
|
|
38
|
+
--card-padding: 16px;
|
|
39
|
+
|
|
40
|
+
display: block;
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
/* Fill the container (grid cell / flex item / block) so width is always
|
|
43
|
+
definite. This is also what keeps `container-type` below from collapsing
|
|
44
|
+
the card to zero width in a shrink-to-fit context (a query container can't
|
|
45
|
+
size to its own content). Same as <a-expander>. */
|
|
46
|
+
width: 100%;
|
|
47
|
+
/* Establish the card as a query container so both the shadow layout and
|
|
48
|
+
consumer slotted content can `@container card (…)` on its width. */
|
|
49
|
+
container-type: inline-size;
|
|
50
|
+
container-name: card;
|
|
51
|
+
|
|
52
|
+
color: var(--card-text);
|
|
53
|
+
background: var(--card-bg);
|
|
54
|
+
/* The edge is a 1px OUTSET shadow ring (spread 1px, no blur), not a border:
|
|
55
|
+
it sits outside the box so full-bleed media reaches the very edge (no 1px
|
|
56
|
+
border inset) and it never adds to layout. `overflow: clip` doesn't clip an
|
|
57
|
+
element's own outset shadow, so the ring shows while the media still clips
|
|
58
|
+
to the rounded corners. */
|
|
59
|
+
box-shadow: 0 0 0 1px var(--card-border);
|
|
60
|
+
border-radius: var(--card-radius);
|
|
61
|
+
/* Clip full-bleed media to the rounded corners. */
|
|
62
|
+
overflow: clip;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
a-card[priority="secondary"] {
|
|
66
|
+
--card-bg: var(--card-bg-secondary);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* `round` re-points --card-radius, so both the host clip-radius and the shadow
|
|
70
|
+
container's ring follow. Bare `round` → 999px (fully round, clamped to the
|
|
71
|
+
box); a length (`round="16px"`, or the wrapper's `round={16}` → inline
|
|
72
|
+
--card-round) sets a custom radius. The typed attr() covers raw-HTML authors. */
|
|
73
|
+
a-card[round] {
|
|
74
|
+
--card-round: attr(round type(<length>));
|
|
75
|
+
--card-radius: var(--card-round, 999px);
|
|
76
|
+
}
|
|
77
|
+
a-card[priority="tertiary"] {
|
|
78
|
+
/* Frosted glass: the primary surface at 1% over a backdrop blur, so the card
|
|
79
|
+
reads over busy / colored / image backgrounds. It keeps the same hairline
|
|
80
|
+
ring as the other priorities (no --card-border override). The blur only
|
|
81
|
+
shows where there's detail behind it; the translucency reads everywhere. */
|
|
82
|
+
--card-bg: color-mix(in oklch, var(--card-bg-primary) 1%, transparent);
|
|
83
|
+
backdrop-filter: blur(8px);
|
|
84
|
+
-webkit-backdrop-filter: blur(8px);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Neutral dark: bg-1 (primary's light surface) is pure black in dark mode, so
|
|
88
|
+
step neutral primary up to bg-4. Secondary stays bg-2 via its token, and a
|
|
89
|
+
toned card's --bg-*-{tone} tokens self-flip, so this is scoped to neutral. */
|
|
90
|
+
.dark a-card:not([tone]),
|
|
91
|
+
.dark a-card[tone="neutral"] {
|
|
92
|
+
--card-bg-primary: var(--bg-4);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Hover lift, link cards only — gated so it doesn't stick after a tap. */
|
|
96
|
+
@media (hover: hover) and (pointer: fine) {
|
|
97
|
+
a-card[href]:not([loading]):hover {
|
|
98
|
+
background: oklch(from var(--card-bg) calc(l + var(--card-bg-hover-lift)) c h);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Pre-upgrade skeleton: the host border / background / radius already paint
|
|
103
|
+
(they're defined here, not in the shadow), so only the content inset is
|
|
104
|
+
missing before the shadow's .content exists — supply it here, then the
|
|
105
|
+
shadow owns padding once :defined. */
|
|
106
|
+
a-card:not(:defined) {
|
|
107
|
+
padding: var(--card-padding);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* A slotted heading node — <a-title> (any level) or a raw <h1>–<h6> — carries a
|
|
111
|
+
document TOP margin (Title level 4 is 20px) that would add a leading gap in the
|
|
112
|
+
header; zero it. The BOTTOM margin stays, so a heading header keeps its gap to
|
|
113
|
+
the body. (A plain-STRING header has no margin of its own, so the `<Card>`
|
|
114
|
+
wrapper wraps it in a div with the matching 8px bottom margin — CSS can't tell
|
|
115
|
+
a string from a styled node, so that split lives in the wrapper.) Specificity
|
|
116
|
+
is bumped past `a-title[level="N"]` (0,1,1) and the reset's `hN` (0,0,1);
|
|
117
|
+
`@layer anta` keeps it overridable by un-layered CSS. */
|
|
118
|
+
a-card :is(h1, h2, h3, h4, h5, h6),
|
|
119
|
+
a-card a-title[level] {
|
|
120
|
+
margin-block-start: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Named tones — re-point the text roles + the per-priority surface pairs to the
|
|
124
|
+
matching theme tokens (which self-flip for dark mode). */
|
|
125
|
+
a-card[tone="brand"] {
|
|
126
|
+
--card-title: var(--text-1-brand);
|
|
127
|
+
--card-text: var(--text-2-brand);
|
|
128
|
+
--card-bg-secondary: var(--bg-2-brand);
|
|
129
|
+
--card-bg-primary: var(--bg-4-brand);
|
|
130
|
+
--card-border: var(--border-4-brand);
|
|
131
|
+
--card-selected-ring: var(--border-1-brand);
|
|
132
|
+
}
|
|
133
|
+
a-card[tone="info"] {
|
|
134
|
+
--card-title: var(--text-1-info);
|
|
135
|
+
--card-text: var(--text-2-info);
|
|
136
|
+
--card-bg-secondary: var(--bg-2-info);
|
|
137
|
+
--card-bg-primary: var(--bg-4-info);
|
|
138
|
+
--card-border: var(--border-4-info);
|
|
139
|
+
--card-selected-ring: var(--border-1-info);
|
|
140
|
+
}
|
|
141
|
+
a-card[tone="success"] {
|
|
142
|
+
--card-title: var(--text-1-success);
|
|
143
|
+
--card-text: var(--text-2-success);
|
|
144
|
+
--card-bg-secondary: var(--bg-2-success);
|
|
145
|
+
--card-bg-primary: var(--bg-4-success);
|
|
146
|
+
--card-border: var(--border-4-success);
|
|
147
|
+
--card-selected-ring: var(--border-1-success);
|
|
148
|
+
}
|
|
149
|
+
a-card[tone="warning"] {
|
|
150
|
+
--card-title: var(--text-1-warning);
|
|
151
|
+
--card-text: var(--text-2-warning);
|
|
152
|
+
--card-bg-secondary: var(--bg-2-warning);
|
|
153
|
+
--card-bg-primary: var(--bg-4-warning);
|
|
154
|
+
--card-border: var(--border-4-warning);
|
|
155
|
+
--card-selected-ring: var(--border-1-warning);
|
|
156
|
+
}
|
|
157
|
+
a-card[tone="critical"] {
|
|
158
|
+
--card-title: var(--text-1-critical);
|
|
159
|
+
--card-text: var(--text-2-critical);
|
|
160
|
+
--card-bg-secondary: var(--bg-2-critical);
|
|
161
|
+
--card-bg-primary: var(--bg-4-critical);
|
|
162
|
+
--card-border: var(--border-4-critical);
|
|
163
|
+
--card-selected-ring: var(--border-1-critical);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Custom tone — any non-named `tone` value keeps the source hue while
|
|
167
|
+
lightness/chroma are pinned per token in oklch, the <a-expander> mechanism.
|
|
168
|
+
The JSX wrapper writes --card-tone-source inline; the typed attr() fallback
|
|
169
|
+
picks up raw <a-card tone="…"> on engines that support it. */
|
|
170
|
+
a-card[tone]:not(
|
|
171
|
+
[tone="neutral"],
|
|
172
|
+
[tone="brand"],
|
|
173
|
+
[tone="info"],
|
|
174
|
+
[tone="success"],
|
|
175
|
+
[tone="warning"],
|
|
176
|
+
[tone="critical"]
|
|
177
|
+
) {
|
|
178
|
+
--card-tone-source: attr(tone type(<color>), currentColor);
|
|
179
|
+
|
|
180
|
+
--card-text: oklch(from var(--card-tone-source) 0.41 0.15 h);
|
|
181
|
+
--card-title: oklch(from var(--card-tone-source) 0.33 0.15 h);
|
|
182
|
+
--card-bg-secondary: oklch(from var(--card-tone-source) 0.99 0.006 h);
|
|
183
|
+
--card-bg-primary: oklch(from var(--card-tone-source) 0.955 0.02 h);
|
|
184
|
+
--card-border: oklch(from var(--card-tone-source) 0.895 0.045 h);
|
|
185
|
+
--card-selected-ring: oklch(from var(--card-tone-source) 0.6 0.15 h);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.dark a-card[tone]:not(
|
|
189
|
+
[tone="neutral"],
|
|
190
|
+
[tone="brand"],
|
|
191
|
+
[tone="info"],
|
|
192
|
+
[tone="success"],
|
|
193
|
+
[tone="warning"],
|
|
194
|
+
[tone="critical"]
|
|
195
|
+
) {
|
|
196
|
+
--card-text: oklch(from var(--card-tone-source) 0.75 0.11 h);
|
|
197
|
+
--card-title: oklch(from var(--card-tone-source) 0.82 0.10 h);
|
|
198
|
+
--card-bg-secondary: oklch(from var(--card-tone-source) 0.13 0.02 h);
|
|
199
|
+
--card-bg-primary: oklch(from var(--card-tone-source) 0.19 0.04 h);
|
|
200
|
+
--card-border: oklch(from var(--card-tone-source) 0.26 0.07 h);
|
|
201
|
+
--card-selected-ring: oklch(from var(--card-tone-source) 0.7 0.13 h);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Size — padding scale (medium is the default, set on the base rule). */
|
|
205
|
+
a-card[size="small"] {
|
|
206
|
+
--card-padding: 12px;
|
|
207
|
+
}
|
|
208
|
+
a-card[size="large"] {
|
|
209
|
+
--card-padding: 20px;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HTMLElementBase } from '../anta_helpers';
|
|
2
|
+
import './a-card.css';
|
|
3
|
+
export declare class ACardElement extends HTMLElementBase {
|
|
4
|
+
#private;
|
|
5
|
+
static observedAttributes: string[];
|
|
6
|
+
constructor();
|
|
7
|
+
connectedCallback(): void;
|
|
8
|
+
attributeChangedCallback(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare function register_a_card(): void;
|