@pienter/ui 0.3.0 → 0.7.1

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 (109) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/CONVENTIONS.md +342 -385
  3. package/README.md +33 -18
  4. package/components/display/record-details/RecordDetails.vue +61 -0
  5. package/components/display/record-details/record-details.css +37 -0
  6. package/components/display/record-details/types.ts +8 -0
  7. package/components/feedback/toast/toast.css +1 -1
  8. package/components/form/block-editor/BlockEditor.vue +454 -0
  9. package/components/form/block-editor/block-editor.css +149 -0
  10. package/components/form/block-editor/types.ts +15 -0
  11. package/components/form/combobox/Combobox.vue +42 -72
  12. package/components/form/combobox/combobox.css +1 -1
  13. package/components/form/form/Form.vue +1 -2
  14. package/components/form/label/label.css +1 -1
  15. package/components/form/number-field/NumberField.vue +1 -2
  16. package/components/form/number-field/number-field.css +1 -1
  17. package/components/form/radio-group/RadioGroup.vue +2 -5
  18. package/components/form/record-form/RecordFields.vue +128 -0
  19. package/components/form/record-form/RecordForm.vue +116 -0
  20. package/components/form/record-form/fields.ts +20 -0
  21. package/components/form/record-form/record-form.css +15 -0
  22. package/components/form/record-form/types.ts +28 -0
  23. package/components/form/slider/slider.css +2 -3
  24. package/components/form/tags-input/tags-input.css +1 -2
  25. package/components/form/text-input/text-input.css +2 -0
  26. package/components/form/textarea/textarea.css +1 -1
  27. package/components/layout/accordion/Accordion.vue +6 -27
  28. package/components/layout/collapsible/Collapsible.vue +9 -19
  29. package/components/layout/index/Index.vue +353 -0
  30. package/components/layout/index/index.css +114 -0
  31. package/components/layout/index/useIndex.ts +390 -0
  32. package/components/layout/table/table.css +5 -4
  33. package/components/navigation/breadcrumb/Breadcrumb.vue +24 -5
  34. package/components/navigation/breadcrumb/breadcrumb.css +15 -0
  35. package/components/navigation/sidebar/Sidebar.vue +329 -84
  36. package/components/navigation/sidebar/SidebarMenuItem.vue +199 -0
  37. package/components/navigation/sidebar/context.ts +21 -0
  38. package/components/navigation/sidebar/sidebar.css +346 -78
  39. package/components/navigation/sidebar/types.ts +13 -1
  40. package/components/navigation/tabs/Tabs.vue +6 -0
  41. package/components/overlay/alert-dialog/AlertDialog.vue +10 -31
  42. package/components/overlay/command/Command.vue +10 -38
  43. package/components/overlay/command/command.css +1 -1
  44. package/components/overlay/dropdown-menu/DropdownMenu.vue +37 -62
  45. package/components/overlay/modal/Modal.vue +7 -28
  46. package/components/overlay/popover/Popover.vue +9 -31
  47. package/components/overlay/sheet/Sheet.vue +7 -28
  48. package/components/overlay/tooltip/Tooltip.vue +14 -19
  49. package/{utils/controllers/dialog.ts → composables/useDialog.ts} +90 -78
  50. package/composables/useDisclosure.ts +113 -0
  51. package/composables/useEventListener.ts +16 -0
  52. package/composables/useMenu.ts +212 -0
  53. package/{utils/controllers/popover.ts → composables/usePopover.ts} +107 -120
  54. package/package.json +16 -40
  55. package/styles/0-settings/colors.css +10 -0
  56. package/styles/0-settings/layout.css +18 -0
  57. package/styles/0-settings/motion.css +2 -2
  58. package/styles/0-settings/spacing.css +3 -1
  59. package/utils/a11y/focus.ts +9 -3
  60. package/utils/cms/index.ts +283 -0
  61. package/utils/cms/schema.json +126 -0
  62. package/utils/navigation/sidebar.ts +97 -0
  63. package/utils/validation/form.ts +6 -9
  64. package/components/action/button/Button.astro +0 -95
  65. package/components/action/button/IconButton.astro +0 -86
  66. package/components/display/avatar/Avatar.astro +0 -17
  67. package/components/display/avatar/AvatarStack.astro +0 -9
  68. package/components/display/badge/Badge.astro +0 -15
  69. package/components/display/empty/Empty.astro +0 -9
  70. package/components/display/icon/Icon.astro +0 -52
  71. package/components/feedback/alert/Alert.astro +0 -52
  72. package/components/feedback/progress/Progress.astro +0 -68
  73. package/components/feedback/skeleton/Skeleton.astro +0 -32
  74. package/components/feedback/spinner/Spinner.astro +0 -25
  75. package/components/feedback/toast/Toast.astro +0 -50
  76. package/components/form/checkbox/Checkbox.astro +0 -79
  77. package/components/form/date-input/DateInput.astro +0 -105
  78. package/components/form/form/Form.astro +0 -106
  79. package/components/form/input-otp/InputOTP.astro +0 -147
  80. package/components/form/label/Label.astro +0 -13
  81. package/components/form/number-field/NumberField.astro +0 -142
  82. package/components/form/radio-group/RadioGroup.astro +0 -105
  83. package/components/form/select/Select.astro +0 -105
  84. package/components/form/slider/Slider.astro +0 -205
  85. package/components/form/switch/Switch.astro +0 -75
  86. package/components/form/tags-input/TagsInput.astro +0 -153
  87. package/components/form/text-input/TextInput.astro +0 -84
  88. package/components/form/textarea/Textarea.astro +0 -86
  89. package/components/layout/card/Card.astro +0 -13
  90. package/components/layout/separator/Separator.astro +0 -31
  91. package/components/layout/table/Table.astro +0 -116
  92. package/components/navigation/breadcrumb/Breadcrumb.astro +0 -36
  93. package/components/navigation/navbar/Navbar.astro +0 -62
  94. package/components/navigation/pagination/PaginationFooter.astro +0 -24
  95. package/components/navigation/sidebar/Sidebar.astro +0 -132
  96. package/components/navigation/sidebar/SidebarItemRender.astro +0 -83
  97. package/components/navigation/sidebar/SidebarItemRender.vue +0 -98
  98. package/components/overlay/alert-dialog/AlertDialog.astro +0 -112
  99. package/components/overlay/modal/Modal.astro +0 -66
  100. package/utils/controllers/disclosure.ts +0 -117
  101. package/utils/controllers/form.ts +0 -524
  102. package/utils/controllers/index.ts +0 -39
  103. package/utils/controllers/menu.ts +0 -255
  104. package/utils/controllers/number-field.ts +0 -103
  105. package/utils/controllers/otp.ts +0 -252
  106. package/utils/controllers/sidebar.ts +0 -610
  107. package/utils/controllers/slider.ts +0 -336
  108. package/utils/controllers/tags-input.ts +0 -255
  109. package/utils/controllers/toast.ts +0 -426
@@ -1,66 +0,0 @@
1
- ---
2
- import './modal.css';
3
- import { generateId } from '../../../utils/a11y/id.js';
4
-
5
- interface Props {
6
- id?: string;
7
- open?: boolean;
8
- persistent?: boolean;
9
- title: string;
10
- subtitle?: string;
11
- }
12
-
13
- const {
14
- id,
15
- open = false,
16
- persistent = false,
17
- title,
18
- subtitle,
19
- ...rest
20
- } = Astro.props;
21
-
22
- const modalId = id ?? generateId('modal');
23
- const titleId = `${modalId}-title`;
24
- ---
25
-
26
- <dialog
27
- id={modalId}
28
- class="pui-modal"
29
- data-state="closed"
30
- data-pui-modal
31
- data-persistent={persistent ? 'true' : undefined}
32
- data-open-initially={open ? 'true' : undefined}
33
- aria-labelledby={titleId}
34
- {...rest}
35
- >
36
- <div class="pui-modal__head">
37
- <h2 id={titleId} class="pui-modal__title">{title}</h2>
38
- {subtitle && <p class="pui-modal__sub">{subtitle}</p>}
39
- </div>
40
- <div class="pui-modal__body"><slot /></div>
41
- {
42
- Astro.slots.has('footer') && (
43
- <div class="pui-modal__foot">
44
- <slot name="footer" />
45
- </div>
46
- )
47
- }
48
- </dialog>
49
-
50
- <script>
51
- import { mountDialog } from '@pienter/ui/utils/controllers/dialog.js';
52
-
53
- document
54
- .querySelectorAll<HTMLDialogElement>('dialog[data-pui-modal]')
55
- .forEach((el) => {
56
- const node = el as HTMLDialogElement & {
57
- _dialog?: ReturnType<typeof mountDialog>;
58
- };
59
- if (node._dialog) return; // idempotent — guard against double-mounts (HMR, multiple <Modal> imports)
60
- const persistent = el.dataset.persistent === 'true';
61
- const openInitially = el.dataset.openInitially === 'true';
62
- const control = mountDialog(el, { persistent });
63
- node._dialog = control;
64
- if (openInitially) control.open();
65
- });
66
- </script>
@@ -1,117 +0,0 @@
1
- /**
2
- * Disclosure controller — shared by Accordion and Collapsible per the
3
- * locked controller-sharing mapping in `CONVENTIONS.md`.
4
- *
5
- * The v1 disclosure family is built on top of the **native HTML
6
- * `<details>` / `<summary>` element pair** per the locked "Use native
7
- * APIs where applicable" rule. The browser already handles:
8
- *
9
- * - Expand / collapse on summary click
10
- * - The `[open]` attribute reflecting current state
11
- * - Enter / Space activation when the summary is focused
12
- * - Tab focus moves to the summary, not the panel content
13
- * - Initial closed state when the `open` attribute is absent
14
- *
15
- * What the browser does NOT provide and we layer on top:
16
- *
17
- * - **Single-open coordination** for Accordion mode — when one
18
- * `<details>` opens, sibling `<details>` elements within the same
19
- * mount root close. Native `<details>` has no built-in mutual
20
- * exclusion (the obsolete `name` attribute exists in some browsers
21
- * but is not reliable cross-browser in 2026).
22
- * - A **callback** so framework code can react to toggles without
23
- * each `<details>` wiring its own `@toggle` listener.
24
- * - A consistent **teardown** lifecycle that matches the rest of the
25
- * controller archetype.
26
- *
27
- * Mount on:
28
- * - A single `<details>` element for Collapsible (no `singleOpen`
29
- * needed; the controller is essentially a callback bridge).
30
- * - A wrapper `<div>` containing multiple `<details>` for Accordion
31
- * (`singleOpen: true` makes them mutually exclusive).
32
- */
33
-
34
- export interface DisclosureConfig {
35
- /**
36
- * When `true`, opening any **top-level** `<details>` within the
37
- * mount root closes all sibling top-level `<details>` elements
38
- * (Accordion single-open mode). A "top-level" `<details>` here
39
- * means one whose nearest ancestor `<details>` lies outside the
40
- * mount root — i.e., a disclosure that belongs to THIS mount, not
41
- * a nested disclosure inside an item's panel content.
42
- *
43
- * When `false`, multiple disclosures can be open simultaneously
44
- * (Collapsible / Accordion multi-open mode). Default: `false`.
45
- */
46
- singleOpen?: boolean;
47
- /**
48
- * Called whenever a `<details>` within the mount root toggles.
49
- * Receives the toggled element and its new open state. Frameworks
50
- * use this to reflect the change back into reactive state (e.g.,
51
- * Vue `update:open`). The controller does NOT track open state
52
- * itself — the DOM is the source of truth via `el.open`.
53
- */
54
- onToggle?: (target: HTMLDetailsElement, open: boolean) => void;
55
- }
56
-
57
- /**
58
- * Mount disclosure behavior on `el`. The mount root may be either a
59
- * single `<details>` (Collapsible) or a wrapper containing multiple
60
- * `<details>` children (Accordion). Returns a teardown function that
61
- * removes the listener; the controller owns no other resources.
62
- *
63
- * The `toggle` event is captured at the mount root because it bubbles
64
- * from the originating `<details>`. This means a single listener
65
- * covers any number of disclosures without per-element wiring.
66
- */
67
- export function mountDisclosure(
68
- el: HTMLElement,
69
- config: DisclosureConfig = {},
70
- ): () => void {
71
- const singleOpen = config.singleOpen ?? false;
72
- const onToggle = config.onToggle;
73
-
74
- /**
75
- * A `<details>` is "top-level" relative to `el` when no other
76
- * `<details>` lies between it and `el` in the ancestor chain. Only
77
- * top-level disclosures participate in single-open coordination —
78
- * nested disclosures are part of the consumer's panel content and
79
- * must not be closed by the outer mount.
80
- */
81
- function isTopLevel(details: HTMLDetailsElement): boolean {
82
- const parentDetails = details.parentElement?.closest('details') ?? null;
83
- if (!parentDetails) return true;
84
- // If the parent `<details>` is outside the mount root, treat
85
- // `details` as top-level for THIS mount.
86
- return !el.contains(parentDetails);
87
- }
88
-
89
- function handleToggle(event: Event): void {
90
- const target = event.target;
91
- if (!(target instanceof HTMLDetailsElement)) return;
92
- if (!el.contains(target)) return;
93
-
94
- if (singleOpen && target.open && isTopLevel(target)) {
95
- // Close every other top-level `<details>` rooted in this mount.
96
- const allDetails =
97
- el.querySelectorAll<HTMLDetailsElement>('details');
98
- allDetails.forEach((other) => {
99
- if (other === target) return;
100
- if (!isTopLevel(other)) return;
101
- if (other.open) other.open = false;
102
- });
103
- }
104
-
105
- onToggle?.(target, target.open);
106
- }
107
-
108
- // `toggle` does not bubble in the standards sense — but a capturing
109
- // listener fires for any descendant `<details>` toggle. Use capture
110
- // phase so a single mount-root listener covers every `<details>`
111
- // within the scope.
112
- el.addEventListener('toggle', handleToggle, true);
113
-
114
- return function teardown(): void {
115
- el.removeEventListener('toggle', handleToggle, true);
116
- };
117
- }