@open-mercato/ui 0.6.1-develop.3256.1.fe3dec2464 → 0.6.1-develop.3260.1.dc719cac84

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 (137) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/backend/CrudForm.js +46 -129
  3. package/dist/backend/CrudForm.js.map +3 -3
  4. package/dist/backend/DataTable.js +10 -3
  5. package/dist/backend/DataTable.js.map +2 -2
  6. package/dist/backend/EmptyState.js +1 -47
  7. package/dist/backend/EmptyState.js.map +2 -2
  8. package/dist/backend/FilterOverlay.js +31 -26
  9. package/dist/backend/FilterOverlay.js.map +2 -2
  10. package/dist/backend/FlashMessages.js +23 -25
  11. package/dist/backend/FlashMessages.js.map +2 -2
  12. package/dist/backend/JsonBuilder.js +4 -2
  13. package/dist/backend/JsonBuilder.js.map +2 -2
  14. package/dist/backend/LogList.js +49 -0
  15. package/dist/backend/LogList.js.map +7 -0
  16. package/dist/backend/crud/CollapsibleGroup.js +13 -3
  17. package/dist/backend/crud/CollapsibleGroup.js.map +2 -2
  18. package/dist/backend/detail/InlineEditors.js +80 -32
  19. package/dist/backend/detail/InlineEditors.js.map +2 -2
  20. package/dist/backend/inputs/ComboboxInput.js +60 -6
  21. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  22. package/dist/backend/inputs/DatePicker.js +6 -142
  23. package/dist/backend/inputs/DatePicker.js.map +2 -2
  24. package/dist/backend/inputs/DateTimePicker.js +6 -182
  25. package/dist/backend/inputs/DateTimePicker.js.map +2 -2
  26. package/dist/backend/inputs/TagsInput.js +16 -19
  27. package/dist/backend/inputs/TagsInput.js.map +2 -2
  28. package/dist/backend/inputs/TimeInput.js +1 -1
  29. package/dist/backend/inputs/TimeInput.js.map +2 -2
  30. package/dist/backend/inputs/TimePicker.js +83 -72
  31. package/dist/backend/inputs/TimePicker.js.map +2 -2
  32. package/dist/backend/utils/customFieldForms.js +2 -2
  33. package/dist/backend/utils/customFieldForms.js.map +1 -1
  34. package/dist/portal/components/PortalEmptyState.js +13 -9
  35. package/dist/portal/components/PortalEmptyState.js.map +2 -2
  36. package/dist/primitives/accordion.js +137 -0
  37. package/dist/primitives/accordion.js.map +7 -0
  38. package/dist/primitives/alert.js +180 -12
  39. package/dist/primitives/alert.js.map +2 -2
  40. package/dist/primitives/calendar.js +122 -32
  41. package/dist/primitives/calendar.js.map +2 -2
  42. package/dist/primitives/compact-select.js +47 -0
  43. package/dist/primitives/compact-select.js.map +7 -0
  44. package/dist/primitives/counter-input.js +174 -0
  45. package/dist/primitives/counter-input.js.map +7 -0
  46. package/dist/primitives/date-picker-helpers.js +79 -0
  47. package/dist/primitives/date-picker-helpers.js.map +7 -0
  48. package/dist/primitives/date-picker.js +216 -0
  49. package/dist/primitives/date-picker.js.map +7 -0
  50. package/dist/primitives/date-range-picker.js +239 -0
  51. package/dist/primitives/date-range-picker.js.map +7 -0
  52. package/dist/primitives/digit-input.js +157 -0
  53. package/dist/primitives/digit-input.js.map +7 -0
  54. package/dist/primitives/empty-state.js +93 -0
  55. package/dist/primitives/empty-state.js.map +7 -0
  56. package/dist/primitives/inline-input.js +29 -0
  57. package/dist/primitives/inline-input.js.map +7 -0
  58. package/dist/primitives/inline-select.js +41 -0
  59. package/dist/primitives/inline-select.js.map +7 -0
  60. package/dist/primitives/notification-stack.js +116 -0
  61. package/dist/primitives/notification-stack.js.map +7 -0
  62. package/dist/primitives/notification.js +62 -0
  63. package/dist/primitives/notification.js.map +7 -0
  64. package/dist/primitives/rich-editor.js +1230 -0
  65. package/dist/primitives/rich-editor.js.map +7 -0
  66. package/dist/primitives/select.js +1 -0
  67. package/dist/primitives/select.js.map +2 -2
  68. package/dist/primitives/skeleton.js +55 -0
  69. package/dist/primitives/skeleton.js.map +7 -0
  70. package/dist/primitives/tag-input.js +186 -0
  71. package/dist/primitives/tag-input.js.map +7 -0
  72. package/dist/primitives/tag.js +66 -13
  73. package/dist/primitives/tag.js.map +2 -2
  74. package/dist/primitives/time-picker.js +793 -0
  75. package/dist/primitives/time-picker.js.map +7 -0
  76. package/package.json +4 -3
  77. package/src/backend/CrudForm.tsx +61 -113
  78. package/src/backend/DataTable.tsx +17 -5
  79. package/src/backend/EmptyState.tsx +13 -63
  80. package/src/backend/FilterOverlay.tsx +38 -22
  81. package/src/backend/FlashMessages.tsx +23 -24
  82. package/src/backend/JsonBuilder.tsx +4 -2
  83. package/src/backend/LogList.tsx +109 -0
  84. package/src/backend/__tests__/CrudForm.datetime.test.tsx +12 -7
  85. package/src/backend/__tests__/FilterOverlay.dateRange.test.tsx +89 -0
  86. package/src/backend/__tests__/LogList.test.tsx +163 -0
  87. package/src/backend/crud/CollapsibleGroup.tsx +17 -3
  88. package/src/backend/detail/InlineEditors.tsx +78 -25
  89. package/src/backend/detail/__tests__/InlineEditors.test.tsx +81 -1
  90. package/src/backend/inputs/ComboboxInput.tsx +81 -9
  91. package/src/backend/inputs/DatePicker.tsx +28 -155
  92. package/src/backend/inputs/DateTimePicker.tsx +25 -193
  93. package/src/backend/inputs/TagsInput.tsx +10 -13
  94. package/src/backend/inputs/TimeInput.tsx +2 -1
  95. package/src/backend/inputs/TimePicker.tsx +99 -70
  96. package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +133 -0
  97. package/src/backend/inputs/__tests__/TimePicker.test.tsx +2 -2
  98. package/src/backend/utils/__tests__/richTextSanitizer.test.ts +5 -2
  99. package/src/backend/utils/customFieldForms.ts +2 -2
  100. package/src/portal/components/PortalEmptyState.tsx +27 -22
  101. package/src/primitives/__tests__/accordion.test.tsx +197 -0
  102. package/src/primitives/__tests__/alert.test.tsx +204 -0
  103. package/src/primitives/__tests__/compact-select.test.tsx +98 -0
  104. package/src/primitives/__tests__/counter-input.test.tsx +165 -0
  105. package/src/primitives/__tests__/date-picker.test.tsx +221 -0
  106. package/src/primitives/__tests__/date-range-picker.test.tsx +173 -0
  107. package/src/primitives/__tests__/digit-input.test.tsx +168 -0
  108. package/src/primitives/__tests__/empty-state.test.tsx +154 -0
  109. package/src/primitives/__tests__/inline-input.test.tsx +100 -0
  110. package/src/primitives/__tests__/inline-select.test.tsx +103 -0
  111. package/src/primitives/__tests__/notification-stack.test.tsx +174 -0
  112. package/src/primitives/__tests__/notification.test.tsx +119 -0
  113. package/src/primitives/__tests__/rich-editor.test.tsx +411 -0
  114. package/src/primitives/__tests__/skeleton.test.tsx +76 -0
  115. package/src/primitives/__tests__/tag-input.test.tsx +198 -0
  116. package/src/primitives/__tests__/tag.test.tsx +123 -0
  117. package/src/primitives/__tests__/time-picker.test.tsx +392 -0
  118. package/src/primitives/accordion.tsx +225 -0
  119. package/src/primitives/alert.tsx +234 -24
  120. package/src/primitives/calendar.tsx +146 -38
  121. package/src/primitives/compact-select.tsx +59 -0
  122. package/src/primitives/counter-input.tsx +211 -0
  123. package/src/primitives/date-picker-helpers.ts +96 -0
  124. package/src/primitives/date-picker.tsx +290 -0
  125. package/src/primitives/date-range-picker.tsx +281 -0
  126. package/src/primitives/digit-input.tsx +208 -0
  127. package/src/primitives/empty-state.tsx +140 -0
  128. package/src/primitives/inline-input.tsx +55 -0
  129. package/src/primitives/inline-select.tsx +74 -0
  130. package/src/primitives/notification-stack.tsx +185 -0
  131. package/src/primitives/notification.tsx +136 -0
  132. package/src/primitives/rich-editor.tsx +1578 -0
  133. package/src/primitives/select.tsx +1 -0
  134. package/src/primitives/skeleton.tsx +70 -0
  135. package/src/primitives/tag-input.tsx +223 -0
  136. package/src/primitives/tag.tsx +69 -10
  137. package/src/primitives/time-picker.tsx +1059 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/primitives/inline-input.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Input, type InputProps } from './input'\n\nexport type InlineInputProps = Omit<InputProps, 'size'> & {\n /**\n * Trigger size. `sm` (h-8) is the default \u2014 inline editors are typically\n * embedded inside dense rows. Pass `'default'` (h-9) when the editor sits\n * alongside other h-9 controls.\n */\n size?: 'sm' | 'default'\n /**\n * Render a subtle input border on hover so the affordance is discoverable.\n * Default `true`. Set to `false` for editors that should remain visually\n * invisible until focused (e.g. plain text cells inside read-only-looking\n * card layouts).\n */\n showBorderOnHover?: boolean\n}\n\n/**\n * Borderless variant of `Input`. At rest the field looks like plain text\n * (no border, no background, no shadow). Hover reveals a subtle border via\n * `showBorderOnHover` (default `true`); focus always reveals the standard\n * `border-foreground` + focus shadow inherited from the underlying `Input`\n * wrapper for accessibility.\n *\n * Forwards every `Input` prop (placeholder, value, onChange, type, etc.).\n * Consumers wire `onBlur` for the \"save on blur\" pattern typical of inline\n * editors.\n */\nexport const InlineInput = React.forwardRef<HTMLInputElement, InlineInputProps>(\n (\n { className, inputClassName, size = 'sm', showBorderOnHover = true, ...rest },\n ref,\n ) => {\n return (\n <Input\n ref={ref}\n size={size}\n className={cn(\n 'border-transparent bg-transparent shadow-none',\n showBorderOnHover ? 'hover:border-input hover:bg-muted/40' : 'hover:bg-transparent',\n className,\n )}\n inputClassName={inputClassName}\n data-slot=\"inline-input\"\n {...rest}\n />\n )\n },\n)\nInlineInput.displayName = 'InlineInput'\n"],
5
+ "mappings": ";AAuCM;AArCN,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,aAA8B;AA6BhC,MAAM,cAAc,MAAM;AAAA,EAC/B,CACE,EAAE,WAAW,gBAAgB,OAAO,MAAM,oBAAoB,MAAM,GAAG,KAAK,GAC5E,QACG;AACH,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA,oBAAoB,yCAAyC;AAAA,UAC7D;AAAA,QACF;AAAA,QACA;AAAA,QACA,aAAU;AAAA,QACT,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AACA,YAAY,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "@open-mercato/shared/lib/utils";
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectGroup,
9
+ SelectItem,
10
+ SelectLabel,
11
+ SelectSeparator,
12
+ SelectTrigger,
13
+ SelectValue
14
+ } from "./select.js";
15
+ const InlineSelectTrigger = React.forwardRef(({ className, size = "sm", showBorderOnHover = true, children, ...props }, ref) => /* @__PURE__ */ jsx(
16
+ SelectTrigger,
17
+ {
18
+ ref,
19
+ size,
20
+ className: cn(
21
+ "border-transparent bg-transparent shadow-none",
22
+ showBorderOnHover ? "hover:border-input hover:bg-muted/40" : "hover:bg-transparent",
23
+ className
24
+ ),
25
+ "data-slot": "inline-select-trigger",
26
+ ...props,
27
+ children
28
+ }
29
+ ));
30
+ InlineSelectTrigger.displayName = "InlineSelectTrigger";
31
+ export {
32
+ InlineSelectTrigger,
33
+ Select,
34
+ SelectContent,
35
+ SelectGroup,
36
+ SelectItem,
37
+ SelectLabel,
38
+ SelectSeparator,
39
+ SelectValue
40
+ };
41
+ //# sourceMappingURL=inline-select.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/primitives/inline-select.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n type SelectTriggerProps,\n} from './select'\n\nexport type InlineSelectTriggerProps = Omit<SelectTriggerProps, 'size'> & {\n /**\n * Trigger size. `sm` (h-8) is the default \u2014 inline editors are typically\n * embedded inside dense rows. Pass `'default'` (h-9) when the editor sits\n * alongside other h-9 controls.\n */\n size?: 'sm' | 'default'\n /**\n * Render a subtle trigger border on hover so the affordance is\n * discoverable. Default `true`. Set to `false` for editors that should\n * remain visually invisible until focused.\n */\n showBorderOnHover?: boolean\n}\n\n/**\n * Borderless variant of `SelectTrigger` \u2014 the select-typed counterpart to\n * `InlineInput`. At rest the trigger looks like plain text (transparent\n * border, transparent background, no shadow); hover reveals a subtle\n * border via `showBorderOnHover` (default `true`), and focus inherits the\n * standard `border-foreground` + focus shadow from the underlying\n * `SelectTrigger` for keyboard accessibility.\n *\n * Composes with the regular `Select` root + `SelectContent` /\n * `SelectItem` \u2014 `inline-select.tsx` re-exports the rest of the `Select`\n * API so consumers can import the entire composition from one path.\n */\nexport const InlineSelectTrigger = React.forwardRef<\n React.ElementRef<typeof SelectTrigger>,\n InlineSelectTriggerProps\n>(({ className, size = 'sm', showBorderOnHover = true, children, ...props }, ref) => (\n <SelectTrigger\n ref={ref}\n size={size}\n className={cn(\n 'border-transparent bg-transparent shadow-none',\n showBorderOnHover ? 'hover:border-input hover:bg-muted/40' : 'hover:bg-transparent',\n className,\n )}\n data-slot=\"inline-select-trigger\"\n {...props}\n >\n {children}\n </SelectTrigger>\n))\nInlineSelectTrigger.displayName = 'InlineSelectTrigger'\n\n// Re-export the rest of the Select API so consumers can import the entire\n// inline-select composition from one path.\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectValue,\n}\n"],
5
+ "mappings": ";AA+CE;AA7CF,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AA6BA,MAAM,sBAAsB,MAAM,WAGvC,CAAC,EAAE,WAAW,OAAO,MAAM,oBAAoB,MAAM,UAAU,GAAG,MAAM,GAAG,QAC3E;AAAA,EAAC;AAAA;AAAA,IACC;AAAA,IACA;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA,oBAAoB,yCAAyC;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,aAAU;AAAA,IACT,GAAG;AAAA,IAEH;AAAA;AACH,CACD;AACD,oBAAoB,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,116 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Notification } from "./notification.js";
5
+ const NotificationContext = React.createContext(null);
6
+ function generateId() {
7
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
8
+ return crypto.randomUUID();
9
+ }
10
+ return `notif-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
11
+ }
12
+ function NotificationProvider({ children, maxVisible = 5 }) {
13
+ const [notifications, setNotifications] = React.useState([]);
14
+ const timersRef = React.useRef(/* @__PURE__ */ new Map());
15
+ const clearTimer = React.useCallback((id) => {
16
+ const t = timersRef.current.get(id);
17
+ if (t) {
18
+ clearTimeout(t);
19
+ timersRef.current.delete(id);
20
+ }
21
+ }, []);
22
+ const dismiss = React.useCallback(
23
+ (id) => {
24
+ clearTimer(id);
25
+ setNotifications((prev) => prev.filter((n) => n.id !== id));
26
+ },
27
+ [clearTimer]
28
+ );
29
+ const notify = React.useCallback(
30
+ (options) => {
31
+ const id = generateId();
32
+ setNotifications((prev) => {
33
+ const next = [...prev, { ...options, id }];
34
+ return next.length > maxVisible ? next.slice(next.length - maxVisible) : next;
35
+ });
36
+ if (typeof options.autoDismissMs === "number" && options.autoDismissMs > 0) {
37
+ const t = setTimeout(() => dismiss(id), options.autoDismissMs);
38
+ timersRef.current.set(id, t);
39
+ }
40
+ return id;
41
+ },
42
+ [dismiss, maxVisible]
43
+ );
44
+ const dismissAll = React.useCallback(() => {
45
+ timersRef.current.forEach((t) => clearTimeout(t));
46
+ timersRef.current.clear();
47
+ setNotifications([]);
48
+ }, []);
49
+ React.useEffect(() => {
50
+ return () => {
51
+ timersRef.current.forEach((t) => clearTimeout(t));
52
+ timersRef.current.clear();
53
+ };
54
+ }, []);
55
+ const value = React.useMemo(
56
+ () => ({ notifications, notify, dismiss, dismissAll }),
57
+ [notifications, notify, dismiss, dismissAll]
58
+ );
59
+ return /* @__PURE__ */ jsx(NotificationContext.Provider, { value, children });
60
+ }
61
+ function useNotification() {
62
+ const ctx = React.useContext(NotificationContext);
63
+ if (!ctx) {
64
+ throw new Error("useNotification must be used within a NotificationProvider");
65
+ }
66
+ return ctx;
67
+ }
68
+ const placementClasses = {
69
+ "top-right": "top-4 right-4 items-end",
70
+ "top-left": "top-4 left-4 items-start",
71
+ "bottom-right": "bottom-4 right-4 items-end",
72
+ "bottom-left": "bottom-4 left-4 items-start",
73
+ "top-center": "top-4 left-1/2 -translate-x-1/2 items-center",
74
+ "bottom-center": "bottom-4 left-1/2 -translate-x-1/2 items-center"
75
+ };
76
+ function NotificationStack({
77
+ placement = "top-right",
78
+ maxWidth = 380,
79
+ className
80
+ }) {
81
+ const { notifications, dismiss } = useNotification();
82
+ if (notifications.length === 0) return null;
83
+ return /* @__PURE__ */ jsx(
84
+ "div",
85
+ {
86
+ className: [
87
+ "pointer-events-none fixed z-toast flex flex-col gap-2",
88
+ placementClasses[placement],
89
+ className
90
+ ].filter(Boolean).join(" "),
91
+ "data-slot": "notification-stack",
92
+ "data-placement": placement,
93
+ children: notifications.map((entry) => /* @__PURE__ */ jsx(
94
+ "div",
95
+ {
96
+ className: "pointer-events-auto w-full",
97
+ style: { maxWidth },
98
+ children: /* @__PURE__ */ jsx(
99
+ Notification,
100
+ {
101
+ ...entry,
102
+ onDismiss: () => dismiss(entry.id)
103
+ }
104
+ )
105
+ },
106
+ entry.id
107
+ ))
108
+ }
109
+ );
110
+ }
111
+ export {
112
+ NotificationProvider,
113
+ NotificationStack,
114
+ useNotification
115
+ };
116
+ //# sourceMappingURL=notification-stack.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/primitives/notification-stack.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Notification, type NotificationProps } from './notification'\n\nexport type NotificationStackPlacement =\n | 'top-right'\n | 'top-left'\n | 'bottom-right'\n | 'bottom-left'\n | 'top-center'\n | 'bottom-center'\n\nexport type NotifyOptions = Omit<NotificationProps, 'id' | 'onDismiss'> & {\n /** Auto-dismiss the notification after this many milliseconds. Omit for manual-only dismiss (Notification default). */\n autoDismissMs?: number\n}\n\nexport type NotificationEntry = NotifyOptions & {\n id: string\n}\n\ntype NotificationContextValue = {\n notifications: NotificationEntry[]\n notify: (options: NotifyOptions) => string\n dismiss: (id: string) => void\n dismissAll: () => void\n}\n\nconst NotificationContext = React.createContext<NotificationContextValue | null>(null)\n\nfunction generateId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `notif-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nexport type NotificationProviderProps = {\n children: React.ReactNode\n /**\n * Maximum number of notifications visible at once. Older entries beyond\n * this cap are removed FIFO. Default `5`.\n */\n maxVisible?: number\n}\n\n/**\n * Mount once near the app root (typically inside `AppShell` next to\n * `FlashMessages`). Provides the `useNotification()` hook to children\n * and renders the `NotificationStack` in a fixed-position portal-style\n * wrapper.\n *\n * Stack placement, gap, and per-entry behavior are consumer choice\n * via `NotificationStack` \u2014 `NotificationProvider` itself does not\n * render the visual list, only the context.\n */\nexport function NotificationProvider({ children, maxVisible = 5 }: NotificationProviderProps) {\n const [notifications, setNotifications] = React.useState<NotificationEntry[]>([])\n const timersRef = React.useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map())\n\n const clearTimer = React.useCallback((id: string) => {\n const t = timersRef.current.get(id)\n if (t) {\n clearTimeout(t)\n timersRef.current.delete(id)\n }\n }, [])\n\n const dismiss = React.useCallback(\n (id: string) => {\n clearTimer(id)\n setNotifications((prev) => prev.filter((n) => n.id !== id))\n },\n [clearTimer],\n )\n\n const notify = React.useCallback(\n (options: NotifyOptions) => {\n const id = generateId()\n setNotifications((prev) => {\n const next = [...prev, { ...options, id }]\n // FIFO trim when exceeding the visible cap.\n return next.length > maxVisible ? next.slice(next.length - maxVisible) : next\n })\n if (typeof options.autoDismissMs === 'number' && options.autoDismissMs > 0) {\n const t = setTimeout(() => dismiss(id), options.autoDismissMs)\n timersRef.current.set(id, t)\n }\n return id\n },\n [dismiss, maxVisible],\n )\n\n const dismissAll = React.useCallback(() => {\n timersRef.current.forEach((t) => clearTimeout(t))\n timersRef.current.clear()\n setNotifications([])\n }, [])\n\n React.useEffect(() => {\n return () => {\n timersRef.current.forEach((t) => clearTimeout(t))\n timersRef.current.clear()\n }\n }, [])\n\n const value = React.useMemo(\n () => ({ notifications, notify, dismiss, dismissAll }),\n [notifications, notify, dismiss, dismissAll],\n )\n\n return <NotificationContext.Provider value={value}>{children}</NotificationContext.Provider>\n}\n\nexport function useNotification(): NotificationContextValue {\n const ctx = React.useContext(NotificationContext)\n if (!ctx) {\n throw new Error('useNotification must be used within a NotificationProvider')\n }\n return ctx\n}\n\nconst placementClasses: Record<NotificationStackPlacement, string> = {\n 'top-right': 'top-4 right-4 items-end',\n 'top-left': 'top-4 left-4 items-start',\n 'bottom-right': 'bottom-4 right-4 items-end',\n 'bottom-left': 'bottom-4 left-4 items-start',\n 'top-center': 'top-4 left-1/2 -translate-x-1/2 items-center',\n 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2 items-center',\n}\n\nexport type NotificationStackProps = {\n /** Where the stack floats on the viewport. Default `'top-right'`. */\n placement?: NotificationStackPlacement\n /** Max width per notification card. Default `380` (matches FlashMessages toast). */\n maxWidth?: number\n className?: string\n}\n\n/**\n * Renders the active `NotificationProvider` queue as a fixed-position\n * stack in one of six placements. Mount once next to\n * `NotificationProvider`, typically inside `AppShell` so notifications\n * float above every backend page.\n *\n * Each entry uses the `Notification` primitive \u2014 its `onDismiss`\n * automatically calls `dismiss(id)` from the provider so the user\n * clicking X removes the entry from the queue.\n */\nexport function NotificationStack({\n placement = 'top-right',\n maxWidth = 380,\n className,\n}: NotificationStackProps) {\n const { notifications, dismiss } = useNotification()\n if (notifications.length === 0) return null\n\n return (\n <div\n className={[\n 'pointer-events-none fixed z-toast flex flex-col gap-2',\n placementClasses[placement],\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n data-slot=\"notification-stack\"\n data-placement={placement}\n >\n {notifications.map((entry) => (\n <div\n key={entry.id}\n className=\"pointer-events-auto w-full\"\n style={{ maxWidth }}\n >\n <Notification\n {...entry}\n onDismiss={() => dismiss(entry.id)}\n />\n </div>\n ))}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,62 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "@open-mercato/shared/lib/utils";
5
+ import {
6
+ Alert,
7
+ AlertDescription,
8
+ AlertTitle
9
+ } from "./alert.js";
10
+ const Notification = React.forwardRef(
11
+ ({
12
+ status,
13
+ style,
14
+ avatar,
15
+ title,
16
+ description,
17
+ timestamp,
18
+ actions,
19
+ dismissible = true,
20
+ onDismiss,
21
+ dismissAriaLabel,
22
+ className,
23
+ id
24
+ }, ref) => {
25
+ return /* @__PURE__ */ jsxs(
26
+ Alert,
27
+ {
28
+ ref,
29
+ status,
30
+ style,
31
+ size: "default",
32
+ icon: avatar,
33
+ dismissible,
34
+ onDismiss,
35
+ dismissAriaLabel,
36
+ className,
37
+ "data-slot": "notification",
38
+ "data-notification-id": id,
39
+ children: [
40
+ (title || timestamp) && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
41
+ title ? /* @__PURE__ */ jsx(AlertTitle, { className: "mb-0 flex-1 min-w-0", children: title }) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
42
+ timestamp ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs leading-5 opacity-60", "data-slot": "notification-timestamp", children: timestamp }) : null
43
+ ] }),
44
+ description ? /* @__PURE__ */ jsx(AlertDescription, { className: cn("opacity-72", title && "mt-1"), "data-slot": "notification-description", children: description }) : null,
45
+ actions ? /* @__PURE__ */ jsx(
46
+ "div",
47
+ {
48
+ className: cn("flex flex-wrap items-center gap-2", (title || description) && "mt-2.5"),
49
+ "data-slot": "notification-actions",
50
+ children: actions
51
+ }
52
+ ) : null
53
+ ]
54
+ }
55
+ );
56
+ }
57
+ );
58
+ Notification.displayName = "Notification";
59
+ export {
60
+ Notification
61
+ };
62
+ //# sourceMappingURL=notification.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/primitives/notification.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport {\n Alert,\n AlertDescription,\n AlertTitle,\n type AlertStatus,\n type AlertStyle,\n} from './alert'\n\nexport type NotificationProps = {\n /**\n * Notification status. Same five values as `Alert`.\n * Default `'information'`.\n */\n status?: AlertStatus\n /**\n * Visual style. Same four values as `Alert`.\n * Default `'light'`.\n */\n style?: AlertStyle\n /**\n * Avatar/icon override. If provided, replaces the per-status default\n * Lucide icon \u2014 typical for user-driven notifications (\"John\n * commented on...\" with the author avatar). Pass an `Avatar`\n * primitive instance or any 20\u00D720 visual.\n */\n avatar?: React.ReactNode\n /**\n * Notification title. Rendered as `AlertTitle` (Label/Small,\n * `font-medium 14/20`).\n */\n title?: React.ReactNode\n /**\n * Notification body. Rendered as `AlertDescription` at 72% opacity\n * per Figma `170:1839` reference (Paragraph/Small, `font-regular\n * 14/20`).\n */\n description?: React.ReactNode\n /**\n * Right-aligned timestamp hint next to the title (e.g. `\"2 min\n * ago\"`). Pre-formatted by the caller \u2014 pass through\n * `formatRelativeTime()` from `@open-mercato/shared/lib/time` for\n * the standard look.\n */\n timestamp?: React.ReactNode\n /**\n * Action buttons / links rendered under the description. Pass a\n * `<>...</>` fragment or an array of action elements. Separators\n * are not auto-inserted \u2014 wrap your actions in a `<div>` with\n * `gap-2` if you need custom spacing.\n */\n actions?: React.ReactNode\n /** Render the trailing X close button. Defaults to `true` \u2014 Figma reference always shows it. */\n dismissible?: boolean\n onDismiss?: () => void\n dismissAriaLabel?: string\n className?: string\n /** Stable id used by the `NotificationStack` (or external trackers) to identify the card. Forwarded as `data-notification-id`. */\n id?: string\n}\n\n/**\n * Card composition over the `Alert` primitive matching the Figma\n * Notification reference (`170:1839` \u2014 Error/Light/Large). Renders a\n * status icon (or custom `avatar`), a title + timestamp row, an\n * `opacity-72` description, and an optional row of action links. Use\n * with `NotificationStack` for corner-floating manual-dismiss UX, or\n * standalone inside a panel / dialog content area.\n *\n * Notification is intentionally a thin wrapper \u2014 every visual token\n * comes from the `Alert` primitive so the look stays in sync with the\n * unified Alert / Notification / Toast design.\n */\nexport const Notification = React.forwardRef<HTMLDivElement, NotificationProps>(\n (\n {\n status,\n style,\n avatar,\n title,\n description,\n timestamp,\n actions,\n dismissible = true,\n onDismiss,\n dismissAriaLabel,\n className,\n id,\n },\n ref,\n ) => {\n return (\n <Alert\n ref={ref}\n status={status}\n style={style}\n size=\"default\"\n icon={avatar}\n dismissible={dismissible}\n onDismiss={onDismiss}\n dismissAriaLabel={dismissAriaLabel}\n className={className}\n data-slot=\"notification\"\n data-notification-id={id}\n >\n {(title || timestamp) && (\n <div className=\"flex items-start gap-2\">\n {title ? <AlertTitle className=\"mb-0 flex-1 min-w-0\">{title}</AlertTitle> : <span className=\"flex-1\" />}\n {timestamp ? (\n <span className=\"shrink-0 text-xs leading-5 opacity-60\" data-slot=\"notification-timestamp\">\n {timestamp}\n </span>\n ) : null}\n </div>\n )}\n {description ? (\n <AlertDescription className={cn('opacity-72', title && 'mt-1')} data-slot=\"notification-description\">\n {description}\n </AlertDescription>\n ) : null}\n {actions ? (\n <div\n className={cn('flex flex-wrap items-center gap-2', (title || description) && 'mt-2.5')}\n data-slot=\"notification-actions\"\n >\n {actions}\n </div>\n ) : null}\n </Alert>\n )\n },\n)\nNotification.displayName = 'Notification'\n"],
5
+ "mappings": ";AA6GU,SACW,KADX;AA3GV,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAkEA,MAAM,eAAe,MAAM;AAAA,EAChC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GACA,QACG;AACH,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAK;AAAA,QACL,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,aAAU;AAAA,QACV,wBAAsB;AAAA,QAEpB;AAAA,oBAAS,cACT,qBAAC,SAAI,WAAU,0BACZ;AAAA,oBAAQ,oBAAC,cAAW,WAAU,uBAAuB,iBAAM,IAAgB,oBAAC,UAAK,WAAU,UAAS;AAAA,YACpG,YACC,oBAAC,UAAK,WAAU,yCAAwC,aAAU,0BAC/D,qBACH,IACE;AAAA,aACN;AAAA,UAED,cACC,oBAAC,oBAAiB,WAAW,GAAG,cAAc,SAAS,MAAM,GAAG,aAAU,4BACvE,uBACH,IACE;AAAA,UACH,UACC;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,GAAG,sCAAsC,SAAS,gBAAgB,QAAQ;AAAA,cACrF,aAAU;AAAA,cAET;AAAA;AAAA,UACH,IACE;AAAA;AAAA;AAAA,IACN;AAAA,EAEJ;AACF;AACA,aAAa,cAAc;",
6
+ "names": []
7
+ }