@riducms/admin 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 (226) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/THIRD_PARTY_NOTICES.md +217 -0
  4. package/package.json +62 -0
  5. package/src/app/admin-core-view-route.svelte +89 -0
  6. package/src/app/admin-layout.svelte +457 -0
  7. package/src/app/admin-navigation-progress.svelte +204 -0
  8. package/src/app/admin-page-title.svelte +94 -0
  9. package/src/app/admin-provider-layer.svelte +26 -0
  10. package/src/app/admin-router-root.svelte +9 -0
  11. package/src/app/admin-routes.svelte +116 -0
  12. package/src/app/deferred-redirect.svelte +23 -0
  13. package/src/app/loading-boundary.svelte +70 -0
  14. package/src/app/not-found-route.svelte +29 -0
  15. package/src/app-meta.ts +2 -0
  16. package/src/app.css +467 -0
  17. package/src/app.svelte +88 -0
  18. package/src/assets/ridu-logo-arch.svg +3 -0
  19. package/src/assets/ridu-word-logo.svg +7 -0
  20. package/src/components/brand/ridu-logo.svelte +24 -0
  21. package/src/components/code-block/code-block.svelte +107 -0
  22. package/src/components/json-tree/json-tree-node.svelte +77 -0
  23. package/src/components/json-tree/json-viewer.svelte +113 -0
  24. package/src/components/ui/banner/banner.svelte +41 -0
  25. package/src/components/ui/banner/index.ts +5 -0
  26. package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +50 -0
  27. package/src/components/ui/bulk-action-bar/index.ts +1 -0
  28. package/src/components/ui/button/index.ts +8 -0
  29. package/src/components/ui/calendar/calendar-caption.svelte +72 -0
  30. package/src/components/ui/calendar/calendar-cell.svelte +20 -0
  31. package/src/components/ui/calendar/calendar-day.svelte +27 -0
  32. package/src/components/ui/calendar/calendar-grid-row.svelte +13 -0
  33. package/src/components/ui/calendar/calendar-grid.svelte +17 -0
  34. package/src/components/ui/calendar/calendar-head-cell.svelte +20 -0
  35. package/src/components/ui/calendar/calendar-header.svelte +20 -0
  36. package/src/components/ui/calendar/calendar-heading.svelte +17 -0
  37. package/src/components/ui/calendar/calendar-month-select.svelte +45 -0
  38. package/src/components/ui/calendar/calendar-month.svelte +16 -0
  39. package/src/components/ui/calendar/calendar-months.svelte +20 -0
  40. package/src/components/ui/calendar/calendar-nav.svelte +20 -0
  41. package/src/components/ui/calendar/calendar-next-button.svelte +30 -0
  42. package/src/components/ui/calendar/calendar-prev-button.svelte +30 -0
  43. package/src/components/ui/calendar/calendar-year-select.svelte +44 -0
  44. package/src/components/ui/calendar/calendar.svelte +118 -0
  45. package/src/components/ui/calendar/index.ts +1 -0
  46. package/src/components/ui/checkbox/checkbox.svelte +35 -0
  47. package/src/components/ui/checkbox/index.ts +1 -0
  48. package/src/components/ui/confirmation-dialog/confirmation-dialog.svelte +37 -0
  49. package/src/components/ui/confirmation-dialog/index.ts +1 -0
  50. package/src/components/ui/date-value-control/date-value-calendar.svelte +37 -0
  51. package/src/components/ui/date-value-control/date-value-control-loader.svelte +36 -0
  52. package/src/components/ui/date-value-control/date-value-control.svelte +177 -0
  53. package/src/components/ui/date-value-control/index.ts +3 -0
  54. package/src/components/ui/dialog/dialog-close.svelte +11 -0
  55. package/src/components/ui/dialog/dialog-content.svelte +52 -0
  56. package/src/components/ui/dialog/dialog-description.svelte +20 -0
  57. package/src/components/ui/dialog/dialog-footer.svelte +20 -0
  58. package/src/components/ui/dialog/dialog-header.svelte +20 -0
  59. package/src/components/ui/dialog/dialog-overlay.svelte +20 -0
  60. package/src/components/ui/dialog/dialog-title.svelte +17 -0
  61. package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
  62. package/src/components/ui/dialog/dialog.svelte +7 -0
  63. package/src/components/ui/dialog/index.ts +8 -0
  64. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +27 -0
  65. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +20 -0
  66. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  67. package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +16 -0
  68. package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
  69. package/src/components/ui/dropdown-menu/index.ts +5 -0
  70. package/src/components/ui/empty-state/empty-state.svelte +34 -0
  71. package/src/components/ui/empty-state/index.ts +1 -0
  72. package/src/components/ui/input/index.ts +1 -0
  73. package/src/components/ui/kbd/index.ts +1 -0
  74. package/src/components/ui/kbd/kbd.svelte +24 -0
  75. package/src/components/ui/pagination/index.ts +1 -0
  76. package/src/components/ui/pagination/pagination.svelte +88 -0
  77. package/src/components/ui/popover/index.ts +3 -0
  78. package/src/components/ui/popover/popover-content.svelte +29 -0
  79. package/src/components/ui/popover/popover-trigger.svelte +11 -0
  80. package/src/components/ui/popover/popover.svelte +7 -0
  81. package/src/components/ui/radio-group/index.ts +3 -0
  82. package/src/components/ui/radio-group/radio-card-item.svelte +38 -0
  83. package/src/components/ui/radio-group/radio-group-item.svelte +17 -0
  84. package/src/components/ui/radio-group/radio-group.svelte +19 -0
  85. package/src/components/ui/select/index.ts +4 -0
  86. package/src/components/ui/select/select-content.svelte +38 -0
  87. package/src/components/ui/select/select-item.svelte +40 -0
  88. package/src/components/ui/select/select-scroll-down-button.svelte +23 -0
  89. package/src/components/ui/select/select-scroll-up-button.svelte +23 -0
  90. package/src/components/ui/select/select-trigger.svelte +29 -0
  91. package/src/components/ui/select/select.svelte +11 -0
  92. package/src/components/ui/sheet/index.ts +6 -0
  93. package/src/components/ui/sheet/sheet-close.svelte +7 -0
  94. package/src/components/ui/sheet/sheet-content.svelte +77 -0
  95. package/src/components/ui/sheet/sheet-description.svelte +17 -0
  96. package/src/components/ui/sheet/sheet-overlay.svelte +36 -0
  97. package/src/components/ui/sheet/sheet-title.svelte +17 -0
  98. package/src/components/ui/sheet/sheet-trigger.svelte +7 -0
  99. package/src/components/ui/sheet/sheet.svelte +7 -0
  100. package/src/components/ui/skeleton/index.ts +1 -0
  101. package/src/components/ui/skeleton/skeleton.svelte +31 -0
  102. package/src/components/ui/slider/index.ts +3 -0
  103. package/src/components/ui/slider/slider-content.svelte +59 -0
  104. package/src/components/ui/slider/slider.svelte +35 -0
  105. package/src/components/ui/sonner/index.ts +1 -0
  106. package/src/components/ui/sonner/sonner.svelte +55 -0
  107. package/src/components/ui/status-indicator/index.ts +5 -0
  108. package/src/components/ui/status-indicator/status-indicator.svelte +54 -0
  109. package/src/components/ui/table/index.ts +6 -0
  110. package/src/components/ui/table/table-body.svelte +20 -0
  111. package/src/components/ui/table/table-cell.svelte +23 -0
  112. package/src/components/ui/table/table-head.svelte +23 -0
  113. package/src/components/ui/table/table-header.svelte +20 -0
  114. package/src/components/ui/table/table-row.svelte +23 -0
  115. package/src/components/ui/table/table.svelte +22 -0
  116. package/src/components/ui/tabs/index.ts +8 -0
  117. package/src/components/ui/tabs/tabs-content.svelte +17 -0
  118. package/src/components/ui/tabs/tabs-list.svelte +40 -0
  119. package/src/components/ui/tabs/tabs-trigger.svelte +23 -0
  120. package/src/components/ui/tabs/tabs.svelte +19 -0
  121. package/src/components/ui/textarea/index.ts +1 -0
  122. package/src/components/ui/tooltip/index.ts +4 -0
  123. package/src/components/ui/tooltip/tooltip-content.svelte +50 -0
  124. package/src/components/ui/tooltip/tooltip-provider.svelte +7 -0
  125. package/src/components/ui/tooltip/tooltip-trigger.svelte +7 -0
  126. package/src/components/ui/tooltip/tooltip.svelte +7 -0
  127. package/src/core/api/admin-client.ts +45 -0
  128. package/src/core/forms/field-condition.ts +63 -0
  129. package/src/core/forms/form-controller.svelte.ts +369 -0
  130. package/src/core/forms/form-draft-recovery.ts +80 -0
  131. package/src/core/forms/form-schema.ts +609 -0
  132. package/src/core/forms/form-validation.ts +750 -0
  133. package/src/core/i18n/admin-i18n.svelte.ts +364 -0
  134. package/src/core/i18n/localized-schema.ts +111 -0
  135. package/src/core/notifications/notification-center.svelte.ts +69 -0
  136. package/src/core/notifications/validation-toast.svelte +18 -0
  137. package/src/core/plugins/field-registry.ts +60 -0
  138. package/src/core/plugins/row-label-registry.ts +62 -0
  139. package/src/core/preferences/preference-write-queue.ts +107 -0
  140. package/src/core/routing/admin-paths.ts +137 -0
  141. package/src/core/runtime/admin-runtime.svelte.ts +625 -0
  142. package/src/features/auth/account-menu.svelte +159 -0
  143. package/src/features/auth/account-navigation.svelte +44 -0
  144. package/src/features/auth/account-route.svelte +395 -0
  145. package/src/features/auth/account-security-controller.svelte.ts +119 -0
  146. package/src/features/auth/account-security-route.svelte +460 -0
  147. package/src/features/auth/auth-brand.svelte +17 -0
  148. package/src/features/auth/auth-frame.svelte +18 -0
  149. package/src/features/auth/create-first-user-controller.svelte.ts +220 -0
  150. package/src/features/auth/create-first-user-route.svelte +174 -0
  151. package/src/features/auth/forgot-password-route.svelte +78 -0
  152. package/src/features/auth/login-route.svelte +221 -0
  153. package/src/features/auth/request-verification-route.svelte +73 -0
  154. package/src/features/auth/reset-password-route.svelte +107 -0
  155. package/src/features/auth/verify-email-route.svelte +60 -0
  156. package/src/features/collections/collection-list-bulk-actions.svelte +143 -0
  157. package/src/features/collections/collection-list-bulk-editor.svelte +157 -0
  158. package/src/features/collections/collection-list-controller.svelte.ts +717 -0
  159. package/src/features/collections/collection-list-filter-popover.svelte +238 -0
  160. package/src/features/collections/collection-list-preferences-controller.svelte.ts +393 -0
  161. package/src/features/collections/collection-list-results.svelte +410 -0
  162. package/src/features/collections/collection-list-route.svelte +951 -0
  163. package/src/features/collections/collection-list-workspace-toolbar.svelte +409 -0
  164. package/src/features/collections/collection-table-skeleton.svelte +44 -0
  165. package/src/features/collections/list-workspace.ts +265 -0
  166. package/src/features/dashboard/dashboard-route.svelte +141 -0
  167. package/src/features/development/development-tools.svelte +217 -0
  168. package/src/features/documents/document-api-view-loader.svelte +31 -0
  169. package/src/features/documents/document-api-view.svelte +547 -0
  170. package/src/features/documents/document-controller.svelte.ts +1252 -0
  171. package/src/features/documents/document-field-partition.ts +18 -0
  172. package/src/features/documents/document-field-sections.svelte +46 -0
  173. package/src/features/documents/document-heading.ts +20 -0
  174. package/src/features/documents/document-route-dirty.ts +43 -0
  175. package/src/features/documents/document-route-loader.svelte +22 -0
  176. package/src/features/documents/document-route.svelte +1124 -0
  177. package/src/features/documents/live-preview-panel.svelte +390 -0
  178. package/src/features/documents/live-preview.ts +61 -0
  179. package/src/features/documents/upload-document-preview-loader.svelte +40 -0
  180. package/src/features/documents/upload-document-preview.svelte +525 -0
  181. package/src/features/localization/content-locale-switcher.svelte +93 -0
  182. package/src/features/navigation/admin-breadcrumbs.svelte +124 -0
  183. package/src/features/navigation/admin-command-menu.svelte +245 -0
  184. package/src/features/navigation/admin-resource-groups.ts +72 -0
  185. package/src/features/reference-browser/reference-browser-loader.svelte +15 -0
  186. package/src/features/reference-browser/reference-browser-workflow.svelte.ts +691 -0
  187. package/src/features/reference-browser/reference-browser.svelte +844 -0
  188. package/src/features/uploads/bulk-upload-route.svelte +624 -0
  189. package/src/features/uploads/bulk-upload.ts +39 -0
  190. package/src/features/uploads/upload-metadata-fields.svelte +112 -0
  191. package/src/features/versions/version-diff.ts +72 -0
  192. package/src/features/versions/version-history-route.svelte +581 -0
  193. package/src/fields/field-clipboard.ts +132 -0
  194. package/src/fields/field-layout.svelte +60 -0
  195. package/src/fields/field-layout.ts +57 -0
  196. package/src/fields/field-messages.svelte +29 -0
  197. package/src/fields/field-renderer.svelte +152 -0
  198. package/src/fields/field-shell.svelte +30 -0
  199. package/src/fields/field-tab-layout.svelte +99 -0
  200. package/src/fields/join/join-field.svelte +358 -0
  201. package/src/fields/localized-field-presentation.ts +17 -0
  202. package/src/fields/nested/nested-field.svelte +602 -0
  203. package/src/fields/nested/scoped-field.ts +78 -0
  204. package/src/fields/nested/sortable-row.svelte +32 -0
  205. package/src/fields/point/point-field.svelte +52 -0
  206. package/src/fields/radio/radio-field.svelte +34 -0
  207. package/src/fields/relationship/relationship-field-controller.svelte.ts +245 -0
  208. package/src/fields/relationship/relationship-field.svelte +258 -0
  209. package/src/fields/relationship/relationship-lookup-controller.svelte.ts +129 -0
  210. package/src/fields/relationship/relationship-query.ts +88 -0
  211. package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +198 -0
  212. package/src/fields/relationship/relationship-quick-picker.svelte +327 -0
  213. package/src/fields/relationship/relationship-value.ts +47 -0
  214. package/src/fields/scalar/date-control-value.ts +60 -0
  215. package/src/fields/scalar/date-value.ts +26 -0
  216. package/src/fields/scalar/scalar-field.svelte +137 -0
  217. package/src/fields/select/select-field.svelte +132 -0
  218. package/src/fields/select/select-value.ts +14 -0
  219. package/src/fields/text/slug-field.svelte +87 -0
  220. package/src/fields/text/slug.ts +38 -0
  221. package/src/fields/text/text-field.svelte +41 -0
  222. package/src/fields/ui/ui-field.svelte +12 -0
  223. package/src/fields/virtual/virtual-field.svelte +28 -0
  224. package/src/index.ts +49 -0
  225. package/src/main.ts +12 -0
  226. package/src/vite-env.d.ts +9 -0
@@ -0,0 +1,77 @@
1
+ <script lang="ts">
2
+ import ChevronRightIcon from "~icons/lucide/chevron-right";
3
+
4
+ import JsonTreeNode from "@admin/components/json-tree/json-tree-node.svelte";
5
+
6
+ let { name, value, last = true }: { name?: string; value: unknown; last?: boolean } = $props();
7
+ const entries = $derived(
8
+ Array.isArray(value)
9
+ ? value.map((item, index) => [String(index), item] as const)
10
+ : isRecord(value)
11
+ ? Object.entries(value)
12
+ : []
13
+ );
14
+ const container = $derived(Array.isArray(value) || isRecord(value));
15
+ const opening = $derived(Array.isArray(value) ? "[" : "{");
16
+ const closing = $derived(Array.isArray(value) ? "]" : "}");
17
+ const countLabel = $derived(
18
+ Array.isArray(value)
19
+ ? entries.length === 1
20
+ ? "item"
21
+ : "items"
22
+ : entries.length === 1
23
+ ? "key"
24
+ : "keys"
25
+ );
26
+
27
+ function isRecord(candidate: unknown): candidate is Record<string, unknown> {
28
+ return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
29
+ }
30
+
31
+ function primitive(candidate: unknown) {
32
+ if (typeof candidate === "string") return JSON.stringify(candidate);
33
+ if (candidate === undefined) return "undefined";
34
+ return String(candidate);
35
+ }
36
+
37
+ function tone(candidate: unknown) {
38
+ if (typeof candidate === "string") return "text-[#b7c89a]";
39
+ if (typeof candidate === "number") return "text-[#c9b38b]";
40
+ if (typeof candidate === "boolean") return "text-[#9fb9d8]";
41
+ return "text-foreground-faint";
42
+ }
43
+ </script>
44
+
45
+ {#if container}
46
+ <details class="group/json" open>
47
+ <summary
48
+ class="flex cursor-pointer list-none items-center gap-1 rounded px-0.5 hover:bg-control-hover focus-visible:outline-1 focus-visible:outline-primary/60"
49
+ aria-label={name === undefined
50
+ ? `${entries.length} ${countLabel}`
51
+ : `${name}, ${entries.length} ${countLabel}`}
52
+ >
53
+ <ChevronRightIcon
54
+ class="size-3 shrink-0 text-foreground-faint transition-transform group-open/json:rotate-90"
55
+ />
56
+ {#if name !== undefined}<span class="text-[#9db5cd]">{JSON.stringify(name)}</span>
57
+ <span class="text-foreground-faint">:</span>{/if}
58
+ <span class="text-foreground-muted">{opening}</span>
59
+ <span class="text-[10px] text-foreground-faint">
60
+ {entries.length}
61
+ {countLabel}
62
+ </span>
63
+ <span class="text-foreground-muted">{closing}{last ? "" : ","}</span>
64
+ </summary>
65
+ <div class="ms-3.5 border-s border-control-border ps-3">
66
+ {#each entries as [key, child], index (key)}
67
+ <JsonTreeNode name={key} value={child} last={index === entries.length - 1} />
68
+ {/each}
69
+ </div>
70
+ </details>
71
+ {:else}
72
+ <div class="flex gap-1 rounded px-0.5 hover:bg-control-hover">
73
+ {#if name !== undefined}<span class="text-[#9db5cd]">{JSON.stringify(name)}</span>
74
+ <span class="text-foreground-faint">:</span>{/if}
75
+ <span class={tone(value)}>{primitive(value)}{last ? "" : ","}</span>
76
+ </div>
77
+ {/if}
@@ -0,0 +1,113 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from "svelte";
3
+ import CheckIcon from "~icons/lucide/check";
4
+ import CopyIcon from "~icons/lucide/copy";
5
+ import Maximize2Icon from "~icons/lucide/maximize-2";
6
+ import Minimize2Icon from "~icons/lucide/minimize-2";
7
+ import { getAdminI18n } from "@riducms/plugin";
8
+
9
+ import JsonTreeNode from "@admin/components/json-tree/json-tree-node.svelte";
10
+ import { Button, buttonVariants } from "@admin/components/ui/button";
11
+ import {
12
+ Dialog,
13
+ DialogClose,
14
+ DialogContent,
15
+ DialogDescription,
16
+ DialogTitle,
17
+ DialogTrigger,
18
+ } from "@admin/components/ui/dialog";
19
+
20
+ let { value, class: className = "" }: { value: unknown; class?: string } = $props();
21
+ const i18n = getAdminI18n();
22
+ let copied = $state(false);
23
+ let copyFailed = $state(false);
24
+ let copyResetTimer: ReturnType<typeof setTimeout> | undefined;
25
+
26
+ onDestroy(() => clearTimeout(copyResetTimer));
27
+
28
+ async function copy() {
29
+ try {
30
+ await navigator.clipboard.writeText(JSON.stringify(value, null, 2));
31
+ copied = true;
32
+ copyFailed = false;
33
+ } catch {
34
+ copyFailed = true;
35
+ }
36
+ clearTimeout(copyResetTimer);
37
+ copyResetTimer = window.setTimeout(() => {
38
+ copied = false;
39
+ copyFailed = false;
40
+ }, 1_500);
41
+ }
42
+ </script>
43
+
44
+ {#snippet viewer(expanded: boolean)}
45
+ <div
46
+ class={[
47
+ "flex min-h-0 flex-col overflow-hidden rounded-[4px] border border-control-border bg-control",
48
+ !expanded && className,
49
+ expanded && "h-full rounded-none border-0 bg-background",
50
+ ]}
51
+ >
52
+ <div class="flex items-center justify-between border-b border-control-border px-3 py-2">
53
+ <span class="font-mono text-[9.5px] tracking-[0.12em] text-foreground-faint uppercase">
54
+ JSON
55
+ </span>
56
+ <div class="flex items-center gap-1">
57
+ {#if expanded}
58
+ <DialogClose
59
+ class={buttonVariants({ variant: "ghost", size: "icon-xs" })}
60
+ aria-label={i18n.t("general:exitExpandedJSONView")}
61
+ title={i18n.t("general:exitExpandedJSONView")}
62
+ >
63
+ <Minimize2Icon class="size-3.5" />
64
+ </DialogClose>
65
+ {:else}
66
+ <DialogTrigger
67
+ class={buttonVariants({ variant: "ghost", size: "icon-xs" })}
68
+ aria-label={i18n.t("general:expandJSONView")}
69
+ title={i18n.t("general:expandJSONView")}
70
+ >
71
+ <Maximize2Icon class="size-3.5" />
72
+ </DialogTrigger>
73
+ {/if}
74
+ <Button
75
+ variant="ghost"
76
+ size="xs"
77
+ onclick={copy}
78
+ aria-label={i18n.t("general:copyJSON")}
79
+ tooltip={copied
80
+ ? i18n.t("general:copied")
81
+ : copyFailed
82
+ ? i18n.t("general:copyUnavailable")
83
+ : i18n.t("general:copyJSON")}
84
+ >
85
+ {#if copied}<CheckIcon class="size-3" /> {i18n.t("general:copied")}{:else}<CopyIcon
86
+ class="size-3"
87
+ />
88
+ {copyFailed ? i18n.t("general:copyUnavailable") : i18n.t("general:copy")}{/if}
89
+ </Button>
90
+ </div>
91
+ </div>
92
+ <section
93
+ class="min-h-72 flex-1 overflow-auto p-4 font-mono text-[11.5px] leading-6"
94
+ aria-label={i18n.t("general:jsonData")}
95
+ >
96
+ <JsonTreeNode {value} />
97
+ </section>
98
+ </div>
99
+ {/snippet}
100
+
101
+ <Dialog>
102
+ {@render viewer(false)}
103
+ <DialogContent
104
+ showCloseButton={false}
105
+ class="inset-3 top-3 left-3 h-auto w-auto max-w-none translate-x-0 translate-y-0 gap-0 overflow-hidden p-0 sm:max-w-none"
106
+ >
107
+ <DialogTitle class="sr-only">{i18n.t("general:expandedJSONResponse")}</DialogTitle>
108
+ <DialogDescription class="sr-only">
109
+ {i18n.t("general:expandedJSONDescription")}
110
+ </DialogDescription>
111
+ {@render viewer(true)}
112
+ </DialogContent>
113
+ </Dialog>
@@ -0,0 +1,41 @@
1
+ <script lang="ts" module>
2
+ import { tv, type VariantProps } from "@riducms/ui";
3
+
4
+ export const bannerVariants = tv({
5
+ base: "flex min-w-0 items-start gap-3 rounded-[3px] border px-3.5 py-3 text-[13.5px] leading-5",
6
+ variants: {
7
+ tone: {
8
+ warning: "border-warning/30 bg-warning/8 text-warning",
9
+ destructive: "border-destructive/30 bg-destructive/8 text-destructive",
10
+ neutral: "border-border bg-muted/35 text-foreground-muted",
11
+ },
12
+ },
13
+ defaultVariants: { tone: "neutral" },
14
+ });
15
+
16
+ export type BannerTone = VariantProps<typeof bannerVariants>["tone"];
17
+ </script>
18
+
19
+ <script lang="ts">
20
+ import type { HTMLAttributes } from "svelte/elements";
21
+
22
+ import { cn, type WithElementRef } from "@riducms/ui";
23
+
24
+ let {
25
+ ref = $bindable(null),
26
+ tone = "neutral",
27
+ class: className,
28
+ children,
29
+ ...restProps
30
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { tone?: BannerTone } = $props();
31
+ </script>
32
+
33
+ <div
34
+ bind:this={ref}
35
+ data-slot="banner"
36
+ class={cn(bannerVariants({ tone }), className)}
37
+ role={tone === "destructive" ? "alert" : "status"}
38
+ {...restProps}
39
+ >
40
+ {@render children?.()}
41
+ </div>
@@ -0,0 +1,5 @@
1
+ export {
2
+ default as Banner,
3
+ bannerVariants,
4
+ type BannerTone,
5
+ } from "@admin/components/ui/banner/banner.svelte";
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import XIcon from "~icons/lucide/x";
3
+ import type { Snippet } from "svelte";
4
+ import { getAdminI18n } from "@riducms/plugin";
5
+
6
+ import { Button } from "@admin/components/ui/button";
7
+
8
+ let {
9
+ count,
10
+ onClear,
11
+ label,
12
+ selectionLabel,
13
+ clearLabel,
14
+ children,
15
+ }: {
16
+ count: number;
17
+ onClear: () => void;
18
+ label?: string;
19
+ selectionLabel?: string;
20
+ clearLabel?: string;
21
+ children?: Snippet;
22
+ } = $props();
23
+ const i18n = getAdminI18n();
24
+ const resolvedLabel = $derived(label ?? i18n.t("general:bulkActions"));
25
+ const resolvedSelectionLabel = $derived(selectionLabel ?? i18n.t("general:selected", { count }));
26
+ const resolvedClearLabel = $derived(clearLabel ?? i18n.t("general:clearSelection"));
27
+ </script>
28
+
29
+ {#if count > 0}
30
+ <div
31
+ data-slot="bulk-action-bar"
32
+ class="ridu-popover-enter fixed bottom-6 left-1/2 z-40 flex max-w-[calc(100vw-2rem)] -translate-x-1/2 items-center gap-2 rounded-[4px] border border-control-border bg-popover px-3 py-2 text-popover-foreground shadow-[var(--shadow-popover)]"
33
+ role="region"
34
+ aria-label={resolvedLabel}
35
+ >
36
+ <span
37
+ class="whitespace-nowrap px-2 text-[13.5px] font-medium text-foreground"
38
+ role="status"
39
+ aria-live="polite"
40
+ aria-atomic="true"
41
+ >
42
+ {resolvedSelectionLabel}
43
+ </span>
44
+ <span class="h-5 w-px bg-control-border" aria-hidden="true"></span>
45
+ {@render children?.()}
46
+ <Button variant="ghost" size="icon-sm" onclick={onClear} aria-label={resolvedClearLabel}>
47
+ <XIcon class="size-3.5" />
48
+ </Button>
49
+ </div>
50
+ {/if}
@@ -0,0 +1 @@
1
+ export { default as BulkActionBar } from "@admin/components/ui/bulk-action-bar/bulk-action-bar.svelte";
@@ -0,0 +1,8 @@
1
+ export {
2
+ Button,
3
+ buttonVariants,
4
+ type ButtonProps,
5
+ type ButtonSize,
6
+ type ButtonVariant,
7
+ type ButtonProps as Props,
8
+ } from "@riducms/ui";
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import { DateFormatter, getLocalTimeZone, type DateValue } from "@internationalized/date";
3
+ import type { ComponentProps } from "svelte";
4
+
5
+ import CalendarMonthSelect from "@admin/components/ui/calendar/calendar-month-select.svelte";
6
+ import CalendarYearSelect from "@admin/components/ui/calendar/calendar-year-select.svelte";
7
+ import type Calendar from "@admin/components/ui/calendar/calendar.svelte";
8
+
9
+ let {
10
+ captionLayout,
11
+ months,
12
+ monthFormat,
13
+ years,
14
+ yearFormat,
15
+ month,
16
+ locale,
17
+ placeholder = $bindable(),
18
+ monthIndex = 0,
19
+ }: {
20
+ captionLayout: ComponentProps<typeof Calendar>["captionLayout"];
21
+ months: ComponentProps<typeof CalendarMonthSelect>["months"];
22
+ monthFormat: ComponentProps<typeof CalendarMonthSelect>["monthFormat"];
23
+ years: ComponentProps<typeof CalendarYearSelect>["years"];
24
+ yearFormat: ComponentProps<typeof CalendarYearSelect>["yearFormat"];
25
+ month: DateValue;
26
+ placeholder: DateValue | undefined;
27
+ locale: string;
28
+ monthIndex?: number;
29
+ } = $props();
30
+
31
+ function formatYear(date: DateValue) {
32
+ const dateObject = date.toDate(getLocalTimeZone());
33
+ if (typeof yearFormat === "function") return yearFormat(dateObject.getFullYear());
34
+ return new DateFormatter(locale, { year: yearFormat }).format(dateObject);
35
+ }
36
+
37
+ function formatMonth(date: DateValue) {
38
+ const dateObject = date.toDate(getLocalTimeZone());
39
+ if (typeof monthFormat === "function") return monthFormat(dateObject.getMonth() + 1);
40
+ return new DateFormatter(locale, { month: monthFormat }).format(dateObject);
41
+ }
42
+ </script>
43
+
44
+ {#snippet monthSelect()}
45
+ <CalendarMonthSelect
46
+ {months}
47
+ {monthFormat}
48
+ value={month.month}
49
+ onchange={(event) => {
50
+ if (placeholder === undefined) return;
51
+ const selectedMonth = Number.parseInt(event.currentTarget.value);
52
+ placeholder = placeholder.set({ month: selectedMonth }).subtract({ months: monthIndex });
53
+ }}
54
+ />
55
+ {/snippet}
56
+
57
+ {#snippet yearSelect()}
58
+ <CalendarYearSelect {years} {yearFormat} value={month.year} />
59
+ {/snippet}
60
+
61
+ {#if captionLayout === "dropdown"}
62
+ {@render monthSelect()}
63
+ {@render yearSelect()}
64
+ {:else if captionLayout === "dropdown-months"}
65
+ {@render monthSelect()}
66
+ {#if placeholder !== undefined}{formatYear(placeholder)}{/if}
67
+ {:else if captionLayout === "dropdown-years"}
68
+ {#if placeholder !== undefined}{formatMonth(placeholder)}{/if}
69
+ {@render yearSelect()}
70
+ {:else}
71
+ {formatMonth(month)} {formatYear(month)}
72
+ {/if}
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+
4
+ import { cn } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: CalendarPrimitive.CellProps = $props();
11
+ </script>
12
+
13
+ <CalendarPrimitive.Cell
14
+ bind:ref
15
+ class={cn(
16
+ "relative size-(--calendar-cell-size) p-0 text-center text-[12px] focus-within:z-20",
17
+ className
18
+ )}
19
+ {...restProps}
20
+ />
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+
4
+ import { buttonVariants } from "@admin/components/ui/button";
5
+ import { cn } from "@riducms/ui";
6
+
7
+ let {
8
+ ref = $bindable(null),
9
+ class: className,
10
+ ...restProps
11
+ }: CalendarPrimitive.DayProps = $props();
12
+ </script>
13
+
14
+ <CalendarPrimitive.Day
15
+ bind:ref
16
+ class={cn(
17
+ buttonVariants({ variant: "ghost", size: "icon-sm" }),
18
+ "size-(--calendar-cell-size) rounded-md p-0 text-[12px] font-normal text-foreground-muted",
19
+ "hover:bg-control-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-primary/60 focus-visible:outline-offset-0",
20
+ "data-[selected]:bg-primary data-[selected]:font-medium data-[selected]:text-primary-foreground data-[selected]:hover:bg-primary-hover data-[selected]:hover:text-primary-foreground",
21
+ "data-[today]:not-data-[selected]:bg-primary/12 data-[today]:not-data-[selected]:font-semibold data-[today]:not-data-[selected]:text-foreground",
22
+ "data-[outside-month]:not-data-[selected]:text-foreground-faint",
23
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-35 data-[unavailable]:line-through",
24
+ className
25
+ )}
26
+ {...restProps}
27
+ />
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+
4
+ import { cn } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: CalendarPrimitive.GridRowProps = $props();
11
+ </script>
12
+
13
+ <CalendarPrimitive.GridRow bind:ref class={cn("flex w-full", className)} {...restProps} />
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+
4
+ import { cn } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: CalendarPrimitive.GridProps = $props();
11
+ </script>
12
+
13
+ <CalendarPrimitive.Grid
14
+ bind:ref
15
+ class={cn("mt-3 flex w-full flex-col gap-1 border-collapse", className)}
16
+ {...restProps}
17
+ />
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+
4
+ import { cn } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: CalendarPrimitive.HeadCellProps = $props();
11
+ </script>
12
+
13
+ <CalendarPrimitive.HeadCell
14
+ bind:ref
15
+ class={cn(
16
+ "font-mono w-(--calendar-cell-size) text-center text-[9px] font-medium tracking-[0.08em] text-foreground-faint uppercase",
17
+ className
18
+ )}
19
+ {...restProps}
20
+ />
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+
4
+ import { cn } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: CalendarPrimitive.HeaderProps = $props();
11
+ </script>
12
+
13
+ <CalendarPrimitive.Header
14
+ bind:ref
15
+ class={cn(
16
+ "flex h-(--calendar-cell-size) w-full items-center justify-center gap-1.5 text-[12px] font-medium",
17
+ className
18
+ )}
19
+ {...restProps}
20
+ />
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+
4
+ import { cn } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ ...restProps
10
+ }: CalendarPrimitive.HeadingProps = $props();
11
+ </script>
12
+
13
+ <CalendarPrimitive.Heading
14
+ bind:ref
15
+ class={cn("px-(--calendar-cell-size) text-[12px] font-medium", className)}
16
+ {...restProps}
17
+ />
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+ import ChevronDownIcon from "~icons/lucide/chevron-down";
4
+
5
+ import { cn, type WithoutChildrenOrChild } from "@riducms/ui";
6
+
7
+ let {
8
+ ref = $bindable(null),
9
+ class: className,
10
+ value,
11
+ onchange,
12
+ ...restProps
13
+ }: WithoutChildrenOrChild<CalendarPrimitive.MonthSelectProps> = $props();
14
+ </script>
15
+
16
+ <span
17
+ class={cn(
18
+ "relative flex rounded-md border border-control-border bg-control transition-colors focus-within:border-primary/60 focus-within:ring-2 focus-within:ring-primary/15",
19
+ className
20
+ )}
21
+ >
22
+ <CalendarPrimitive.MonthSelect bind:ref class="absolute inset-0 opacity-0" {...restProps}>
23
+ {#snippet child({ props, monthItems, selectedMonthItem })}
24
+ <select {...props} {value} {onchange} data-ridu-native-exception>
25
+ {#each monthItems as monthItem (monthItem.value)}
26
+ <option
27
+ value={monthItem.value}
28
+ selected={value === undefined
29
+ ? monthItem.value === selectedMonthItem.value
30
+ : monthItem.value === value}
31
+ >
32
+ {monthItem.label}
33
+ </option>
34
+ {/each}
35
+ </select>
36
+ <span
37
+ class="flex h-8 items-center gap-1 ps-2 pe-1 text-[12px] font-medium text-foreground select-none"
38
+ aria-hidden="true"
39
+ >
40
+ {monthItems.find((item) => item.value === value)?.label ?? selectedMonthItem.label}
41
+ <ChevronDownIcon class="size-3 text-foreground-faint" />
42
+ </span>
43
+ {/snippet}
44
+ </CalendarPrimitive.MonthSelect>
45
+ </span>
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+
4
+ import { cn, type WithElementRef } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ children,
10
+ ...restProps
11
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
12
+ </script>
13
+
14
+ <div bind:this={ref} class={cn("flex flex-col", className)} {...restProps}>
15
+ {@render children?.()}
16
+ </div>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+
4
+ import { cn, type WithElementRef } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ children,
10
+ ...restProps
11
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
12
+ </script>
13
+
14
+ <div
15
+ bind:this={ref}
16
+ class={cn("relative flex flex-col gap-4 md:flex-row", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </div>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+
4
+ import { cn, type WithElementRef } from "@riducms/ui";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ children,
10
+ ...restProps
11
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
12
+ </script>
13
+
14
+ <nav
15
+ bind:this={ref}
16
+ class={cn("absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </nav>
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { Calendar as CalendarPrimitive } from "bits-ui";
3
+ import ChevronRightIcon from "~icons/lucide/chevron-right";
4
+
5
+ import { buttonVariants, type ButtonVariant } from "@admin/components/ui/button";
6
+ import { cn } from "@riducms/ui";
7
+
8
+ let {
9
+ ref = $bindable(null),
10
+ class: className,
11
+ children,
12
+ variant = "ghost",
13
+ ...restProps
14
+ }: CalendarPrimitive.NextButtonProps & { variant?: ButtonVariant } = $props();
15
+ </script>
16
+
17
+ {#snippet fallback()}
18
+ <ChevronRightIcon class="size-3.5" aria-hidden="true" />
19
+ {/snippet}
20
+
21
+ <CalendarPrimitive.NextButton
22
+ bind:ref
23
+ class={cn(
24
+ buttonVariants({ variant, size: "icon-sm" }),
25
+ "size-(--calendar-cell-size) bg-transparent p-0 disabled:opacity-35 rtl:rotate-180",
26
+ className
27
+ )}
28
+ children={children ?? fallback}
29
+ {...restProps}
30
+ />