@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.
Files changed (62) hide show
  1. package/package.json +28 -4
  2. package/src/astro/Accordion.astro +37 -0
  3. package/src/astro/Alert.astro +73 -0
  4. package/src/astro/Badge.astro +23 -0
  5. package/src/astro/Breadcrumbs.astro +45 -0
  6. package/src/astro/Button.astro +125 -0
  7. package/src/astro/Card.astro +63 -0
  8. package/src/astro/Checkbox.astro +61 -0
  9. package/src/astro/Chip.astro +116 -0
  10. package/src/astro/CodeBlock.astro +17 -0
  11. package/src/astro/EmptyState.astro +45 -0
  12. package/src/astro/Example.astro +37 -0
  13. package/src/astro/Grid.astro +50 -0
  14. package/src/astro/GridItem.astro +23 -0
  15. package/src/astro/Icon.astro +52 -0
  16. package/src/astro/Modal.astro +117 -0
  17. package/src/astro/Navbar.astro +756 -0
  18. package/src/astro/PageHeader.astro +34 -0
  19. package/src/astro/Pagination.astro +162 -0
  20. package/src/astro/Radio.astro +58 -0
  21. package/src/astro/RadioGroup.astro +25 -0
  22. package/src/astro/Section.astro +25 -0
  23. package/src/astro/Select.astro +403 -0
  24. package/src/astro/Sidebar.astro +215 -0
  25. package/src/astro/Skeleton.astro +39 -0
  26. package/src/astro/Spinner.astro +19 -0
  27. package/src/astro/Table.astro +254 -0
  28. package/src/astro/TextField.astro +287 -0
  29. package/src/astro/ThemeToggle.astro +66 -0
  30. package/src/astro/Toast.astro +181 -0
  31. package/src/astro/TopBar.astro +29 -0
  32. package/src/data/icons.ts +104 -0
  33. package/src/scripts/toast.ts +531 -0
  34. package/src/styles/accordion.css +77 -0
  35. package/src/styles/alert.css +106 -0
  36. package/src/styles/badge.css +46 -0
  37. package/src/styles/base.css +203 -0
  38. package/src/styles/breadcrumbs.css +52 -0
  39. package/src/styles/button.css +163 -0
  40. package/src/styles/card.css +108 -0
  41. package/src/styles/chip.css +104 -0
  42. package/src/styles/choice.css +137 -0
  43. package/src/styles/code.css +42 -0
  44. package/src/styles/empty.css +57 -0
  45. package/src/styles/example.css +55 -0
  46. package/src/styles/grid.css +62 -0
  47. package/src/styles/input.css +198 -0
  48. package/src/styles/modal.css +143 -0
  49. package/src/styles/motion.css +96 -0
  50. package/src/styles/nav.css +230 -0
  51. package/src/styles/navbar.css +656 -0
  52. package/src/styles/page-header.css +59 -0
  53. package/src/styles/pagination.css +77 -0
  54. package/src/styles/section.css +36 -0
  55. package/src/styles/select.css +235 -0
  56. package/src/styles/skeleton.css +64 -0
  57. package/src/styles/spinner.css +42 -0
  58. package/src/styles/table.css +175 -0
  59. package/src/styles/theme-toggle.css +29 -0
  60. package/src/styles/toast.css +207 -0
  61. package/src/styles/topbar.css +30 -0
  62. 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>