sid-ui 0.0.0-stage → 0.1.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 (122) hide show
  1. package/README.md +54 -2
  2. package/package.json +51 -4
  3. package/src/brand/BrandFooter.tsx +45 -0
  4. package/src/brand/BrandHeading.tsx +24 -0
  5. package/src/brand/BrandLockup.tsx +47 -0
  6. package/src/brand/JoinedLogo.tsx +67 -0
  7. package/src/brand/JoinedMark.tsx +76 -0
  8. package/src/brand/geometry.ts +26 -0
  9. package/src/brand/index.ts +11 -0
  10. package/src/brand/name.ts +5 -0
  11. package/src/brand/paint.tsx +56 -0
  12. package/src/components/Action.tsx +44 -0
  13. package/src/components/AddressSelector.tsx +99 -0
  14. package/src/components/BarChart.tsx +132 -0
  15. package/src/components/Calendar.tsx +284 -0
  16. package/src/components/CalendarAgenda.tsx +87 -0
  17. package/src/components/CalendarMonth.tsx +223 -0
  18. package/src/components/CalendarSchedule.tsx +210 -0
  19. package/src/components/Card.tsx +80 -0
  20. package/src/components/Charts.tsx +64 -0
  21. package/src/components/Chat.tsx +35 -0
  22. package/src/components/CitySelector.tsx +81 -0
  23. package/src/components/Clock.tsx +196 -0
  24. package/src/components/CodeInput.tsx +183 -0
  25. package/src/components/Container.tsx +20 -0
  26. package/src/components/Content.tsx +50 -0
  27. package/src/components/Data.tsx +43 -0
  28. package/src/components/DataInput.tsx +107 -0
  29. package/src/components/DateField.tsx +162 -0
  30. package/src/components/DonutChart.tsx +123 -0
  31. package/src/components/Drawer.tsx +110 -0
  32. package/src/components/Feedback.tsx +37 -0
  33. package/src/components/FileUploader.tsx +377 -0
  34. package/src/components/FunnelChart.tsx +83 -0
  35. package/src/components/Glyph.tsx +330 -0
  36. package/src/components/GoogleSignIn.tsx +67 -0
  37. package/src/components/GridSystem.tsx +131 -0
  38. package/src/components/HeatmapCalendar.tsx +167 -0
  39. package/src/components/KanbanBoard.tsx +421 -0
  40. package/src/components/Kpi.tsx +61 -0
  41. package/src/components/LayoutPrimitives.tsx +86 -0
  42. package/src/components/Lists.tsx +19 -0
  43. package/src/components/LocationSelector.tsx +49 -0
  44. package/src/components/MessageList.tsx +80 -0
  45. package/src/components/Nav.tsx +69 -0
  46. package/src/components/Navigation.tsx +52 -0
  47. package/src/components/Notification.tsx +115 -0
  48. package/src/components/NotificationTrigger.tsx +248 -0
  49. package/src/components/Overlay.tsx +50 -0
  50. package/src/components/Page.tsx +143 -0
  51. package/src/components/PageTabs.tsx +28 -0
  52. package/src/components/PickerShell.tsx +138 -0
  53. package/src/components/PillNav.tsx +194 -0
  54. package/src/components/Primitives.tsx +30 -0
  55. package/src/components/Rating.tsx +78 -0
  56. package/src/components/Responsive.tsx +128 -0
  57. package/src/components/ResponsiveFrame.tsx +122 -0
  58. package/src/components/Segment.tsx +190 -0
  59. package/src/components/Sparkline.tsx +52 -0
  60. package/src/components/StateSelector.tsx +39 -0
  61. package/src/components/Sticky.tsx +95 -0
  62. package/src/components/Table.tsx +344 -0
  63. package/src/components/ThemeToggle.tsx +48 -0
  64. package/src/components/Tile.tsx +23 -0
  65. package/src/components/TimeColumns.tsx +155 -0
  66. package/src/components/TimeDial.tsx +260 -0
  67. package/src/components/TimeField.tsx +196 -0
  68. package/src/components/TimeInput.tsx +213 -0
  69. package/src/components/TimeSlots.tsx +83 -0
  70. package/src/components/Timeline.tsx +111 -0
  71. package/src/components/TokenDemo.tsx +378 -0
  72. package/src/components/Tree.tsx +283 -0
  73. package/src/components/TrendChart.tsx +211 -0
  74. package/src/components/breakpoints.ts +42 -0
  75. package/src/components/calendarTypes.ts +26 -0
  76. package/src/components/chartMath.ts +125 -0
  77. package/src/components/chartParts.tsx +116 -0
  78. package/src/components/codeInputRules.ts +58 -0
  79. package/src/components/date.ts +87 -0
  80. package/src/components/hooks.ts +53 -0
  81. package/src/components/index.ts +195 -0
  82. package/src/components/kanban.ts +78 -0
  83. package/src/components/motion.ts +78 -0
  84. package/src/components/places-search.tsx +73 -0
  85. package/src/components/places.ts +209 -0
  86. package/src/components/size.ts +3 -0
  87. package/src/components/time.ts +83 -0
  88. package/src/components/useFileDrop.ts +104 -0
  89. package/src/places/geoapify.ts +129 -0
  90. package/src/styles/astryx.css +5 -0
  91. package/src/styles/components/brand.css +98 -0
  92. package/src/styles/components/calendar.css +680 -0
  93. package/src/styles/components/charts.css +659 -0
  94. package/src/styles/components/code-input.css +233 -0
  95. package/src/styles/components/google.css +11 -0
  96. package/src/styles/components/message-list.css +153 -0
  97. package/src/styles/components/notification.css +350 -0
  98. package/src/styles/components/picker.css +114 -0
  99. package/src/styles/components/pill-nav.css +179 -0
  100. package/src/styles/components/responsive.css +419 -0
  101. package/src/styles/components/sticky.css +23 -0
  102. package/src/styles/components/table.css +267 -0
  103. package/src/styles/components/time.css +526 -0
  104. package/src/styles/components/timeline.css +426 -0
  105. package/src/styles/components/tree.css +269 -0
  106. package/src/styles/components.css +423 -0
  107. package/src/styles/joined.css +4 -0
  108. package/src/styles/theme.css +79 -0
  109. package/src/styles/tokens.css +338 -0
  110. package/src/theme/JoinedProvider.tsx +50 -0
  111. package/src/theme/index.ts +3 -0
  112. package/src/theme/joined.d.ts +11 -0
  113. package/src/theme/joined.js +569 -0
  114. package/src/theme/joined.theme.ts +74 -0
  115. package/src/theme/joined.variants.d.ts +16 -0
  116. package/src/theme/theme.css +786 -0
  117. package/styles/astryx.css +1 -0
  118. package/styles/components.css +1 -0
  119. package/styles/joined.css +1 -0
  120. package/styles/theme.css +1 -0
  121. package/styles/tokens.css +1 -0
  122. package/styles.css +1 -0
@@ -0,0 +1,80 @@
1
+ import { Badge, type BadgeVariant } from "./Feedback";
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ export type MessageListItem = {
6
+ id: string;
7
+ sender: string;
8
+ subject: string;
9
+ /** The first line of the body, shown after the subject in secondary ink. */
10
+ snippet?: string;
11
+ /** Already formatted, e.g. "1:19 PM" or "Oct 2". */
12
+ time: string;
13
+ isUnread?: boolean;
14
+ tag?: { label: string; variant?: BadgeVariant };
15
+ };
16
+
17
+ /**
18
+ * Mail as one line per message: an unread dot, the sender, the subject with its first
19
+ * line, a label, and the time. Rows stack to two lines in a narrow container. A heading
20
+ * such as "Unread" or "Read" can sit above each group.
21
+ */
22
+ export function MessageList({
23
+ items,
24
+ label,
25
+ heading,
26
+ icon,
27
+ selectedId,
28
+ onSelect,
29
+ empty = "No messages.",
30
+ }: {
31
+ items: MessageListItem[];
32
+ /** Accessible name for the list. */
33
+ label: string;
34
+ /** A group title above the rows, with its count. */
35
+ heading?: string;
36
+ icon?: ReactNode;
37
+ selectedId?: string | null;
38
+ onSelect?: (id: string) => void;
39
+ empty?: ReactNode;
40
+ }) {
41
+ return (
42
+ <section className="os-mail">
43
+ {heading ? (
44
+ <header className="os-mail-heading">
45
+ {icon ? <span className="os-mail-heading-icon">{icon}</span> : null}
46
+ <span>{heading}</span>
47
+ <span className="os-mail-count">{items.length}</span>
48
+ </header>
49
+ ) : null}
50
+ {items.length === 0 ? (
51
+ <p className="os-mail-empty">{empty}</p>
52
+ ) : (
53
+ <ul className="os-mail-list" aria-label={label}>
54
+ {items.map((item) => (
55
+ <li key={item.id}>
56
+ <button
57
+ type="button"
58
+ className="os-mail-row"
59
+ data-unread={item.isUnread || undefined}
60
+ aria-current={selectedId === item.id || undefined}
61
+ onClick={() => onSelect?.(item.id)}
62
+ >
63
+ <span className="os-mail-dot" aria-label={item.isUnread ? "Unread" : undefined} />
64
+ <span className="os-mail-sender">{item.sender}</span>
65
+ <span className="os-mail-text">
66
+ <span className="os-mail-subject">{item.subject}</span>
67
+ {item.snippet ? <span className="os-mail-snippet"> — {item.snippet}</span> : null}
68
+ </span>
69
+ <span className="os-mail-tag">
70
+ {item.tag ? <Badge label={item.tag.label} variant={item.tag.variant} /> : null}
71
+ </span>
72
+ <span className="os-mail-time">{item.time}</span>
73
+ </button>
74
+ </li>
75
+ ))}
76
+ </ul>
77
+ )}
78
+ </section>
79
+ );
80
+ }
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import { BRAND_NAME, BrandHeading } from "../brand";
4
+
5
+ import { Button } from "./Action";
6
+ import { Avatar } from "./Content";
7
+ import { TopNav, TopNavItem } from "./LayoutPrimitives";
8
+
9
+ import type { ReactNode } from "react";
10
+
11
+ export interface NavItem {
12
+ label: string;
13
+ active?: boolean;
14
+ href?: string;
15
+ }
16
+
17
+ export interface NavProps {
18
+ /** Product name. The bare brand renders as the wordmark; a sub-product gets the app icon and its name. */
19
+ brand?: string;
20
+ items?: NavItem[];
21
+ cta?: string;
22
+ onCtaClick?: () => void;
23
+ /** The signed-in person; the avatar derives initials from it. */
24
+ userName?: string;
25
+ /** Optional route for the signed-in person's profile. */
26
+ userHref?: string;
27
+ trailing?: ReactNode;
28
+ showAvatar?: boolean;
29
+ }
30
+
31
+ /** The Joined product bar — an Astryx TopNav with one primary action and the signed-in person. */
32
+ export function Nav({
33
+ brand = BRAND_NAME,
34
+ items = [],
35
+ cta,
36
+ onCtaClick,
37
+ userName = "Jordan Miles",
38
+ userHref,
39
+ trailing,
40
+ showAvatar = false,
41
+ }: NavProps) {
42
+ return (
43
+ <TopNav
44
+ label={brand}
45
+ heading={<BrandHeading product={brand} />}
46
+ startContent={
47
+ <>
48
+ {items.map((item) => (
49
+ <TopNavItem
50
+ key={item.label}
51
+ label={item.label}
52
+ href={item.href ?? "#"}
53
+ isSelected={item.active}
54
+ />
55
+ ))}
56
+ </>
57
+ }
58
+ endContent={
59
+ <>
60
+ {cta && <Button label={cta} variant="primary" size="sm" onClick={onCtaClick} />}
61
+ {trailing}
62
+ {showAvatar && (
63
+ <Avatar name={userName} alt={`${userName} profile`} href={userHref} size="sm" />
64
+ )}
65
+ </>
66
+ }
67
+ />
68
+ );
69
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Navigation components are original Astryx — Joined only themes them.
3
+ * TopNav and SideNav live in ./LayoutPrimitives; Nav composes TopNav.
4
+ */
5
+ export {
6
+ Breadcrumbs,
7
+ BreadcrumbItem,
8
+ BreadcrumbMenuItem,
9
+ BreadcrumbMenuDivider,
10
+ } from "@astryxdesign/core/Breadcrumbs";
11
+ export type {
12
+ BreadcrumbsProps,
13
+ BreadcrumbsVariant,
14
+ BreadcrumbItemProps,
15
+ } from "@astryxdesign/core/Breadcrumbs";
16
+
17
+ export { Pagination, generatePageRange } from "@astryxdesign/core/Pagination";
18
+ export type {
19
+ PaginationProps,
20
+ PaginationVariant,
21
+ PaginationSize,
22
+ } from "@astryxdesign/core/Pagination";
23
+
24
+ export { Stepper, Step } from "@astryxdesign/core/Stepper";
25
+ export type {
26
+ StepperProps,
27
+ StepProps,
28
+ StepStatus,
29
+ StepperOrientation,
30
+ StepperCollapsedVariant,
31
+ } from "@astryxdesign/core/Stepper";
32
+
33
+ export { TabList, Tab, TabMenu } from "@astryxdesign/core/TabList";
34
+ export type {
35
+ TabListProps,
36
+ TabProps,
37
+ TabMenuProps,
38
+ TabMenuOption,
39
+ TabListSize,
40
+ TabListOverflow,
41
+ } from "@astryxdesign/core/TabList";
42
+
43
+ export { Outline, parseOutlineFromMarkdown } from "@astryxdesign/core/Outline";
44
+ export type { OutlineProps, OutlineItem } from "@astryxdesign/core/Outline";
45
+
46
+ export {
47
+ TopNavMenu,
48
+ TopNavMegaMenu,
49
+ TopNavMegaMenuItem,
50
+ TopNavMegaMenuFeaturedCard,
51
+ } from "@astryxdesign/core/TopNav";
52
+ export type { TopNavMenuItemData } from "@astryxdesign/core/TopNav";
@@ -0,0 +1,115 @@
1
+ "use client";
2
+
3
+ import { Glyph } from "./Glyph";
4
+
5
+ import type { ReactNode } from "react";
6
+
7
+ /** Accent on the unread mark. The title always says what happened. */
8
+ export type NotificationTone = "accent" | "success" | "warning" | "danger" | "neutral";
9
+
10
+ export interface NotificationProps {
11
+ /** Who or what this is about. */
12
+ title: ReactNode;
13
+ /** The one-line detail under the title. */
14
+ description?: ReactNode;
15
+ /** When it happened — a relative time or a short label. */
16
+ time?: ReactNode;
17
+ /** Leading visual — an avatar or an icon. */
18
+ start?: ReactNode;
19
+ /** A next step, such as a button or a link. */
20
+ action?: ReactNode;
21
+ /** Draws the unread mark. Read items stay quiet. */
22
+ unread?: boolean;
23
+ tone?: NotificationTone;
24
+ href?: string;
25
+ onClick?: () => void;
26
+ /** Removes the item. The button is labelled for assistive tech. */
27
+ onDismiss?: () => void;
28
+ dismissLabel?: string;
29
+ }
30
+
31
+ export interface NotificationListProps {
32
+ children: ReactNode;
33
+ /** Accessible name for the list. */
34
+ label?: string;
35
+ /** Title and actions above the list, such as “Mark all read”. */
36
+ header?: ReactNode;
37
+ }
38
+
39
+ /** A feed of things that happened — unread marks, times, and optional actions. */
40
+ export function NotificationList({
41
+ children,
42
+ label = "Notifications",
43
+ header,
44
+ }: NotificationListProps) {
45
+ return (
46
+ <section className="os-notes" aria-label={header ? undefined : label}>
47
+ {header && <div className="os-notes-header">{header}</div>}
48
+ <ul className="os-notes-list" aria-label={header ? label : undefined}>
49
+ {children}
50
+ </ul>
51
+ </section>
52
+ );
53
+ }
54
+
55
+ /** One item in a notification feed. Unread items announce themselves; the title carries the meaning. */
56
+ export function Notification({
57
+ title,
58
+ description,
59
+ time,
60
+ start,
61
+ action,
62
+ unread = false,
63
+ tone = "accent",
64
+ href,
65
+ onClick,
66
+ onDismiss,
67
+ dismissLabel = "Dismiss",
68
+ }: NotificationProps) {
69
+ const body = (
70
+ <>
71
+ <span
72
+ className={["os-note-mark", unread && `os-note-tone-${tone}`].filter(Boolean).join(" ")}
73
+ aria-hidden
74
+ />
75
+ <span className={start ? "os-note-start" : "os-note-start os-note-start-empty"}>{start}</span>
76
+ <span className="os-note-body">
77
+ <span className="os-note-title">{title}</span>
78
+ {description != null && <span className="os-note-description">{description}</span>}
79
+ </span>
80
+ {time != null && <span className="os-note-time">{time}</span>}
81
+ </>
82
+ );
83
+
84
+ return (
85
+ <li className={["os-note", unread && "os-note-unread"].filter(Boolean).join(" ")}>
86
+ {href ? (
87
+ <a className="os-note-main" href={href}>
88
+ {body}
89
+ {unread && <span className="os-sr">Unread. </span>}
90
+ </a>
91
+ ) : onClick ? (
92
+ <button type="button" className="os-note-main" onClick={onClick}>
93
+ {body}
94
+ {unread && <span className="os-sr">Unread. </span>}
95
+ </button>
96
+ ) : (
97
+ <div className="os-note-main">
98
+ {body}
99
+ {unread && <span className="os-sr">Unread. </span>}
100
+ </div>
101
+ )}
102
+ {action && <span className="os-note-action">{action}</span>}
103
+ {onDismiss && (
104
+ <button
105
+ type="button"
106
+ className="os-note-dismiss"
107
+ aria-label={dismissLabel}
108
+ onClick={onDismiss}
109
+ >
110
+ <Glyph name="close" />
111
+ </button>
112
+ )}
113
+ </li>
114
+ );
115
+ }
@@ -0,0 +1,248 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useId,
9
+ useRef,
10
+ useState,
11
+ type ReactNode,
12
+ } from "react";
13
+ import { createPortal } from "react-dom";
14
+
15
+ import { Glyph } from "./Glyph";
16
+
17
+ import type { NotificationTone } from "./Notification";
18
+
19
+ /** Where a triggered notification sits. Logical start/end mirror under RTL. */
20
+ export type NotificationPosition = "topStart" | "topEnd" | "bottomStart" | "bottomEnd";
21
+
22
+ export interface ShowNotificationOptions {
23
+ title: ReactNode;
24
+ description?: ReactNode;
25
+ tone?: NotificationTone;
26
+ /** Corner of the viewport. */
27
+ position?: NotificationPosition;
28
+ /** How long it stays. 0 keeps it until dismissed. */
29
+ duration?: number;
30
+ }
31
+
32
+ export type NotificationDismiss = () => void;
33
+
34
+ const POSITIONS: NotificationPosition[] = ["topStart", "topEnd", "bottomStart", "bottomEnd"];
35
+ const DEFAULT_DURATION_MS = 5000;
36
+ const DEFAULT_POSITION: NotificationPosition = "topEnd";
37
+ /** Matches --duration-slow, so the card finishes its exit before it unmounts. */
38
+ const EXIT_MS = 200;
39
+
40
+ interface Notice {
41
+ id: number;
42
+ title: ReactNode;
43
+ description?: ReactNode;
44
+ tone: NotificationTone;
45
+ position: NotificationPosition;
46
+ duration: number;
47
+ leaving: boolean;
48
+ }
49
+
50
+ function exitDelay() {
51
+ if (
52
+ typeof window !== "undefined" &&
53
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches
54
+ )
55
+ return 0;
56
+ return EXIT_MS;
57
+ }
58
+
59
+ interface NotificationApi {
60
+ show: (options: ShowNotificationOptions) => NotificationDismiss;
61
+ dismiss: (id: number) => void;
62
+ items: Notice[];
63
+ }
64
+
65
+ const NotificationContext = createContext<NotificationApi | null>(null);
66
+
67
+ /**
68
+ * Mounts the four corners. JoinedProvider includes this, so useNotification
69
+ * works anywhere inside it.
70
+ */
71
+ export function NotificationViewport({ children }: { children: ReactNode }) {
72
+ const [items, setItems] = useState<Notice[]>([]);
73
+ const nextId = useRef(0);
74
+
75
+ const dismiss = useCallback((id: number) => {
76
+ setItems((all) => all.map((item) => (item.id === id ? { ...item, leaving: true } : item)));
77
+ }, []);
78
+
79
+ const remove = useCallback((id: number) => {
80
+ setItems((all) => all.filter((item) => item.id !== id));
81
+ }, []);
82
+
83
+ const show = useCallback(
84
+ (options: ShowNotificationOptions): NotificationDismiss => {
85
+ const id = nextId.current + 1;
86
+ nextId.current = id;
87
+ const notice: Notice = {
88
+ id,
89
+ title: options.title,
90
+ description: options.description,
91
+ tone: options.tone ?? "accent",
92
+ position: options.position ?? DEFAULT_POSITION,
93
+ duration: options.duration ?? DEFAULT_DURATION_MS,
94
+ leaving: false,
95
+ };
96
+ setItems((all) => [notice, ...all]);
97
+ return () => dismiss(id);
98
+ },
99
+ [dismiss],
100
+ );
101
+
102
+ return (
103
+ <NotificationContext.Provider value={{ show, dismiss, items }}>
104
+ {children}
105
+ <NotificationStacks items={items} onDismiss={dismiss} onRemove={remove} />
106
+ </NotificationContext.Provider>
107
+ );
108
+ }
109
+
110
+ /** Show a notification at a corner. Returns a function that dismisses it. */
111
+ export function useNotification(): (options: ShowNotificationOptions) => NotificationDismiss {
112
+ const api = useContext(NotificationContext);
113
+ return useCallback(
114
+ (options: ShowNotificationOptions) => {
115
+ if (!api) {
116
+ throw new Error("useNotification must be used inside NotificationViewport");
117
+ }
118
+ return api.show(options);
119
+ },
120
+ [api],
121
+ );
122
+ }
123
+
124
+ function NotificationStacks({
125
+ items,
126
+ onDismiss,
127
+ onRemove,
128
+ }: {
129
+ items: Notice[];
130
+ onDismiss: (id: number) => void;
131
+ onRemove: (id: number) => void;
132
+ }) {
133
+ const layerRef = useRef<HTMLDivElement>(null);
134
+ const [mounted, setMounted] = useState(false);
135
+ useEffect(() => setMounted(true), []);
136
+
137
+ useEffect(() => {
138
+ const layer = layerRef.current;
139
+ if (!layer || typeof layer.showPopover !== "function") return;
140
+ const open = items.length > 0;
141
+ const isOpen = layer.matches(":popover-open");
142
+ if (open && !isOpen) layer.showPopover();
143
+ if (!open && isOpen) layer.hidePopover();
144
+ }, [items.length]);
145
+
146
+ if (!mounted) return null;
147
+
148
+ return createPortal(
149
+ <div ref={layerRef} popover="manual" className="os-note-layer">
150
+ {POSITIONS.map((position) => (
151
+ <NotificationStack
152
+ key={position}
153
+ position={position}
154
+ items={items.filter((item) => item.position === position)}
155
+ onDismiss={onDismiss}
156
+ onRemove={onRemove}
157
+ />
158
+ ))}
159
+ </div>,
160
+ document.body,
161
+ );
162
+ }
163
+
164
+ function NotificationStack({
165
+ position,
166
+ items,
167
+ onDismiss,
168
+ onRemove,
169
+ }: {
170
+ position: NotificationPosition;
171
+ items: Notice[];
172
+ onDismiss: (id: number) => void;
173
+ onRemove: (id: number) => void;
174
+ }) {
175
+ if (items.length === 0) return null;
176
+ const fromBottom = position.startsWith("bottom");
177
+ return (
178
+ <div
179
+ className={[
180
+ "os-note-stack",
181
+ `os-note-stack-${position}`,
182
+ fromBottom && "os-note-stack-reverse",
183
+ ]
184
+ .filter(Boolean)
185
+ .join(" ")}
186
+ role="region"
187
+ aria-label="Notifications"
188
+ >
189
+ {items.map((item) => (
190
+ <NotificationCard
191
+ key={item.id}
192
+ item={item}
193
+ onDismiss={() => onDismiss(item.id)}
194
+ onRemove={() => onRemove(item.id)}
195
+ />
196
+ ))}
197
+ </div>
198
+ );
199
+ }
200
+
201
+ function NotificationCard({
202
+ item,
203
+ onDismiss,
204
+ onRemove,
205
+ }: {
206
+ item: Notice;
207
+ onDismiss: () => void;
208
+ onRemove: () => void;
209
+ }) {
210
+ const titleId = useId();
211
+
212
+ useEffect(() => {
213
+ if (item.duration === 0 || item.leaving) return;
214
+ const timer = window.setTimeout(onDismiss, item.duration);
215
+ return () => window.clearTimeout(timer);
216
+ }, [item.duration, item.leaving, onDismiss]);
217
+
218
+ useEffect(() => {
219
+ if (!item.leaving) return;
220
+ const timer = window.setTimeout(onRemove, exitDelay());
221
+ return () => window.clearTimeout(timer);
222
+ }, [item.leaving, onRemove]);
223
+
224
+ return (
225
+ <div
226
+ className={["os-note-slot", item.leaving && "os-note-slot-leaving"].filter(Boolean).join(" ")}
227
+ >
228
+ <div
229
+ className={`os-note-card os-note-card-${item.tone}`}
230
+ role={item.tone === "danger" ? "alert" : "status"}
231
+ aria-labelledby={titleId}
232
+ >
233
+ <span className="os-note-card-mark" aria-hidden />
234
+ <span className="os-note-card-copy">
235
+ <span className="os-note-card-title" id={titleId}>
236
+ {item.title}
237
+ </span>
238
+ {item.description != null && (
239
+ <span className="os-note-card-description">{item.description}</span>
240
+ )}
241
+ </span>
242
+ <button type="button" className="os-note-dismiss" aria-label="Dismiss" onClick={onDismiss}>
243
+ <Glyph name="close" />
244
+ </button>
245
+ </div>
246
+ </div>
247
+ );
248
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Overlays are original Astryx — Joined only themes them. Tooltip lives in
3
+ * ./Primitives; DropdownMenu and MoreMenu live in ./Action.
4
+ */
5
+ export { Dialog, DialogHeader, useImperativeDialog } from "@astryxdesign/core/Dialog";
6
+ export type {
7
+ DialogProps,
8
+ DialogHeaderProps,
9
+ DialogVariant,
10
+ DialogPurpose,
11
+ } from "@astryxdesign/core/Dialog";
12
+
13
+ export { AlertDialog, useImperativeAlertDialog } from "@astryxdesign/core/AlertDialog";
14
+ export type { AlertDialogProps } from "@astryxdesign/core/AlertDialog";
15
+
16
+ export { BottomSheet, BottomSheetSwitcher } from "@astryxdesign/core/BottomSheet";
17
+ export type {
18
+ BottomSheetProps,
19
+ BottomSheetHeight,
20
+ BottomSheetSwitcherProps,
21
+ } from "@astryxdesign/core/BottomSheet";
22
+
23
+ export { Popover, usePopover } from "@astryxdesign/core/Popover";
24
+ export type { PopoverProps } from "@astryxdesign/core/Popover";
25
+
26
+ export { HoverCard, useHoverCard } from "@astryxdesign/core/HoverCard";
27
+ export type { HoverCardProps } from "@astryxdesign/core/HoverCard";
28
+
29
+ export { useTooltip } from "@astryxdesign/core/Tooltip";
30
+
31
+ export { ContextMenu, ContextMenuItem, ContextMenuDivider } from "@astryxdesign/core/ContextMenu";
32
+ export type {
33
+ ContextMenuProps,
34
+ ContextMenuItemData,
35
+ ContextMenuOption,
36
+ } from "@astryxdesign/core/ContextMenu";
37
+
38
+ export {
39
+ CommandPalette,
40
+ CommandPaletteInput,
41
+ CommandPaletteList,
42
+ CommandPaletteItem,
43
+ CommandPaletteGroup,
44
+ CommandPaletteFooter,
45
+ CommandPaletteEmpty,
46
+ } from "@astryxdesign/core/CommandPalette";
47
+ export type { CommandPaletteProps } from "@astryxdesign/core/CommandPalette";
48
+
49
+ export { Lightbox, useLightbox } from "@astryxdesign/core/Lightbox";
50
+ export type { LightboxProps, LightboxMedia } from "@astryxdesign/core/Lightbox";