@xtyle/astro 0.5.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/README.md +42 -0
- package/package.json +25 -0
- package/src/Accordion.astro +79 -0
- package/src/Alert.astro +57 -0
- package/src/AppShell.astro +97 -0
- package/src/Avatar.astro +71 -0
- package/src/AvatarGroup.astro +44 -0
- package/src/Badge.astro +73 -0
- package/src/Bar.astro +83 -0
- package/src/Breadcrumb.astro +54 -0
- package/src/Button.astro +91 -0
- package/src/Card.astro +44 -0
- package/src/CardLink.astro +60 -0
- package/src/Carousel.astro +39 -0
- package/src/Checkbox.astro +68 -0
- package/src/CheckboxGroup.astro +54 -0
- package/src/Cluster.astro +46 -0
- package/src/Code.astro +42 -0
- package/src/ColorPicker.astro +184 -0
- package/src/Dialog.astro +69 -0
- package/src/Dock.astro +79 -0
- package/src/DockZone.astro +17 -0
- package/src/Eyebrow.astro +30 -0
- package/src/Field.astro +117 -0
- package/src/FormGroup.astro +71 -0
- package/src/Grid.astro +57 -0
- package/src/Heading.astro +34 -0
- package/src/Heatmap.astro +116 -0
- package/src/Hero.astro +19 -0
- package/src/Icon.astro +53 -0
- package/src/Image.astro +56 -0
- package/src/Kbd.astro +27 -0
- package/src/Link.astro +56 -0
- package/src/Menu.astro +38 -0
- package/src/NumberInput.astro +86 -0
- package/src/Pagination.astro +53 -0
- package/src/Panel.astro +68 -0
- package/src/Parallax.astro +18 -0
- package/src/Pie.astro +65 -0
- package/src/Progress.astro +96 -0
- package/src/Radio.astro +71 -0
- package/src/RadioGroup.astro +44 -0
- package/src/Section.astro +61 -0
- package/src/Segment.astro +15 -0
- package/src/Segmented.astro +95 -0
- package/src/Select.astro +79 -0
- package/src/Separator.astro +36 -0
- package/src/Skeleton.astro +25 -0
- package/src/Slider.astro +93 -0
- package/src/Sparkline.astro +79 -0
- package/src/Spinner.astro +37 -0
- package/src/Splitter.astro +87 -0
- package/src/Stack.astro +42 -0
- package/src/Stat.astro +70 -0
- package/src/Statusbar.astro +56 -0
- package/src/Swatch.astro +69 -0
- package/src/Switch.astro +89 -0
- package/src/Table.astro +46 -0
- package/src/Tabs.astro +94 -0
- package/src/Text.astro +54 -0
- package/src/Textarea.astro +81 -0
- package/src/Toast.astro +62 -0
- package/src/ToastRegion.astro +39 -0
- package/src/Toc.astro +35 -0
- package/src/Toolbar.astro +68 -0
- package/src/Tooltip.astro +68 -0
- package/src/Tree.astro +78 -0
- package/src/internal/palette.ts +92 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @xtyle/astro
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@xtyle/astro)
|
|
4
|
+
[](https://xtyle.dev)
|
|
5
|
+
[](https://opensource.org/license/mit)
|
|
6
|
+
|
|
7
|
+
Zero-JS Astro 6 components that emit semantic HTML against the xtyle component classes,
|
|
8
|
+
the SSR binding of the [`@xtyle/core`](https://www.npmjs.com/package/@xtyle/core) component
|
|
9
|
+
contract. Each component server-renders the matching markup and ships **no client
|
|
10
|
+
JavaScript** unless that component genuinely needs it.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @xtyle/astro @xtyle/core
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`astro@^6` is a peer dependency; `@xtyle/core` (pulled in automatically) provides the
|
|
19
|
+
markup contract and the derivation engine.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```astro
|
|
24
|
+
---
|
|
25
|
+
import Button from "@xtyle/astro/Button.astro";
|
|
26
|
+
import Card from "@xtyle/astro/Card.astro";
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<Card>
|
|
30
|
+
<Button tone="accent">Save</Button>
|
|
31
|
+
</Card>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Components are styled entirely by **design tokens**, the CSS custom properties a xtyle
|
|
35
|
+
*algorithm* derives. Emit a theme to CSS (via the `xtyle` CLI in `@xtyle/core`) and drop it
|
|
36
|
+
on `:root`; every component themes with it, with nothing running at runtime. See
|
|
37
|
+
[`@xtyle/core`](https://www.npmjs.com/package/@xtyle/core) for deriving a theme, and
|
|
38
|
+
[xtyle.dev](https://xtyle.dev) for the full component reference.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xtyle/astro",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Zero-JS Astro components that emit semantic HTML against the xtyle component classes; the SSR binding of the component contract.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./*": "./src/*"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@xtyle/core": "^0.5.0"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"astro": "^6.0.0"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/nekoyoubi/xtyle.git",
|
|
23
|
+
"directory": "packages/astro"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { type AccordionSection } from "@xtyle/core/markup";
|
|
3
|
+
import { renderFragmentLight } from "@xtyle/core/elements/ssr";
|
|
4
|
+
|
|
5
|
+
type Size = "sm" | "md" | "lg";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
items?: AccordionSection[];
|
|
9
|
+
multiple?: boolean;
|
|
10
|
+
size?: Size;
|
|
11
|
+
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
12
|
+
/** Render zero-JS: emit the declarative shadow but never load the runtime to hydrate it. */
|
|
13
|
+
static?: boolean;
|
|
14
|
+
class?: string;
|
|
15
|
+
/** Any other attribute (`id`, `title`, `data-*`, `aria-*`, …) passes through to the element. */
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
items,
|
|
21
|
+
multiple = false,
|
|
22
|
+
size = "md",
|
|
23
|
+
headingLevel,
|
|
24
|
+
static: isStatic = false,
|
|
25
|
+
class: extra,
|
|
26
|
+
...rest
|
|
27
|
+
} = Astro.props;
|
|
28
|
+
|
|
29
|
+
const sections = items ?? [];
|
|
30
|
+
const uid = `xtyle-accordion-${Math.random().toString(36).slice(2, 9)}`;
|
|
31
|
+
|
|
32
|
+
const openKeys: string[] = [];
|
|
33
|
+
sections.forEach((section, i) => {
|
|
34
|
+
if (!section.open) return;
|
|
35
|
+
const key = section.value ?? String(i);
|
|
36
|
+
if (multiple) openKeys.push(key);
|
|
37
|
+
else if (openKeys.length === 0) openKeys.push(key);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const bindings = {
|
|
41
|
+
sections: sections.map((section, i) => ({
|
|
42
|
+
header: section.header,
|
|
43
|
+
panel: section.panel,
|
|
44
|
+
panelSlot: section.panelSlot,
|
|
45
|
+
value: section.value ?? String(i),
|
|
46
|
+
disabled: section.disabled,
|
|
47
|
+
})),
|
|
48
|
+
openKeys,
|
|
49
|
+
size,
|
|
50
|
+
headingLevel,
|
|
51
|
+
uid,
|
|
52
|
+
};
|
|
53
|
+
// Two authoring modes. items-mode renders the whole structure light from `bindings` and ships no
|
|
54
|
+
// children. The advanced slotted mode pairs the consumer's `slot="header"`/`slot="panel"` children
|
|
55
|
+
// by order, so they must survive *individually* (with their slot attributes) — Astro's `<slot
|
|
56
|
+
// name>` passthrough keeps them intact, and the element builds the structure from them on connect.
|
|
57
|
+
const useItems = sections.length > 0;
|
|
58
|
+
const light = useItems ? await renderFragmentLight("accordion", bindings) : "";
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
{useItems ? (
|
|
62
|
+
<xtyle-accordion
|
|
63
|
+
multiple={multiple || undefined}
|
|
64
|
+
size={size}
|
|
65
|
+
heading-level={headingLevel}
|
|
66
|
+
items={JSON.stringify(items)}
|
|
67
|
+
class={extra}
|
|
68
|
+
set:html={light}
|
|
69
|
+
{...rest}
|
|
70
|
+
/>
|
|
71
|
+
) : (
|
|
72
|
+
<xtyle-accordion multiple={multiple || undefined} size={size} heading-level={headingLevel} class={extra} {...rest}>
|
|
73
|
+
<slot name="header" />
|
|
74
|
+
<slot name="panel" />
|
|
75
|
+
<slot />
|
|
76
|
+
</xtyle-accordion>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
{!isStatic && <script>import "@xtyle/core/elements";</script>}
|
package/src/Alert.astro
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { renderFragmentLight, composeFallbackSlot } from "@xtyle/core/elements/ssr";
|
|
3
|
+
import type { FullTone } from "@xtyle/core";
|
|
4
|
+
|
|
5
|
+
type AlertSeverity = "success" | "warn" | "danger" | "info";
|
|
6
|
+
type AlertVariant = "soft" | "solid";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** Color — any semantic role, accent variant, or named hue. Defaults to the severity color. */
|
|
10
|
+
tone?: FullTone;
|
|
11
|
+
/** Meaning — drives the glyph + politeness, independent of color. Omit for a color-only notice. */
|
|
12
|
+
severity?: AlertSeverity;
|
|
13
|
+
variant?: AlertVariant;
|
|
14
|
+
dismissible?: boolean;
|
|
15
|
+
dismissLabel?: string;
|
|
16
|
+
/** Render zero-JS: emit the resolved light-DOM markup but never load the runtime to hydrate it. */
|
|
17
|
+
static?: boolean;
|
|
18
|
+
class?: string;
|
|
19
|
+
/** Any other attribute (`id`, `title`, `data-*`, `aria-*`, …) passes through to the element. */
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
tone,
|
|
25
|
+
severity,
|
|
26
|
+
variant = "soft",
|
|
27
|
+
dismissible = false,
|
|
28
|
+
dismissLabel = "Dismiss",
|
|
29
|
+
static: isStatic = false,
|
|
30
|
+
class: extra,
|
|
31
|
+
...rest
|
|
32
|
+
} = Astro.props;
|
|
33
|
+
|
|
34
|
+
const bindings = { tone, severity, variant, dismissible, dismissLabel };
|
|
35
|
+
const light = await renderFragmentLight("alert", bindings);
|
|
36
|
+
const iconHtml = Astro.slots.has("icon") ? await Astro.slots.render("icon") : null;
|
|
37
|
+
const titleHtml = Astro.slots.has("title") ? await Astro.slots.render("title") : "";
|
|
38
|
+
const messageHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
|
|
39
|
+
const actionsHtml = Astro.slots.has("actions") ? await Astro.slots.render("actions") : "";
|
|
40
|
+
const composed = composeFallbackSlot(light, "icon", iconHtml)
|
|
41
|
+
.replace('<slot name="title"></slot>', () => titleHtml)
|
|
42
|
+
.replace('<slot name="actions"></slot>', () => actionsHtml)
|
|
43
|
+
.replace("<slot></slot>", () => messageHtml);
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
<xtyle-alert
|
|
47
|
+
tone={tone}
|
|
48
|
+
severity={severity}
|
|
49
|
+
variant={variant}
|
|
50
|
+
dismissible={dismissible || undefined}
|
|
51
|
+
dismiss-label={dismissLabel}
|
|
52
|
+
class={extra}
|
|
53
|
+
set:html={composed}
|
|
54
|
+
{...rest}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
{!isStatic && <script>import "@xtyle/core/elements";</script>}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { renderFragmentLight } from "@xtyle/core/elements/ssr";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
skipLink?: string | boolean;
|
|
6
|
+
/** Initial width of the left rail; sets `--xtyle-app-left` server-side so the grid doesn't reflow on hydration. Number → px. */
|
|
7
|
+
leftSize?: number | string;
|
|
8
|
+
/** Initial width of the right rail; sets `--xtyle-app-right` server-side so the grid doesn't reflow on hydration. Number → px. */
|
|
9
|
+
rightSize?: number | string;
|
|
10
|
+
/** Make the left / right rail user-resizable: a drag handle, arrow-key nudges, and a double-click reset to `leftSize` / `rightSize`. Needs the hydrated runtime. */
|
|
11
|
+
leftResizable?: boolean;
|
|
12
|
+
rightResizable?: boolean;
|
|
13
|
+
/** Clamp for the resizable rails, in px. */
|
|
14
|
+
leftMin?: number;
|
|
15
|
+
leftMax?: number;
|
|
16
|
+
rightMin?: number;
|
|
17
|
+
rightMax?: number;
|
|
18
|
+
/** The `id` of the `<main>` region and the skip link's target. Override when more than one shell renders on a page so the ids stay unique. */
|
|
19
|
+
mainId?: string;
|
|
20
|
+
/** Render zero-JS: emit the declarative shadow but never load the runtime to hydrate it. */
|
|
21
|
+
static?: boolean;
|
|
22
|
+
class?: string;
|
|
23
|
+
/** Any other attribute (`id`, `title`, `data-*`, `aria-*`, …) passes through to the element. */
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
skipLink,
|
|
29
|
+
leftSize,
|
|
30
|
+
rightSize,
|
|
31
|
+
leftResizable,
|
|
32
|
+
rightResizable,
|
|
33
|
+
leftMin,
|
|
34
|
+
leftMax,
|
|
35
|
+
rightMin,
|
|
36
|
+
rightMax,
|
|
37
|
+
mainId = "main",
|
|
38
|
+
static: isStatic = false,
|
|
39
|
+
class: extra,
|
|
40
|
+
...rest
|
|
41
|
+
} = Astro.props;
|
|
42
|
+
|
|
43
|
+
const hasLeft = Astro.slots.has("left");
|
|
44
|
+
const hasRight = Astro.slots.has("right");
|
|
45
|
+
const hasToolbar = Astro.slots.has("toolbar");
|
|
46
|
+
const hasStatusbar = Astro.slots.has("statusbar");
|
|
47
|
+
const showSkip = skipLink !== undefined;
|
|
48
|
+
const skipText = typeof skipLink === "string" && skipLink.length > 0 ? skipLink : "Skip to main content";
|
|
49
|
+
|
|
50
|
+
const railSize = (v: number | string): string => (typeof v === "number" ? `${v}px` : v);
|
|
51
|
+
const bodyStyle =
|
|
52
|
+
[
|
|
53
|
+
leftSize !== undefined ? `--xtyle-app-left: ${railSize(leftSize)}` : null,
|
|
54
|
+
rightSize !== undefined ? `--xtyle-app-right: ${railSize(rightSize)}` : null,
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join("; ") || null;
|
|
58
|
+
|
|
59
|
+
const bindings = {
|
|
60
|
+
skipLink: showSkip,
|
|
61
|
+
skipLinkText: skipText,
|
|
62
|
+
bodyStyle,
|
|
63
|
+
mainId,
|
|
64
|
+
leftResizable: Boolean(leftResizable),
|
|
65
|
+
rightResizable: Boolean(rightResizable),
|
|
66
|
+
};
|
|
67
|
+
const light = await renderFragmentLight("app-shell", bindings);
|
|
68
|
+
const toolbarHtml = hasToolbar ? await Astro.slots.render("toolbar") : "";
|
|
69
|
+
const leftHtml = hasLeft ? await Astro.slots.render("left") : "";
|
|
70
|
+
const rightHtml = hasRight ? await Astro.slots.render("right") : "";
|
|
71
|
+
const statusbarHtml = hasStatusbar ? await Astro.slots.render("statusbar") : "";
|
|
72
|
+
const defaultHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
|
|
73
|
+
const composed = light
|
|
74
|
+
.replace('<slot name="toolbar"></slot>', () => toolbarHtml)
|
|
75
|
+
.replace('<slot name="left"></slot>', () => leftHtml)
|
|
76
|
+
.replace('<slot name="right"></slot>', () => rightHtml)
|
|
77
|
+
.replace('<slot name="statusbar"></slot>', () => statusbarHtml)
|
|
78
|
+
.replace("<slot></slot>", () => defaultHtml);
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
<xtyle-app-shell
|
|
82
|
+
skip-link={typeof skipLink === "string" ? skipLink : showSkip ? "" : undefined}
|
|
83
|
+
left-size={leftSize === undefined ? undefined : String(leftSize)}
|
|
84
|
+
right-size={rightSize === undefined ? undefined : String(rightSize)}
|
|
85
|
+
left-resizable={leftResizable ? "" : undefined}
|
|
86
|
+
right-resizable={rightResizable ? "" : undefined}
|
|
87
|
+
left-min={leftMin === undefined ? undefined : String(leftMin)}
|
|
88
|
+
left-max={leftMax === undefined ? undefined : String(leftMax)}
|
|
89
|
+
right-min={rightMin === undefined ? undefined : String(rightMin)}
|
|
90
|
+
right-max={rightMax === undefined ? undefined : String(rightMax)}
|
|
91
|
+
main-id={mainId === "main" ? undefined : mainId}
|
|
92
|
+
class={extra}
|
|
93
|
+
set:html={composed}
|
|
94
|
+
{...rest}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{!isStatic && <script>import "@xtyle/core/elements";</script>}
|
package/src/Avatar.astro
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { renderFragmentLight } from "@xtyle/core/elements/ssr";
|
|
3
|
+
|
|
4
|
+
import type { Tone, FullTone as BadgeTone } from "@xtyle/core";
|
|
5
|
+
type AvatarSize = "sm" | "md" | "lg" | "xl";
|
|
6
|
+
type AvatarShape = "circle" | "square";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
src?: string;
|
|
10
|
+
alt?: string;
|
|
11
|
+
tone?: BadgeTone;
|
|
12
|
+
size?: AvatarSize;
|
|
13
|
+
shape?: AvatarShape;
|
|
14
|
+
status?: Tone;
|
|
15
|
+
statusLabel?: string;
|
|
16
|
+
/** Breathe the status dot for a live / online presence: a bare `true` pulses slow, `"fast"` quick. */
|
|
17
|
+
pulse?: boolean | "slow" | "fast";
|
|
18
|
+
/** Render zero-JS: emit the resolved light-DOM markup but never load the runtime to hydrate it. */
|
|
19
|
+
static?: boolean;
|
|
20
|
+
class?: string;
|
|
21
|
+
/** Any other attribute (`id`, `title`, `data-*`, `aria-*`, …) passes through to the element. */
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
src,
|
|
27
|
+
alt = "",
|
|
28
|
+
tone = "neutral",
|
|
29
|
+
size = "md",
|
|
30
|
+
shape = "circle",
|
|
31
|
+
status,
|
|
32
|
+
statusLabel,
|
|
33
|
+
pulse,
|
|
34
|
+
static: isStatic = false,
|
|
35
|
+
class: extra,
|
|
36
|
+
...rest
|
|
37
|
+
} = Astro.props;
|
|
38
|
+
|
|
39
|
+
const bindings = {
|
|
40
|
+
src: src ?? null,
|
|
41
|
+
alt,
|
|
42
|
+
tone,
|
|
43
|
+
size,
|
|
44
|
+
shape,
|
|
45
|
+
status: status ?? null,
|
|
46
|
+
statusLabel: statusLabel ?? null,
|
|
47
|
+
pulse: pulse ?? null,
|
|
48
|
+
};
|
|
49
|
+
const chrome = await renderFragmentLight("avatar", bindings);
|
|
50
|
+
const iconHtml = Astro.slots.has("icon") ? await Astro.slots.render("icon") : "";
|
|
51
|
+
const bodyHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
|
|
52
|
+
const composed = chrome
|
|
53
|
+
.replace('<slot name="icon"></slot>', () => iconHtml)
|
|
54
|
+
.replace("<slot></slot>", () => bodyHtml);
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
<xtyle-avatar
|
|
58
|
+
src={src ?? undefined}
|
|
59
|
+
alt={alt}
|
|
60
|
+
tone={tone}
|
|
61
|
+
size={size}
|
|
62
|
+
shape={shape}
|
|
63
|
+
status={status ?? undefined}
|
|
64
|
+
status-label={statusLabel ?? undefined}
|
|
65
|
+
pulse={pulse === true ? "" : pulse || undefined}
|
|
66
|
+
class={extra}
|
|
67
|
+
set:html={composed}
|
|
68
|
+
{...rest}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
{!isStatic && <script>import "@xtyle/core/elements";</script>}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { renderFragmentLight } from "@xtyle/core/elements/ssr";
|
|
3
|
+
|
|
4
|
+
type AvatarGroupSize = "sm" | "md" | "lg" | "xl";
|
|
5
|
+
type AvatarGroupSpacing = "snug" | "normal" | "loose";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
size?: AvatarGroupSize;
|
|
9
|
+
spacing?: AvatarGroupSpacing;
|
|
10
|
+
/** How many avatars beyond the shown ones to summarize as a trailing `+N` chip. */
|
|
11
|
+
overflow?: number;
|
|
12
|
+
/** Accessible name for the group (a list of people). */
|
|
13
|
+
label?: string;
|
|
14
|
+
/** Render zero-JS: emit the resolved light-DOM markup but never load the runtime to hydrate it. */
|
|
15
|
+
static?: boolean;
|
|
16
|
+
class?: string;
|
|
17
|
+
/** Any other attribute (`id`, `title`, `data-*`, `aria-*`, …) passes through to the element. */
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { size, spacing, overflow, label, static: isStatic = false, class: extra, ...rest } = Astro.props;
|
|
22
|
+
|
|
23
|
+
const bindings = {
|
|
24
|
+
size: size ?? null,
|
|
25
|
+
spacing: spacing ?? null,
|
|
26
|
+
overflow: overflow ?? 0,
|
|
27
|
+
label: label ?? null,
|
|
28
|
+
};
|
|
29
|
+
const chrome = await renderFragmentLight("avatar-group", bindings);
|
|
30
|
+
const slotHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
|
|
31
|
+
const composed = chrome.replace("<slot></slot>", () => slotHtml);
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<xtyle-avatar-group
|
|
35
|
+
size={size}
|
|
36
|
+
spacing={spacing}
|
|
37
|
+
overflow={overflow ?? undefined}
|
|
38
|
+
label={label}
|
|
39
|
+
class={extra}
|
|
40
|
+
set:html={composed}
|
|
41
|
+
{...rest}
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
{!isStatic && <script>import "@xtyle/core/elements";</script>}
|
package/src/Badge.astro
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { renderFragmentLight } from "@xtyle/core/elements/ssr";
|
|
3
|
+
|
|
4
|
+
import type { FullTone as BadgeTone } from "@xtyle/core";
|
|
5
|
+
type Size = "sm" | "md" | "lg";
|
|
6
|
+
type BadgeVariant = "solid" | "soft" | "outline";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
variant?: BadgeVariant;
|
|
10
|
+
tone?: BadgeTone;
|
|
11
|
+
size?: Size;
|
|
12
|
+
dot?: boolean;
|
|
13
|
+
pulse?: boolean | "slow" | "fast";
|
|
14
|
+
count?: string | number;
|
|
15
|
+
removable?: boolean;
|
|
16
|
+
removeLabel?: string;
|
|
17
|
+
/** Render zero-JS: emit the resolved light-DOM markup but never load the runtime to hydrate it. */
|
|
18
|
+
static?: boolean;
|
|
19
|
+
class?: string;
|
|
20
|
+
/** Any other attribute (`id`, `title`, `data-*`, `aria-*`, …) passes through to the element. */
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
variant = "soft",
|
|
26
|
+
tone = "neutral",
|
|
27
|
+
size = "md",
|
|
28
|
+
dot = false,
|
|
29
|
+
pulse,
|
|
30
|
+
count,
|
|
31
|
+
removable = false,
|
|
32
|
+
removeLabel,
|
|
33
|
+
static: isStatic = false,
|
|
34
|
+
class: extra,
|
|
35
|
+
...rest
|
|
36
|
+
} = Astro.props;
|
|
37
|
+
|
|
38
|
+
const slotHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
|
|
39
|
+
// Compose the dismiss button's accessible name from the label text when the consumer didn't name
|
|
40
|
+
// it — so a zero-JS (`static`) removable badge still announces "Remove <text>" instead of a bare
|
|
41
|
+
// "Remove". The runtime composes this client-side from the slot, but `static` never loads it.
|
|
42
|
+
const slotText = slotHtml.replace(/<[^>]*>/g, "").trim();
|
|
43
|
+
const resolvedRemoveLabel = removeLabel ?? (removable && slotText ? `Remove ${slotText}` : undefined);
|
|
44
|
+
|
|
45
|
+
const bindings = {
|
|
46
|
+
variant,
|
|
47
|
+
tone,
|
|
48
|
+
size,
|
|
49
|
+
dot,
|
|
50
|
+
pulse: pulse === true ? "slow" : pulse || null,
|
|
51
|
+
count: count ?? null,
|
|
52
|
+
removable,
|
|
53
|
+
removeLabel: resolvedRemoveLabel,
|
|
54
|
+
};
|
|
55
|
+
const chrome = await renderFragmentLight("badge", bindings);
|
|
56
|
+
const composed = chrome.replace("<slot></slot>", () => slotHtml);
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<xtyle-badge
|
|
60
|
+
variant={variant}
|
|
61
|
+
tone={tone}
|
|
62
|
+
size={size}
|
|
63
|
+
dot={dot || undefined}
|
|
64
|
+
pulse={pulse === true ? "slow" : pulse || undefined}
|
|
65
|
+
count={count !== undefined && count !== null && count !== "" ? String(count) : undefined}
|
|
66
|
+
removable={removable || undefined}
|
|
67
|
+
remove-label={resolvedRemoveLabel}
|
|
68
|
+
class={extra}
|
|
69
|
+
set:html={composed}
|
|
70
|
+
{...rest}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
{!isStatic && <script>import "@xtyle/core/elements";</script>}
|
package/src/Bar.astro
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { renderFragmentLight } from "@xtyle/core/elements/ssr";
|
|
3
|
+
import { resolveSeriesColorsFor } from "./internal/palette.ts";
|
|
4
|
+
import type { BarSeries, BarScheme } from "@xtyle/core";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
series?: BarSeries[];
|
|
8
|
+
categories?: string[];
|
|
9
|
+
scheme?: BarScheme;
|
|
10
|
+
reverse?: boolean;
|
|
11
|
+
colorBy?: "series" | "category";
|
|
12
|
+
orientation?: "vertical" | "horizontal";
|
|
13
|
+
stacked?: boolean;
|
|
14
|
+
showValues?: boolean;
|
|
15
|
+
legend?: boolean;
|
|
16
|
+
height?: number;
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Make bars actionable: each becomes a `role="button"` that fires `select` on click or Enter/Space. */
|
|
19
|
+
selectable?: boolean;
|
|
20
|
+
/** Render zero-JS: emit the resolved light-DOM markup but never load the runtime to hydrate it. */
|
|
21
|
+
static?: boolean;
|
|
22
|
+
class?: string;
|
|
23
|
+
/** Any other attribute (`id`, `title`, `data-*`, `aria-*`, …) passes through to the element. */
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
series = [],
|
|
29
|
+
categories = [],
|
|
30
|
+
scheme = "accents",
|
|
31
|
+
reverse = false,
|
|
32
|
+
colorBy,
|
|
33
|
+
orientation = "vertical",
|
|
34
|
+
stacked = false,
|
|
35
|
+
showValues = false,
|
|
36
|
+
legend = true,
|
|
37
|
+
height = 320,
|
|
38
|
+
label,
|
|
39
|
+
selectable = false,
|
|
40
|
+
static: isStatic = false,
|
|
41
|
+
class: extra,
|
|
42
|
+
...rest
|
|
43
|
+
} = Astro.props;
|
|
44
|
+
|
|
45
|
+
const resolvedColorBy = colorBy ?? (series.length <= 1 ? "category" : "series");
|
|
46
|
+
const colorItems = resolvedColorBy === "category" ? categories.map(() => ({})) : series;
|
|
47
|
+
const colors = await resolveSeriesColorsFor(scheme, colorItems, reverse);
|
|
48
|
+
const bindings = {
|
|
49
|
+
series,
|
|
50
|
+
categories,
|
|
51
|
+
colors,
|
|
52
|
+
colorBy: resolvedColorBy,
|
|
53
|
+
orientation,
|
|
54
|
+
stacked,
|
|
55
|
+
showValues,
|
|
56
|
+
legend,
|
|
57
|
+
height,
|
|
58
|
+
title: label ?? null,
|
|
59
|
+
ariaLabel: label ?? null,
|
|
60
|
+
selectable,
|
|
61
|
+
};
|
|
62
|
+
const chrome = await renderFragmentLight("bar", bindings);
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
<xtyle-bar
|
|
66
|
+
series={JSON.stringify(series)}
|
|
67
|
+
categories={JSON.stringify(categories)}
|
|
68
|
+
scheme={Array.isArray(scheme) ? JSON.stringify(scheme) : scheme}
|
|
69
|
+
reverse={reverse || undefined}
|
|
70
|
+
color-by={colorBy}
|
|
71
|
+
orientation={orientation === "horizontal" ? "horizontal" : undefined}
|
|
72
|
+
stacked={stacked || undefined}
|
|
73
|
+
show-values={showValues || undefined}
|
|
74
|
+
legend={legend ? undefined : "false"}
|
|
75
|
+
selectable={selectable || undefined}
|
|
76
|
+
height={height}
|
|
77
|
+
label={label}
|
|
78
|
+
class={extra}
|
|
79
|
+
set:html={chrome}
|
|
80
|
+
{...rest}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
{!isStatic && <script>import "@xtyle/core/elements";</script>}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { type BreadcrumbItem } from "@xtyle/core/markup";
|
|
3
|
+
import { renderFragmentLight } from "@xtyle/core/elements/ssr";
|
|
4
|
+
import type { Size, FullTone } from "@xtyle/core";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
items?: BreadcrumbItem[];
|
|
8
|
+
separator?: string;
|
|
9
|
+
tone?: FullTone;
|
|
10
|
+
size?: Size;
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Render zero-JS: emit the resolved light-DOM markup but never load the runtime to hydrate it. */
|
|
13
|
+
static?: boolean;
|
|
14
|
+
class?: string;
|
|
15
|
+
/** Any other attribute (`id`, `title`, `data-*`, `aria-*`, …) passes through to the element. */
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
items,
|
|
21
|
+
separator = "/",
|
|
22
|
+
tone = "accent",
|
|
23
|
+
size = "md",
|
|
24
|
+
label = "Breadcrumb",
|
|
25
|
+
static: isStatic = false,
|
|
26
|
+
class: extra,
|
|
27
|
+
...rest
|
|
28
|
+
} = Astro.props;
|
|
29
|
+
|
|
30
|
+
const bindings = {
|
|
31
|
+
items: items ?? [],
|
|
32
|
+
separator,
|
|
33
|
+
tone,
|
|
34
|
+
size,
|
|
35
|
+
label,
|
|
36
|
+
};
|
|
37
|
+
const light = await renderFragmentLight("breadcrumb", bindings);
|
|
38
|
+
// only the no-items (consumer-slotted) render carries a marker; the items render fills the list
|
|
39
|
+
const slotHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
|
|
40
|
+
const composed = light.replace("<slot></slot>", () => slotHtml);
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<xtyle-breadcrumb
|
|
44
|
+
items={items ? JSON.stringify(items) : undefined}
|
|
45
|
+
separator={separator}
|
|
46
|
+
tone={tone}
|
|
47
|
+
size={size}
|
|
48
|
+
label={label}
|
|
49
|
+
class={extra}
|
|
50
|
+
set:html={composed}
|
|
51
|
+
{...rest}
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
{!isStatic && <script>import "@xtyle/core/elements";</script>}
|