@db-vincent/components 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +28 -4
- package/src/astro/Accordion.astro +37 -0
- package/src/astro/Alert.astro +73 -0
- package/src/astro/Badge.astro +23 -0
- package/src/astro/Breadcrumbs.astro +45 -0
- package/src/astro/Button.astro +125 -0
- package/src/astro/Card.astro +63 -0
- package/src/astro/Checkbox.astro +61 -0
- package/src/astro/Chip.astro +116 -0
- package/src/astro/CodeBlock.astro +17 -0
- package/src/astro/EmptyState.astro +45 -0
- package/src/astro/Example.astro +37 -0
- package/src/astro/Grid.astro +50 -0
- package/src/astro/GridItem.astro +23 -0
- package/src/astro/Icon.astro +52 -0
- package/src/astro/Modal.astro +117 -0
- package/src/astro/Navbar.astro +756 -0
- package/src/astro/PageHeader.astro +34 -0
- package/src/astro/Pagination.astro +162 -0
- package/src/astro/Radio.astro +58 -0
- package/src/astro/RadioGroup.astro +25 -0
- package/src/astro/Section.astro +25 -0
- package/src/astro/Select.astro +403 -0
- package/src/astro/Sidebar.astro +215 -0
- package/src/astro/Skeleton.astro +39 -0
- package/src/astro/Spinner.astro +19 -0
- package/src/astro/Table.astro +254 -0
- package/src/astro/TextField.astro +287 -0
- package/src/astro/ThemeToggle.astro +66 -0
- package/src/astro/Toast.astro +181 -0
- package/src/astro/TopBar.astro +29 -0
- package/src/data/icons.ts +104 -0
- package/src/scripts/toast.ts +531 -0
- package/src/styles/accordion.css +77 -0
- package/src/styles/alert.css +106 -0
- package/src/styles/badge.css +46 -0
- package/src/styles/base.css +203 -0
- package/src/styles/breadcrumbs.css +52 -0
- package/src/styles/button.css +163 -0
- package/src/styles/card.css +108 -0
- package/src/styles/chip.css +104 -0
- package/src/styles/choice.css +137 -0
- package/src/styles/code.css +42 -0
- package/src/styles/empty.css +57 -0
- package/src/styles/example.css +55 -0
- package/src/styles/grid.css +62 -0
- package/src/styles/input.css +198 -0
- package/src/styles/modal.css +143 -0
- package/src/styles/motion.css +96 -0
- package/src/styles/nav.css +230 -0
- package/src/styles/navbar.css +656 -0
- package/src/styles/page-header.css +59 -0
- package/src/styles/pagination.css +77 -0
- package/src/styles/section.css +36 -0
- package/src/styles/select.css +235 -0
- package/src/styles/skeleton.css +64 -0
- package/src/styles/spinner.css +42 -0
- package/src/styles/table.css +175 -0
- package/src/styles/theme-toggle.css +29 -0
- package/src/styles/toast.css +207 -0
- package/src/styles/topbar.css +30 -0
- package/README.md +0 -3
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
import "../styles/grid.css";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
columns?: number;
|
|
6
|
+
phoneColumns?: number;
|
|
7
|
+
gap?: "sm" | "md" | "lg";
|
|
8
|
+
auto?: boolean;
|
|
9
|
+
masonry?: boolean;
|
|
10
|
+
minItem?: string;
|
|
11
|
+
container?: boolean;
|
|
12
|
+
as?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
columns = 12,
|
|
18
|
+
phoneColumns = 1,
|
|
19
|
+
gap = "md",
|
|
20
|
+
auto = false,
|
|
21
|
+
masonry = false,
|
|
22
|
+
minItem = "16rem",
|
|
23
|
+
container = false,
|
|
24
|
+
as: Tag = "div",
|
|
25
|
+
class: className,
|
|
26
|
+
} = Astro.props;
|
|
27
|
+
|
|
28
|
+
const style = `--grid-columns:${columns};--grid-columns-phone:${phoneColumns};--grid-min:${minItem};--grid-masonry-columns:${columns}`;
|
|
29
|
+
const classes = [
|
|
30
|
+
"ds-grid",
|
|
31
|
+
gap !== "md" && `ds-grid--gap-${gap}`,
|
|
32
|
+
auto && "ds-grid--auto",
|
|
33
|
+
masonry && "ds-grid--masonry",
|
|
34
|
+
className,
|
|
35
|
+
]
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join(" ");
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
{container ? (
|
|
41
|
+
<div class="ds-grid__container">
|
|
42
|
+
<Tag class={classes} style={style}>
|
|
43
|
+
<slot />
|
|
44
|
+
</Tag>
|
|
45
|
+
</div>
|
|
46
|
+
) : (
|
|
47
|
+
<Tag class={classes} style={style}>
|
|
48
|
+
<slot />
|
|
49
|
+
</Tag>
|
|
50
|
+
)}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
import "../styles/grid.css";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
span?: number;
|
|
6
|
+
phoneSpan?: number;
|
|
7
|
+
as?: string;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
span = 1,
|
|
13
|
+
phoneSpan = 1,
|
|
14
|
+
as: Tag = "div",
|
|
15
|
+
class: className,
|
|
16
|
+
} = Astro.props;
|
|
17
|
+
|
|
18
|
+
const style = `--grid-item-span:${span};--grid-item-span-phone:${phoneSpan}`;
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<Tag class:list={["ds-grid__item", className]} style={style}>
|
|
22
|
+
<slot />
|
|
23
|
+
</Tag>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Functional icon set. Decorative by default (aria-hidden). Pass `label` only
|
|
4
|
+
* when the icon is the entire accessible name, e.g. an icon-only button.
|
|
5
|
+
* Icons are 24×24, stroke-based, and inherit the current text color.
|
|
6
|
+
*/
|
|
7
|
+
import { ICONS } from "../data/icons";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
name: string;
|
|
11
|
+
size?: number | string;
|
|
12
|
+
stroke?: number;
|
|
13
|
+
label?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { name, size = 16, stroke, label, class: className } = Astro.props;
|
|
18
|
+
|
|
19
|
+
/* Three sizes carry the whole interface: 14 (inline with small text and
|
|
20
|
+
inside compact controls), 16 (default), 20 (status and dialog chrome).
|
|
21
|
+
24 is reserved for empty states. The stroke is derived from the size so
|
|
22
|
+
every icon renders at the same optical weight (about 1.2-1.5px). */
|
|
23
|
+
const px = typeof size === "number" ? size : Number.parseFloat(size) || 16;
|
|
24
|
+
const OPTICAL_STROKE: Record<number, number> = {
|
|
25
|
+
14: 2,
|
|
26
|
+
16: 1.75,
|
|
27
|
+
20: 1.6,
|
|
28
|
+
24: 1.5,
|
|
29
|
+
};
|
|
30
|
+
const strokeWidth = stroke ?? OPTICAL_STROKE[px] ?? (px <= 14 ? 2 : 1.6);
|
|
31
|
+
|
|
32
|
+
const paths = ICONS[name];
|
|
33
|
+
if (!paths) {
|
|
34
|
+
throw new Error(`Icon: unknown name "${name}".`);
|
|
35
|
+
}
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
<svg
|
|
39
|
+
class:list={["ds-icon", className]}
|
|
40
|
+
width={size}
|
|
41
|
+
height={size}
|
|
42
|
+
viewBox="0 0 24 24"
|
|
43
|
+
fill="none"
|
|
44
|
+
stroke="currentColor"
|
|
45
|
+
stroke-width={strokeWidth}
|
|
46
|
+
stroke-linecap="round"
|
|
47
|
+
stroke-linejoin="round"
|
|
48
|
+
role={label ? "img" : undefined}
|
|
49
|
+
aria-label={label}
|
|
50
|
+
aria-hidden={label ? undefined : "true"}
|
|
51
|
+
set:html={paths}
|
|
52
|
+
/>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Icon from "./Icon.astro";
|
|
3
|
+
import Button from "./Button.astro";
|
|
4
|
+
import "../styles/modal.css";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Modal dialog on native <dialog>. Native showModal() supplies the focus trap,
|
|
8
|
+
* Escape handling and inert background. The trigger is wired with ARIA; focus
|
|
9
|
+
* returns to the trigger on close automatically.
|
|
10
|
+
*/
|
|
11
|
+
interface Props {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
triggerLabel?: string;
|
|
16
|
+
triggerVariant?: "primary" | "secondary" | "ghost" | "danger";
|
|
17
|
+
size?: "sm" | "md" | "lg";
|
|
18
|
+
variant?: "dialog" | "sheet";
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
id,
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
triggerLabel = "Open dialog",
|
|
27
|
+
triggerVariant = "secondary",
|
|
28
|
+
size = "md",
|
|
29
|
+
variant = "dialog",
|
|
30
|
+
class: className,
|
|
31
|
+
} = Astro.props;
|
|
32
|
+
|
|
33
|
+
const titleId = `${id}-title`;
|
|
34
|
+
const descriptionId = description ? `${id}-description` : undefined;
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
<Button
|
|
38
|
+
variant={triggerVariant}
|
|
39
|
+
type="button"
|
|
40
|
+
data-modal-open={id}
|
|
41
|
+
aria-haspopup="dialog"
|
|
42
|
+
>
|
|
43
|
+
{triggerLabel}
|
|
44
|
+
</Button>
|
|
45
|
+
|
|
46
|
+
<dialog
|
|
47
|
+
class:list={[
|
|
48
|
+
"ds-modal",
|
|
49
|
+
size !== "md" && `ds-modal--${size}`,
|
|
50
|
+
variant === "sheet" && "ds-modal--sheet",
|
|
51
|
+
className,
|
|
52
|
+
]}
|
|
53
|
+
id={id}
|
|
54
|
+
aria-labelledby={titleId}
|
|
55
|
+
aria-describedby={descriptionId}
|
|
56
|
+
>
|
|
57
|
+
<div class="ds-modal__header">
|
|
58
|
+
<div>
|
|
59
|
+
<h2 class="ds-modal__title" id={titleId}>
|
|
60
|
+
{title}
|
|
61
|
+
</h2>
|
|
62
|
+
{description && (
|
|
63
|
+
<p class="ds-modal__description" id={descriptionId}>
|
|
64
|
+
{description}
|
|
65
|
+
</p>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
<form method="dialog">
|
|
69
|
+
<button class="ds-modal__close" type="submit" aria-label="Close dialog">
|
|
70
|
+
<Icon name="x" size={20} />
|
|
71
|
+
</button>
|
|
72
|
+
</form>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="ds-modal__body">
|
|
75
|
+
<slot />
|
|
76
|
+
</div>
|
|
77
|
+
<slot name="footer" />
|
|
78
|
+
</dialog>
|
|
79
|
+
|
|
80
|
+
<script>
|
|
81
|
+
const bind = () => {
|
|
82
|
+
document
|
|
83
|
+
.querySelectorAll<HTMLElement>("[data-modal-open]")
|
|
84
|
+
.forEach((trigger) => {
|
|
85
|
+
if (trigger.dataset.bound) return;
|
|
86
|
+
trigger.dataset.bound = "true";
|
|
87
|
+
const id = trigger.dataset.modalOpen;
|
|
88
|
+
const dialog = id
|
|
89
|
+
? (document.getElementById(id) as HTMLDialogElement | null)
|
|
90
|
+
: null;
|
|
91
|
+
if (!dialog) return;
|
|
92
|
+
trigger.addEventListener("click", () => {
|
|
93
|
+
dialog.showModal();
|
|
94
|
+
// Land on an explicit [autofocus], else the first footer action
|
|
95
|
+
// (Cancel in a destructive dialog), never on the close icon.
|
|
96
|
+
dialog
|
|
97
|
+
.querySelector<HTMLElement>(
|
|
98
|
+
"[autofocus], .ds-modal__footer button, .ds-modal__footer a",
|
|
99
|
+
)
|
|
100
|
+
?.focus();
|
|
101
|
+
});
|
|
102
|
+
dialog.addEventListener("click", (event) => {
|
|
103
|
+
if (event.target !== dialog) return;
|
|
104
|
+
const rect = dialog.getBoundingClientRect();
|
|
105
|
+
const inside =
|
|
106
|
+
event.clientX >= rect.left &&
|
|
107
|
+
event.clientX <= rect.right &&
|
|
108
|
+
event.clientY >= rect.top &&
|
|
109
|
+
event.clientY <= rect.bottom;
|
|
110
|
+
if (!inside) dialog.close();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
bind();
|
|
116
|
+
document.addEventListener("astro:page-load", bind);
|
|
117
|
+
</script>
|