@gunjo/ui 0.0.1-alpha.0 → 0.0.1-alpha.2

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 (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +90 -0
  3. package/README.md +52 -91
  4. package/package.json +47 -6
  5. package/src/components/display/Accordion.tsx +185 -0
  6. package/src/components/display/AccordionGroup.tsx +155 -0
  7. package/src/components/display/ActionDataTable.tsx +413 -0
  8. package/src/components/display/ActivityTimelineCard.tsx +483 -0
  9. package/src/components/display/AnalyticsCard.tsx +167 -0
  10. package/src/components/display/AssetCard.tsx +242 -0
  11. package/src/components/display/AssetGrid.tsx +164 -0
  12. package/src/components/display/Avatar.tsx +127 -0
  13. package/src/components/display/AvatarGroup.tsx +131 -0
  14. package/src/components/{atoms → display}/Badge.tsx +3 -3
  15. package/src/components/display/BarChart.tsx +247 -0
  16. package/src/components/{molecules → display}/Card.tsx +1 -1
  17. package/src/components/display/Carousel.tsx +593 -0
  18. package/src/components/display/ChartLegend.tsx +124 -0
  19. package/src/components/display/ChatMessage.tsx +382 -0
  20. package/src/components/display/ChoroplethMap.tsx +613 -0
  21. package/src/components/display/Code.tsx +42 -0
  22. package/src/components/display/CodeBlock.tsx +338 -0
  23. package/src/components/display/ColorSwatch.tsx +71 -0
  24. package/src/components/display/ConcentricProgressCard.tsx +545 -0
  25. package/src/components/display/DataTable.tsx +522 -0
  26. package/src/components/display/DistributionBar.tsx +102 -0
  27. package/src/components/display/DocNote.tsx +36 -0
  28. package/src/components/display/DonutChart.tsx +257 -0
  29. package/src/components/display/EmptyState.tsx +44 -0
  30. package/src/components/display/FileTree.tsx +180 -0
  31. package/src/components/display/GaugeChart.tsx +219 -0
  32. package/src/components/display/HeatmapChart.tsx +266 -0
  33. package/src/components/display/Icon.tsx +66 -0
  34. package/src/components/display/ImagePreview.tsx +140 -0
  35. package/src/components/{atoms → display}/Img.tsx +46 -12
  36. package/src/components/display/LabeledDonutCard.tsx +475 -0
  37. package/src/components/display/LineChart.tsx +464 -0
  38. package/src/components/{molecules → display}/List.tsx +20 -13
  39. package/src/components/display/MarkdownRenderer.tsx +157 -0
  40. package/src/components/display/MetadataList.tsx +81 -0
  41. package/src/components/display/MiniDistributionBarCard.tsx +314 -0
  42. package/src/components/display/PieChart.tsx +234 -0
  43. package/src/components/display/QuadrantMatrix.tsx +330 -0
  44. package/src/components/display/RadarChart.tsx +335 -0
  45. package/src/components/display/RadialBarChart.tsx +264 -0
  46. package/src/components/display/RetentionCohortCard.tsx +350 -0
  47. package/src/components/display/RibbonChart.tsx +618 -0
  48. package/src/components/display/SearchableAccordion.tsx +270 -0
  49. package/src/components/display/SegmentTimelineCard.tsx +452 -0
  50. package/src/components/display/SegmentedGaugeCard.tsx +607 -0
  51. package/src/components/display/Spacer.tsx +51 -0
  52. package/src/components/display/SparklineChart.tsx +394 -0
  53. package/src/components/display/StackedBarChart.tsx +393 -0
  54. package/src/components/display/Statistic.tsx +70 -0
  55. package/src/components/{molecules → display}/Table.tsx +22 -7
  56. package/src/components/display/Tag.tsx +80 -0
  57. package/src/components/display/TagEditor.tsx +141 -0
  58. package/src/components/display/Timeline.tsx +121 -0
  59. package/src/components/{atoms → display}/ToolPill.tsx +42 -18
  60. package/src/components/display/TreeView.tsx +226 -0
  61. package/src/components/display/chart-tooltip.tsx +423 -0
  62. package/src/components/display/chart-utils.ts +71 -0
  63. package/src/components/display/circular-chart-utils.ts +147 -0
  64. package/src/components/display/generated/default-variant-keys.ts +90 -0
  65. package/src/components/display/generated/variant-keys.ts +169 -0
  66. package/src/components/{atoms → feedback}/Alert.tsx +12 -5
  67. package/src/components/feedback/Banner.tsx +90 -0
  68. package/src/components/{molecules → feedback}/NotificationCenter.tsx +64 -31
  69. package/src/components/feedback/ProgressWidget.tsx +44 -0
  70. package/src/components/{atoms → feedback}/Spinner.tsx +2 -2
  71. package/src/components/{molecules → feedback}/StatusBar.tsx +4 -4
  72. package/src/components/feedback/StatusScreen.tsx +148 -0
  73. package/src/components/{molecules → feedback}/Stepper.tsx +10 -5
  74. package/src/components/feedback/Toast.tsx +108 -0
  75. package/src/components/feedback/ToastProvider.tsx +78 -0
  76. package/src/components/feedback/generated/default-variant-keys.ts +16 -0
  77. package/src/components/feedback/generated/variant-keys.ts +21 -0
  78. package/src/components/generated/component-manifest.ts +1568 -454
  79. package/src/components/generated/component-style-hints.ts +1958 -718
  80. package/src/components/{atoms → inputs}/ButtonVariants.ts +13 -3
  81. package/src/components/inputs/Calendar.tsx +212 -0
  82. package/src/components/inputs/ChatComposer.tsx +75 -0
  83. package/src/components/inputs/ChatInput.tsx +528 -0
  84. package/src/components/{atoms → inputs}/Checkbox.tsx +2 -2
  85. package/src/components/inputs/Combobox.tsx +175 -0
  86. package/src/components/inputs/CopyButton.tsx +187 -0
  87. package/src/components/inputs/DatePicker.tsx +519 -0
  88. package/src/components/inputs/DateRangePicker.tsx +878 -0
  89. package/src/components/inputs/EditableField.tsx +182 -0
  90. package/src/components/{organisms → inputs}/FileUploader.tsx +24 -9
  91. package/src/components/inputs/FilterButton.tsx +163 -0
  92. package/src/components/{molecules → inputs}/Form.tsx +20 -3
  93. package/src/components/{atoms → inputs}/Input.tsx +2 -0
  94. package/src/components/inputs/InputOTP.tsx +75 -0
  95. package/src/components/inputs/Mention.tsx +279 -0
  96. package/src/components/inputs/NumberInput.tsx +109 -0
  97. package/src/components/inputs/PasswordGroup.tsx +138 -0
  98. package/src/components/inputs/PasswordInput.tsx +74 -0
  99. package/src/components/inputs/PasswordRequirementList.tsx +96 -0
  100. package/src/components/inputs/PasswordStrengthMeter.tsx +93 -0
  101. package/src/components/inputs/PhoneInput.tsx +99 -0
  102. package/src/components/inputs/PostalCodeInput.tsx +98 -0
  103. package/src/components/inputs/RangeSlider.tsx +129 -0
  104. package/src/components/inputs/SearchInput.tsx +76 -0
  105. package/src/components/inputs/Select.tsx +39 -0
  106. package/src/components/{atoms → inputs}/Slider.tsx +18 -5
  107. package/src/components/{molecules → inputs}/SortButton.tsx +5 -2
  108. package/src/components/{atoms → inputs}/Switch.tsx +15 -4
  109. package/src/components/inputs/TagInput.tsx +114 -0
  110. package/src/components/{atoms → inputs}/Textarea.tsx +1 -0
  111. package/src/components/inputs/TimePicker.tsx +150 -0
  112. package/src/components/inputs/Toggle.tsx +48 -0
  113. package/src/components/{atoms → inputs}/ToggleGroup.tsx +2 -2
  114. package/src/components/inputs/TooltipButton.tsx +148 -0
  115. package/src/components/inputs/VoiceInputButton.tsx +317 -0
  116. package/src/components/inputs/calendar-holidays.ts +56 -0
  117. package/src/components/inputs/generated/default-variant-keys.ts +32 -0
  118. package/src/components/{atoms → inputs}/generated/variant-keys.ts +19 -27
  119. package/src/components/layout/AspectRatio.tsx +12 -0
  120. package/src/components/layout/AssetInspectorPanel.tsx +416 -0
  121. package/src/components/layout/Cluster.tsx +56 -0
  122. package/src/components/layout/CollapsiblePanelToggle.tsx +94 -0
  123. package/src/components/layout/Container.tsx +43 -0
  124. package/src/components/layout/DeviceFrame.tsx +227 -0
  125. package/src/components/layout/Grid.tsx +65 -0
  126. package/src/components/layout/HStack.tsx +73 -0
  127. package/src/components/{organisms → layout}/InspectorPanel.tsx +6 -5
  128. package/src/components/layout/MarqueeFrame.tsx +158 -0
  129. package/src/components/layout/Resizable.tsx +94 -0
  130. package/src/components/layout/ScrollArea.tsx +71 -0
  131. package/src/components/{organisms → layout}/SpatialCanvas.tsx +12 -7
  132. package/src/components/layout/VStack.tsx +69 -0
  133. package/src/components/layout/generated/default-variant-keys.ts +16 -0
  134. package/src/components/layout/generated/variant-keys.ts +21 -0
  135. package/src/components/{molecules → navigation}/Breadcrumb.tsx +5 -4
  136. package/src/components/navigation/Command.tsx +266 -0
  137. package/src/components/navigation/CommandPalette.tsx +83 -0
  138. package/src/components/navigation/DocumentPager.tsx +171 -0
  139. package/src/components/navigation/Footer.tsx +88 -0
  140. package/src/components/navigation/Header.tsx +80 -0
  141. package/src/components/{molecules → navigation}/Menubar.tsx +45 -12
  142. package/src/components/navigation/NavigationMenu.tsx +128 -0
  143. package/src/components/navigation/PageAside.tsx +84 -0
  144. package/src/components/{molecules → navigation}/Pagination.tsx +60 -7
  145. package/src/components/{organisms → navigation}/RightRail.tsx +1 -1
  146. package/src/components/navigation/Sidebar.tsx +223 -0
  147. package/src/components/navigation/SidebarItem.tsx +160 -0
  148. package/src/components/{molecules → navigation}/Tabs.tsx +2 -2
  149. package/src/components/navigation/TextLink.tsx +71 -0
  150. package/src/components/navigation/generated/default-variant-keys.ts +12 -0
  151. package/src/components/navigation/generated/variant-keys.ts +13 -0
  152. package/src/components/overlay/AIChatInput.tsx +5 -0
  153. package/src/components/overlay/AIChatMessage.tsx +6 -0
  154. package/src/components/overlay/AlertDialog.tsx +145 -0
  155. package/src/components/overlay/ChatPanel.tsx +180 -0
  156. package/src/components/{molecules → overlay}/ContextMenu.tsx +65 -29
  157. package/src/components/{molecules → overlay}/Dialog.tsx +21 -13
  158. package/src/components/overlay/Drawer.tsx +131 -0
  159. package/src/components/{molecules → overlay}/DropdownMenu.tsx +52 -17
  160. package/src/components/overlay/FloatingPanel.tsx +90 -0
  161. package/src/components/overlay/HoverCard.tsx +36 -0
  162. package/src/components/overlay/MediaLightbox.tsx +403 -0
  163. package/src/components/overlay/MediaPickerDialog.tsx +198 -0
  164. package/src/components/overlay/Modal.tsx +103 -0
  165. package/src/components/overlay/OnboardingFlow.tsx +172 -0
  166. package/src/components/overlay/Popover.tsx +36 -0
  167. package/src/components/overlay/ShareModal.tsx +324 -0
  168. package/src/components/{molecules → overlay}/Sheet.tsx +76 -19
  169. package/src/components/overlay/Tooltip.tsx +130 -0
  170. package/src/components/overlay/generated/default-variant-keys.ts +14 -0
  171. package/src/components/overlay/generated/variant-keys.ts +17 -0
  172. package/src/components/patterns/BlogTemplate.tsx +46 -0
  173. package/src/components/{templates → patterns}/DashboardTemplate.tsx +2 -2
  174. package/src/components/patterns/DocsTemplate.tsx +41 -0
  175. package/src/components/{templates → patterns}/MediaLibraryTemplate.tsx +1 -1
  176. package/src/components/patterns/OnboardingTemplate.tsx +32 -0
  177. package/src/components/patterns/PricingTemplate.tsx +106 -0
  178. package/src/globals.css +173 -22
  179. package/src/index.ts +177 -76
  180. package/tailwind-theme-extend.cjs +48 -3
  181. package/design/atoms-metadata.json +0 -82
  182. package/design/molecules-metadata.json +0 -130
  183. package/design/organisms-metadata.json +0 -38
  184. package/design/templates-metadata.json +0 -38
  185. package/src/components/atoms/Avatar.tsx +0 -57
  186. package/src/components/atoms/Select.tsx +0 -28
  187. package/src/components/atoms/generated/default-variant-keys.ts +0 -36
  188. package/src/components/molecules/AIChatInput.tsx +0 -140
  189. package/src/components/molecules/AIChatMessage.tsx +0 -109
  190. package/src/components/molecules/Accordion.tsx +0 -99
  191. package/src/components/molecules/Calendar.tsx +0 -60
  192. package/src/components/molecules/Carousel.tsx +0 -261
  193. package/src/components/molecules/Command.tsx +0 -152
  194. package/src/components/molecules/FilterButton.tsx +0 -133
  195. package/src/components/molecules/HoverCard.tsx +0 -29
  196. package/src/components/molecules/Modal.tsx +0 -66
  197. package/src/components/molecules/Popover.tsx +0 -31
  198. package/src/components/molecules/ProgressWidget.tsx +0 -40
  199. package/src/components/molecules/Resizable.tsx +0 -47
  200. package/src/components/molecules/ScrollArea.tsx +0 -48
  201. package/src/components/molecules/SidebarItem.tsx +0 -134
  202. package/src/components/molecules/Toast.tsx +0 -57
  203. package/src/components/molecules/Tooltip.tsx +0 -30
  204. package/src/components/molecules/generated/default-variant-keys.ts +0 -22
  205. package/src/components/molecules/generated/variant-keys.ts +0 -33
  206. package/src/components/organisms/CommandPalette.tsx +0 -58
  207. package/src/components/organisms/FloatingPanel.tsx +0 -46
  208. package/src/components/organisms/ShareModal.tsx +0 -182
  209. package/src/components/organisms/ToastProvider.tsx +0 -49
  210. /package/src/components/{atoms → display}/Kbd.tsx +0 -0
  211. /package/src/components/{atoms → display}/Separator.tsx +0 -0
  212. /package/src/components/{atoms → display}/Skeleton.tsx +0 -0
  213. /package/src/components/{atoms → feedback}/Progress.tsx +0 -0
  214. /package/src/components/{atoms → inputs}/Button.tsx +0 -0
  215. /package/src/components/{atoms → inputs}/Label.tsx +0 -0
  216. /package/src/components/{atoms → inputs}/RadioGroup.tsx +0 -0
  217. /package/src/components/{organisms → navigation}/AppRail.tsx +0 -0
  218. /package/src/components/{templates → patterns}/AuthTemplate.tsx +0 -0
  219. /package/src/components/{templates → patterns}/BannalyzeTemplate.tsx +0 -0
  220. /package/src/components/{templates → patterns}/ChatTemplate.tsx +0 -0
  221. /package/src/components/{templates → patterns}/EditorTemplate.tsx +0 -0
  222. /package/src/components/{templates → patterns}/KanbanTemplate.tsx +0 -0
  223. /package/src/components/{templates → patterns}/LandingTemplate.tsx +0 -0
  224. /package/src/components/{templates → patterns}/SettingsTemplate.tsx +0 -0
@@ -0,0 +1,148 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+ import { statusScreenDefaultVariantKey } from "./generated/default-variant-keys"
5
+ import type { StatusScreenVariantKey } from "./generated/variant-keys"
6
+
7
+ export type StatusScreenVariant = StatusScreenVariantKey
8
+
9
+ export interface StatusScreenProps
10
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
11
+ /** Selects sensible defaults for code / icon tone / title / description. */
12
+ variant?: StatusScreenVariant
13
+ /** Large code shown at the top, such as "404". Overrides the variant default. */
14
+ code?: React.ReactNode
15
+ /** Icon shown in place of the code for icon-led states. */
16
+ icon?: React.ReactNode
17
+ title?: React.ReactNode
18
+ description?: React.ReactNode
19
+ /** Primary action or action group, usually Button. */
20
+ action?: React.ReactNode
21
+ /** Optional muted detail block, such as request ID or diagnostic text. */
22
+ details?: React.ReactNode
23
+ }
24
+
25
+ type VariantPreset = {
26
+ code?: string
27
+ tone: "default" | "destructive"
28
+ title: string
29
+ description: string
30
+ }
31
+
32
+ const VARIANT_PRESETS: Record<StatusScreenVariantKey, VariantPreset> = {
33
+ "not-found": {
34
+ code: "404",
35
+ tone: "default",
36
+ title: "Page not found",
37
+ description:
38
+ "The page you are looking for doesn't exist or has been moved.",
39
+ },
40
+ error: {
41
+ code: "500",
42
+ tone: "destructive",
43
+ title: "Something went wrong",
44
+ description: "An unexpected error occurred. We've been notified.",
45
+ },
46
+ forbidden: {
47
+ code: "403",
48
+ tone: "destructive",
49
+ title: "Access denied",
50
+ description: "You don't have permission to view this page.",
51
+ },
52
+ offline: {
53
+ tone: "default",
54
+ title: "You're offline",
55
+ description: "Check your connection and try again.",
56
+ },
57
+ maintenance: {
58
+ tone: "default",
59
+ title: "Under maintenance",
60
+ description: "We'll be back shortly. Thanks for your patience.",
61
+ },
62
+ "coming-soon": {
63
+ tone: "default",
64
+ title: "Coming soon",
65
+ description: "This page isn't available yet. Check back soon.",
66
+ },
67
+ }
68
+
69
+ /**
70
+ * Full-page status screen behind 404 / 500 / offline / 403 / maintenance /
71
+ * coming-soon states. Pick a variant for defaults, then override code, icon,
72
+ * title, description, action, or details as needed.
73
+ */
74
+ const StatusScreen = React.forwardRef<HTMLDivElement, StatusScreenProps>(
75
+ (
76
+ {
77
+ className,
78
+ variant = statusScreenDefaultVariantKey,
79
+ code,
80
+ icon,
81
+ title,
82
+ description,
83
+ action,
84
+ details,
85
+ children,
86
+ ...props
87
+ },
88
+ ref
89
+ ) => {
90
+ const preset = VARIANT_PRESETS[variant] ?? VARIANT_PRESETS["not-found"]
91
+ const resolvedCode = code ?? preset.code
92
+ const resolvedTitle = title ?? preset.title
93
+ const resolvedDescription = description ?? preset.description
94
+
95
+ return (
96
+ <div
97
+ ref={ref}
98
+ data-variant={variant}
99
+ className={cn(
100
+ "flex min-h-[60vh] w-full flex-col items-center justify-center gap-4 p-8 text-center",
101
+ className
102
+ )}
103
+ {...props}
104
+ >
105
+ {icon ? (
106
+ <div
107
+ className={cn(
108
+ "flex h-16 w-16 items-center justify-center [&_svg]:h-16 [&_svg]:w-16",
109
+ preset.tone === "destructive"
110
+ ? "text-destructive"
111
+ : "text-muted-foreground"
112
+ )}
113
+ aria-hidden="true"
114
+ >
115
+ {icon}
116
+ </div>
117
+ ) : resolvedCode ? (
118
+ <p
119
+ className={cn(
120
+ "text-7xl font-extrabold tracking-tight",
121
+ preset.tone === "destructive"
122
+ ? "text-destructive"
123
+ : "text-foreground"
124
+ )}
125
+ >
126
+ {resolvedCode}
127
+ </p>
128
+ ) : null}
129
+ <p className="text-2xl font-semibold text-foreground">
130
+ {resolvedTitle}
131
+ </p>
132
+ <p className="max-w-md text-sm text-muted-foreground">
133
+ {resolvedDescription}
134
+ </p>
135
+ {action ? <div className="mt-2">{action}</div> : null}
136
+ {details ? (
137
+ <pre className="mt-4 max-w-xl overflow-x-auto rounded-md bg-muted p-3 text-left font-mono text-xs text-muted-foreground">
138
+ {details}
139
+ </pre>
140
+ ) : null}
141
+ {children}
142
+ </div>
143
+ )
144
+ }
145
+ )
146
+ StatusScreen.displayName = "StatusScreen"
147
+
148
+ export { StatusScreen }
@@ -1,5 +1,5 @@
1
1
  import * as React from "react"
2
- import { Check } from "lucide-react"
2
+ import { IconCheck as Check } from "@tabler/icons-react";
3
3
  import { cva, type VariantProps } from "class-variance-authority"
4
4
  import { cn } from "../../lib/utils"
5
5
 
@@ -56,11 +56,12 @@ function StepCircle({ state, index }: { state: StepperState; index: number }) {
56
56
  }
57
57
 
58
58
  const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(
59
- ({ className, orientation, steps, ...props }, ref) => {
59
+ ({ className, orientation, steps, role = "list", ...props }, ref) => {
60
60
  const isHorizontal = orientation !== "vertical"
61
61
  return (
62
62
  <div
63
63
  ref={ref}
64
+ role={role}
64
65
  className={cn(stepperVariants({ orientation }), "p-4", className)}
65
66
  {...props}
66
67
  >
@@ -69,15 +70,19 @@ const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(
69
70
  return (
70
71
  <React.Fragment key={`${step.label}-${idx}`}>
71
72
  <div
73
+ role="listitem"
74
+ aria-current={step.state === "current" ? "step" : undefined}
75
+ data-stepper-item
72
76
  className={cn(
73
- "flex items-center gap-2",
74
- isHorizontal ? "flex-col" : "flex-row"
77
+ "flex min-w-0 items-center gap-2",
78
+ isHorizontal ? "w-16 shrink-0 flex-col text-center sm:w-20" : "flex-row"
75
79
  )}
76
80
  >
77
81
  <StepCircle state={step.state} index={idx} />
78
82
  <span
83
+ data-stepper-label
79
84
  className={cn(
80
- "text-xs font-medium",
85
+ "max-w-full text-xs font-medium leading-tight",
81
86
  step.state === "upcoming"
82
87
  ? "text-muted-foreground"
83
88
  : "text-foreground"
@@ -0,0 +1,108 @@
1
+ "use client"
2
+ import React, { useEffect, useState } from 'react';
3
+ import { IconAlertTriangle, IconCircleCheck, IconInfoCircle, IconX } from "@tabler/icons-react";
4
+ import { cn } from '../../lib/utils'; // Relative path to lib
5
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../overlay/Tooltip";
6
+ import type { ToastVariantKey } from "./generated/variant-keys";
7
+ import { toastDefaultVariantKey } from "./generated/default-variant-keys";
8
+
9
+ type TooltipContentProps = React.ComponentPropsWithoutRef<typeof TooltipContent>;
10
+
11
+ export type ToastType = ToastVariantKey;
12
+
13
+ export interface ToastProps {
14
+ message: string;
15
+ type?: ToastType;
16
+ isVisible: boolean;
17
+ onClose: () => void;
18
+ duration?: number;
19
+ placement?: "fixed" | "inline";
20
+ closeLabel?: string;
21
+ tooltipPortalContainer?: TooltipContentProps["portalContainer"];
22
+ className?: string;
23
+ }
24
+
25
+ export function Toast({
26
+ message,
27
+ type = toastDefaultVariantKey,
28
+ isVisible,
29
+ onClose,
30
+ duration = 3000,
31
+ placement = "fixed",
32
+ closeLabel = "Close notification",
33
+ tooltipPortalContainer,
34
+ className,
35
+ }: ToastProps) {
36
+ const [shouldRender, setShouldRender] = useState(isVisible);
37
+
38
+ useEffect(() => {
39
+ if (isVisible) {
40
+ const timer = setTimeout(() => {
41
+ onClose();
42
+ }, duration);
43
+ return () => clearTimeout(timer);
44
+ }
45
+ }, [isVisible, duration, onClose]);
46
+
47
+ useEffect(() => {
48
+ if (isVisible) {
49
+ setShouldRender(true);
50
+ return;
51
+ }
52
+
53
+ if (!shouldRender) return;
54
+
55
+ const timer = setTimeout(() => {
56
+ setShouldRender(false);
57
+ }, 300);
58
+
59
+ return () => clearTimeout(timer);
60
+ }, [isVisible, shouldRender]);
61
+
62
+ if (!shouldRender) return null;
63
+
64
+ const icons: Record<ToastVariantKey, React.ReactNode> = {
65
+ success: <IconCircleCheck className="text-success" size={20} stroke={2} />,
66
+ error: <IconAlertTriangle className="text-destructive" size={20} stroke={2} />,
67
+ info: <IconInfoCircle className="text-info" size={20} stroke={2} />
68
+ };
69
+
70
+ const bgColors: Record<ToastVariantKey, string> = {
71
+ success: 'bg-success-subtle border-success-border text-success-subtle-foreground',
72
+ error: 'bg-destructive-subtle border-destructive-border text-destructive-subtle-foreground',
73
+ info: 'bg-info-subtle border-info-border text-info-subtle-foreground'
74
+ };
75
+
76
+ return (
77
+ <div
78
+ role={type === "error" ? "alert" : "status"}
79
+ className={cn(
80
+ placement === "fixed" ? "fixed left-4 right-4 top-[72px] z-[9999] sm:left-auto sm:right-6" : "relative",
81
+ bgColors[type],
82
+ "flex w-full max-w-[calc(100vw-2rem)] flex-row items-start gap-3 rounded-xl border px-4 py-3 shadow-2xl duration-300 pointer-events-auto transform-gpu will-change-transform [animation-fill-mode:both] sm:w-[360px] sm:max-w-sm",
83
+ isVisible
84
+ ? "animate-in slide-in-from-top-5 fade-in-0"
85
+ : "animate-out slide-out-to-top-5 fade-out-0",
86
+ className
87
+ )}
88
+ >
89
+ {icons[type]}
90
+ <p className="min-w-0 flex-1 break-words text-sm font-medium [overflow-wrap:anywhere]">{message}</p>
91
+ <TooltipProvider>
92
+ <Tooltip>
93
+ <TooltipTrigger asChild>
94
+ <button
95
+ type="button"
96
+ onClick={onClose}
97
+ aria-label={closeLabel}
98
+ className="ml-auto shrink-0 rounded-lg p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
99
+ >
100
+ <IconX size={14} stroke={2} />
101
+ </button>
102
+ </TooltipTrigger>
103
+ <TooltipContent portalContainer={tooltipPortalContainer}>{closeLabel}</TooltipContent>
104
+ </Tooltip>
105
+ </TooltipProvider>
106
+ </div>
107
+ );
108
+ }
@@ -0,0 +1,78 @@
1
+ "use client"
2
+ import React, { createContext, useContext, useState, useCallback } from 'react';
3
+ import { createPortal } from "react-dom";
4
+ import { Toast, ToastType } from './Toast';
5
+
6
+ interface ToastContextType {
7
+ showToast: (message: string, type: ToastType, duration?: number) => void;
8
+ }
9
+
10
+ const ToastContext = createContext<ToastContextType | undefined>(undefined);
11
+
12
+ export interface ToastProviderProps {
13
+ children: React.ReactNode;
14
+ labels?: {
15
+ close?: string;
16
+ };
17
+ portalContainer?: HTMLElement | null;
18
+ placement?: "viewport" | "container";
19
+ }
20
+
21
+ export const ToastProvider = ({ children, labels, portalContainer, placement = "viewport" }: ToastProviderProps) => {
22
+ const [toasts, setToasts] = useState<{ id: number, message: string, type: ToastType, duration: number, isVisible: boolean }[]>([]);
23
+ const [mounted, setMounted] = useState(false);
24
+
25
+ React.useEffect(() => {
26
+ setMounted(true);
27
+ }, []);
28
+
29
+ const showToast = useCallback((message: string, type: ToastType, duration = 3000) => {
30
+ const id = Date.now();
31
+ setToasts(prev => [...prev, { id, message, type, duration, isVisible: true }]);
32
+ }, []);
33
+
34
+ const closeToast = useCallback((id: number) => {
35
+ setToasts(prev => prev.map(t => t.id === id ? { ...t, isVisible: false } : t));
36
+ setTimeout(() => {
37
+ setToasts(prev => prev.filter(t => t.id !== id));
38
+ }, 300);
39
+ }, []);
40
+
41
+ const target = placement === "container" ? portalContainer : portalContainer ?? (mounted ? document.body : null);
42
+ const toastStack = (
43
+ <div
44
+ className={
45
+ placement === "container"
46
+ ? "absolute right-3 top-3 z-[100] flex w-[min(360px,calc(100%-1.5rem))] flex-col items-stretch gap-2 pointer-events-none"
47
+ : "fixed left-4 right-4 top-[72px] z-[100] flex flex-col items-stretch gap-2 pointer-events-none sm:left-auto sm:right-6 sm:w-[360px]"
48
+ }
49
+ >
50
+ {toasts.map(toast => (
51
+ <Toast
52
+ key={toast.id}
53
+ message={toast.message}
54
+ type={toast.type}
55
+ isVisible={toast.isVisible}
56
+ onClose={() => closeToast(toast.id)}
57
+ duration={toast.duration}
58
+ placement="inline"
59
+ closeLabel={labels?.close}
60
+ tooltipPortalContainer={portalContainer}
61
+ />
62
+ ))}
63
+ </div>
64
+ );
65
+
66
+ return (
67
+ <ToastContext.Provider value={{ showToast }}>
68
+ {children}
69
+ {target ? createPortal(toastStack, target) : null}
70
+ </ToastContext.Provider>
71
+ );
72
+ };
73
+
74
+ export const useToast = () => {
75
+ const context = useContext(ToastContext);
76
+ if (!context) throw new Error('useToast must be used within a ToastProvider');
77
+ return context;
78
+ };
@@ -0,0 +1,16 @@
1
+ /* eslint-disable */
2
+ // Generated by `npm run design:sync:components`. Do not edit manually.
3
+
4
+ import type { AlertVariantKey, SpinnerVariantKey, StatusScreenVariantKey, ToastVariantKey } from "./variant-keys";
5
+
6
+ export const alertDefaultVariantKey: AlertVariantKey = "default";
7
+ export const spinnerDefaultVariantKey: SpinnerVariantKey = "default";
8
+ export const statusScreenDefaultVariantKey: StatusScreenVariantKey = "not-found";
9
+ export const toastDefaultVariantKey: ToastVariantKey = "success";
10
+
11
+ export const feedbackDefaultVariantKeys = {
12
+ alert: alertDefaultVariantKey,
13
+ spinner: spinnerDefaultVariantKey,
14
+ statusScreen: statusScreenDefaultVariantKey,
15
+ toast: toastDefaultVariantKey,
16
+ } as const;
@@ -0,0 +1,21 @@
1
+ /* eslint-disable */
2
+ // Generated by `npm run design:sync:components`. Do not edit manually.
3
+
4
+ export const alertVariantKeys = ["default", "destructive", "info", "success", "warning"] as const;
5
+ export type AlertVariantKey = (typeof alertVariantKeys)[number];
6
+
7
+ export const spinnerVariantKeys = ["default", "lg", "sm"] as const;
8
+ export type SpinnerVariantKey = (typeof spinnerVariantKeys)[number];
9
+
10
+ export const statusScreenVariantKeys = ["coming-soon", "error", "forbidden", "maintenance", "not-found", "offline"] as const;
11
+ export type StatusScreenVariantKey = (typeof statusScreenVariantKeys)[number];
12
+
13
+ export const toastVariantKeys = ["error", "info", "success"] as const;
14
+ export type ToastVariantKey = (typeof toastVariantKeys)[number];
15
+
16
+ export const feedbackVariantKeys = {
17
+ alert: alertVariantKeys,
18
+ spinner: spinnerVariantKeys,
19
+ statusScreen: statusScreenVariantKeys,
20
+ toast: toastVariantKeys,
21
+ } as const;