myshell-react-lib 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 (151) hide show
  1. package/README.md +268 -0
  2. package/dist/assets/audio-playing.json +3657 -0
  3. package/dist/index.cjs +9654 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1431 -0
  6. package/dist/index.d.ts +1431 -0
  7. package/dist/index.js +8788 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +140 -0
  10. package/src/common/assets/audio-playing.json +3657 -0
  11. package/src/common/constants/constants.ts +24 -0
  12. package/src/common/constants/types/common.ts +10 -0
  13. package/src/common/hooks/useAudioPlayer.tsx +198 -0
  14. package/src/common/hooks/useDevice.ts +26 -0
  15. package/src/common/hooks/useNativeBridge.ts +42 -0
  16. package/src/common/hooks/useNotification.tsx +179 -0
  17. package/src/common/hooks/useWindowWidth.ts +19 -0
  18. package/src/common/utils/common-helper.ts +81 -0
  19. package/src/components/ItemDemo.tsx +15 -0
  20. package/src/components/accordion.tsx +126 -0
  21. package/src/components/alert-dialog.tsx +148 -0
  22. package/src/components/alert.tsx +65 -0
  23. package/src/components/aspect-ratio.tsx +7 -0
  24. package/src/components/audio-player.tsx +58 -0
  25. package/src/components/avatar.tsx +133 -0
  26. package/src/components/badge.tsx +65 -0
  27. package/src/components/button/button.styles.ts +258 -0
  28. package/src/components/button/button.tsx +215 -0
  29. package/src/components/button/icon-button.styles.ts +101 -0
  30. package/src/components/button/icon-button.tsx +100 -0
  31. package/src/components/button/index.tsx +3 -0
  32. package/src/components/button/link-button.tsx +184 -0
  33. package/src/components/cascader.tsx +175 -0
  34. package/src/components/checkbox.tsx +135 -0
  35. package/src/components/command.tsx +155 -0
  36. package/src/components/context-menu.tsx +198 -0
  37. package/src/components/count-down.tsx +83 -0
  38. package/src/components/custom-notification.tsx +95 -0
  39. package/src/components/dialog.tsx +158 -0
  40. package/src/components/drawer.tsx +116 -0
  41. package/src/components/dropdown-menu.tsx +196 -0
  42. package/src/components/energy-progress.tsx +55 -0
  43. package/src/components/form.tsx +201 -0
  44. package/src/components/group.tsx +9 -0
  45. package/src/components/guide.tsx +243 -0
  46. package/src/components/icon.tsx +89 -0
  47. package/src/components/icons/outline/DownIcon.tsx +18 -0
  48. package/src/components/icons/outline/FilterIcon.tsx +21 -0
  49. package/src/components/icons/outline/arrow-left.tsx +16 -0
  50. package/src/components/icons/outline/arrow-up-tray.tsx +16 -0
  51. package/src/components/icons/outline/check-circle.tsx +17 -0
  52. package/src/components/icons/outline/config.tsx +42 -0
  53. package/src/components/icons/outline/pencil-square.tsx +16 -0
  54. package/src/components/icons/outline/trash.tsx +17 -0
  55. package/src/components/icons/outline/window.tsx +16 -0
  56. package/src/components/icons/outline/x-circle.tsx +17 -0
  57. package/src/components/icons/outline/x-mark.tsx +16 -0
  58. package/src/components/icons/solid/audio-playing.tsx +31 -0
  59. package/src/components/icons/solid/caret-down.tsx +14 -0
  60. package/src/components/icons/solid/code.tsx +18 -0
  61. package/src/components/icons/solid/drag.tsx +14 -0
  62. package/src/components/icons/solid/phone.tsx +23 -0
  63. package/src/components/icons/solid/rectangle-group.tsx +14 -0
  64. package/src/components/image.tsx +151 -0
  65. package/src/components/input.tsx +118 -0
  66. package/src/components/label.tsx +26 -0
  67. package/src/components/link.tsx +123 -0
  68. package/src/components/marquee/index.css +15 -0
  69. package/src/components/marquee/marquee.tsx +220 -0
  70. package/src/components/masonry.tsx +138 -0
  71. package/src/components/menubar.tsx +234 -0
  72. package/src/components/mobile/m-tooltip.tsx +34 -0
  73. package/src/components/modal.tsx +561 -0
  74. package/src/components/navigation-bar.tsx +100 -0
  75. package/src/components/number-input.tsx +143 -0
  76. package/src/components/page-content.tsx +16 -0
  77. package/src/components/popover.tsx +191 -0
  78. package/src/components/progress.tsx +80 -0
  79. package/src/components/radio-group.tsx +44 -0
  80. package/src/components/scroll-area.tsx +49 -0
  81. package/src/components/search-bar.tsx +140 -0
  82. package/src/components/secondary-navigation-bar.tsx +307 -0
  83. package/src/components/select.tsx +273 -0
  84. package/src/components/separator.tsx +31 -0
  85. package/src/components/sheet.tsx +143 -0
  86. package/src/components/skeleton.tsx +20 -0
  87. package/src/components/slider.tsx +160 -0
  88. package/src/components/spinner.tsx +48 -0
  89. package/src/components/swiper/index.module.scss +88 -0
  90. package/src/components/swiper/index.tsx +319 -0
  91. package/src/components/switch.tsx +67 -0
  92. package/src/components/tabs.tsx +325 -0
  93. package/src/components/textarea.tsx +71 -0
  94. package/src/components/toast/toast.tsx +182 -0
  95. package/src/components/toast/toaster.tsx +160 -0
  96. package/src/components/toast/use-toast.tsx +248 -0
  97. package/src/components/toggle-group.tsx +64 -0
  98. package/src/components/toggle.tsx +46 -0
  99. package/src/components/tooltip.tsx +283 -0
  100. package/src/components/typography.tsx +437 -0
  101. package/src/index.ts +66 -0
  102. package/src/lib/utils.ts +62 -0
  103. package/src/stories/Accordion.stories.tsx +64 -0
  104. package/src/stories/AccordionItem.stories.tsx +48 -0
  105. package/src/stories/Avatar.stories.ts +58 -0
  106. package/src/stories/Badge.stories.tsx +40 -0
  107. package/src/stories/BannerSwiper.stories.tsx +102 -0
  108. package/src/stories/Button.stories.tsx +543 -0
  109. package/src/stories/Checkbox.stories.tsx +161 -0
  110. package/src/stories/Configure.mdx +341 -0
  111. package/src/stories/CssProperties.mdx +30 -0
  112. package/src/stories/Description.stories.ts +70 -0
  113. package/src/stories/Display.stories.ts +64 -0
  114. package/src/stories/FeaturedSwiper.stories.tsx +6978 -0
  115. package/src/stories/GridSwiper.stories.tsx +1407 -0
  116. package/src/stories/Guide.stories.tsx +247 -0
  117. package/src/stories/Heading.stories.ts +89 -0
  118. package/src/stories/Icon.stories.ts +77 -0
  119. package/src/stories/IconButton.stories.tsx +301 -0
  120. package/src/stories/IconTextButton.stories.ts +59 -0
  121. package/src/stories/Image.stories.ts +55 -0
  122. package/src/stories/Input.stories.tsx +203 -0
  123. package/src/stories/Modal.stories.tsx +144 -0
  124. package/src/stories/NavigationBar.stories.tsx +81 -0
  125. package/src/stories/Notification.stories.tsx +276 -0
  126. package/src/stories/Popover.stories.tsx +100 -0
  127. package/src/stories/SearchBar.stories.ts +43 -0
  128. package/src/stories/SecondaryNavigationBar.stories.tsx +199 -0
  129. package/src/stories/Select.stories.tsx +107 -0
  130. package/src/stories/Separator.stories.tsx +49 -0
  131. package/src/stories/Spinner.stories.tsx +48 -0
  132. package/src/stories/SubHeading.stories.ts +64 -0
  133. package/src/stories/Swich.stories.tsx +69 -0
  134. package/src/stories/Tabs.stories.tsx +90 -0
  135. package/src/stories/Text.stories.ts +78 -0
  136. package/src/stories/Textarea.stories.tsx +155 -0
  137. package/src/stories/Toast.stories.tsx +424 -0
  138. package/src/stories/Tooltip.stories.tsx +244 -0
  139. package/src/stories/ViewAutoSwiper.stories.tsx +1408 -0
  140. package/src/styles/components-dark.scss +212 -0
  141. package/src/styles/components-light.scss +210 -0
  142. package/src/styles/design-dark.scss +330 -0
  143. package/src/styles/design-light.scss +345 -0
  144. package/src/styles/design2-dark.scss +319 -0
  145. package/src/styles/design2-light.scss +364 -0
  146. package/src/styles/font.css +19 -0
  147. package/src/styles/global.scss +251 -0
  148. package/src/styles/md-viewer.scss +155 -0
  149. package/src/styles/new-tokens.scss +255 -0
  150. package/src/styles/tokens.scss +401 -0
  151. package/src/types/scss.d.ts +24 -0
@@ -0,0 +1,71 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/lib/utils';
4
+
5
+ import { Text } from './typography';
6
+
7
+ export type TextareaProps =
8
+ React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
9
+ maxLengthClassName?: string;
10
+ };
11
+ const Textarea = React.forwardRef<
12
+ HTMLTextAreaElement,
13
+ TextareaProps & { error?: string }
14
+ >(
15
+ (
16
+ { className, maxLength, value, error, maxLengthClassName, ...props },
17
+ ref
18
+ ) => {
19
+ return (
20
+ <div>
21
+ <div className="relative">
22
+ <textarea
23
+ className={cn(
24
+ 'w-full min-h-[123px] p-3 pb-10 rounded-lg border border-Colors-Border-Default bg-Colors-Background-Normal-Primary-Active ',
25
+ 'text-sm text-Colors-Text-Default placeholder:text-Colors-Text-Subtlest ring-offset-cc-Focus-Rings-Brand-default',
26
+ 'hover:border-hovered bg-cc-Input-bg-default hover:bg-cc-Input-bg-hover disabled:bg-cc-Input-bg-disabled aria-[invalid=true]:border-Colors-Border-Critical aria-[invalid=true]:hover:bg-Colors-Background-Critical-Subtle',
27
+ 'aria-[invalid=true]:focus-visible:ring-error file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-Colors-Text-Subtler',
28
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cc-Focus-Rings-Brand-default focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-30',
29
+ (maxLength && (value?.toString()?.length || 0) > maxLength) ||
30
+ error
31
+ ? 'border-Colors-Border-Critical hover:bg-Colors-Background-Critical-Subtle focus-visible:ring-error'
32
+ : '',
33
+ className
34
+ )}
35
+ ref={ref}
36
+ maxLength={maxLength}
37
+ value={value}
38
+ {...props}
39
+ />
40
+ {maxLength ? (
41
+ <div
42
+ className={cn(
43
+ 'absolute text-right bottom-5 right-4 border-Colors-Border-Default',
44
+ maxLengthClassName
45
+ )}
46
+ >
47
+ <Text size="sm" color="subtlest">{`${
48
+ value?.toString()?.length || 0
49
+ }/${maxLength}`}</Text>
50
+ </div>
51
+ ) : null}
52
+ </div>
53
+ {error && (
54
+ <div className="w-full">
55
+ <Text
56
+ className="text-wrap"
57
+ size="sm"
58
+ weight="regular"
59
+ color="critical"
60
+ >
61
+ {error}
62
+ </Text>
63
+ </div>
64
+ )}
65
+ </div>
66
+ );
67
+ }
68
+ );
69
+ Textarea.displayName = 'Textarea';
70
+
71
+ export { Textarea };
@@ -0,0 +1,182 @@
1
+ 'use client';
2
+
3
+ import * as ToastPrimitives from '@radix-ui/react-toast';
4
+ import { cva, type VariantProps } from 'class-variance-authority';
5
+ import * as React from 'react';
6
+ import { cn } from '@/lib/utils';
7
+ import XMarkIcon from '@heroicons/react/24/outline/esm/XMarkIcon';
8
+
9
+ const ToastProvider = ToastPrimitives.Provider;
10
+
11
+ const viewportPositionVariants = cva(
12
+ 'fixed z-[1000001] flex max-h-screen w-full p-0 gap-2',
13
+ {
14
+ variants: {
15
+ position: {
16
+ 'top-left': 'top-6 left-6 flex-col items-start',
17
+ 'top-center': 'top-6 left-1/2 -translate-x-1/2 flex-col items-center',
18
+ 'top-right': 'top-6 right-6 flex-col items-end',
19
+ 'bottom-left': 'bottom-6 left-6 flex-col-reverse items-start',
20
+ 'bottom-center':
21
+ 'bottom-6 left-1/2 -translate-x-1/2 flex-col-reverse items-center',
22
+ 'bottom-right': 'bottom-6 right-6 flex-col-reverse items-end',
23
+ 'left-center': 'left-6 top-1/2 -translate-y-1/2 flex-row items-center',
24
+ 'right-center':
25
+ 'right-6 top-1/2 -translate-y-1/2 flex-row-reverse items-center',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ position: 'top-right',
30
+ },
31
+ }
32
+ );
33
+
34
+ interface ToastViewportProps
35
+ extends React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> {
36
+ position?:
37
+ | 'top-left'
38
+ | 'top-center'
39
+ | 'top-right'
40
+ | 'bottom-left'
41
+ | 'bottom-center'
42
+ | 'bottom-right'
43
+ | 'left-center'
44
+ | 'right-center';
45
+ }
46
+
47
+ const ToastViewport = React.forwardRef<
48
+ React.ElementRef<typeof ToastPrimitives.Viewport>,
49
+ ToastViewportProps
50
+ >(({ className, position, ...props }, ref) => (
51
+ <ToastPrimitives.Viewport
52
+ ref={ref}
53
+ className={cn(viewportPositionVariants({ position }), className)}
54
+ {...props}
55
+ />
56
+ ));
57
+ ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
58
+
59
+ const toastVariants = cva(
60
+ 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border border-Colors-Border-Opaque p-6 pr-8 shadow-modal-default transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 max-w-md',
61
+ {
62
+ variants: {
63
+ variant: {
64
+ info: 'bg-Colors-Background-Normal-Primary-Default p-4',
65
+ warning: 'bg-Colors-Background-Normal-Primary-Default p-4',
66
+ error: 'bg-Colors-Background-Normal-Primary-Default p-4',
67
+ success: 'bg-Colors-Background-Normal-Primary-Default p-4',
68
+ },
69
+ position: {
70
+ 'top-left':
71
+ 'data-[state=open]:slide-in-from-left-full data-[state=closed]:slide-out-to-left-full',
72
+ 'top-center':
73
+ 'data-[state=open]:slide-in-from-top-full data-[state=closed]:slide-out-to-top-full',
74
+ 'top-right':
75
+ 'data-[state=open]:slide-in-from-right-full data-[state=closed]:slide-out-to-right-full',
76
+ 'bottom-left':
77
+ 'data-[state=open]:slide-in-from-left-full data-[state=closed]:slide-out-to-left-full',
78
+ 'bottom-center':
79
+ 'data-[state=open]:slide-in-from-bottom-full data-[state=closed]:slide-out-to-bottom-full',
80
+ 'bottom-right':
81
+ 'data-[state=open]:slide-in-from-right-full data-[state=closed]:slide-out-to-right-full',
82
+ 'left-center':
83
+ 'data-[state=open]:slide-in-from-left-full data-[state=closed]:slide-out-to-left-full',
84
+ 'right-center':
85
+ 'data-[state=open]:slide-in-from-right-full data-[state=closed]:slide-out-to-right-full',
86
+ },
87
+ },
88
+ defaultVariants: {
89
+ variant: 'info',
90
+ position: 'top-right',
91
+ },
92
+ }
93
+ );
94
+
95
+ const Toast = React.forwardRef<
96
+ React.ElementRef<typeof ToastPrimitives.Root>,
97
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
98
+ VariantProps<typeof toastVariants>
99
+ >(({ className, variant, position, ...props }, ref) => {
100
+ return (
101
+ <ToastPrimitives.Root
102
+ ref={ref}
103
+ className={cn(toastVariants({ variant, position }), className)}
104
+ data-position={position}
105
+ {...props}
106
+ />
107
+ );
108
+ });
109
+ Toast.displayName = ToastPrimitives.Root.displayName;
110
+
111
+ const ToastAction = React.forwardRef<
112
+ React.ElementRef<typeof ToastPrimitives.Action>,
113
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
114
+ >(({ className, ...props }, ref) => (
115
+ <ToastPrimitives.Action
116
+ ref={ref}
117
+ className={cn(
118
+ 'inline-flex h-8 shrink-0 items-center justify-center rounded-md border border-slate-200 bg-transparent px-3 text-sm font-medium ring-offset-white transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-slate-100/40 group-[.destructive]:hover:border-red-500/30 group-[.destructive]:hover:bg-red-500 group-[.destructive]:hover:text-slate-50 group-[.destructive]:focus:ring-red-500 dark:border-slate-800 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:focus:ring-slate-300 dark:group-[.destructive]:border-slate-800/40 dark:group-[.destructive]:hover:border-red-900/30 dark:group-[.destructive]:hover:bg-red-900 dark:group-[.destructive]:hover:text-slate-50 dark:group-[.destructive]:focus:ring-red-900',
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ ));
124
+ ToastAction.displayName = ToastPrimitives.Action.displayName;
125
+
126
+ const ToastClose = React.forwardRef<
127
+ React.ElementRef<typeof ToastPrimitives.Close>,
128
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
129
+ >(({ className, ...props }, ref) => (
130
+ <ToastPrimitives.Close
131
+ ref={ref}
132
+ className={cn(
133
+ 'absolute right-4 top-4 rounded-md p-1 text-slate-950/50 transition-opacity hover:text-slate-950 outline-none focus:outline-none focus:ring-2 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:text-slate-50/50 dark:hover:text-slate-50',
134
+ className
135
+ )}
136
+ toast-close=""
137
+ {...props}
138
+ >
139
+ <XMarkIcon className="h-5 w-5" />
140
+ </ToastPrimitives.Close>
141
+ ));
142
+ ToastClose.displayName = ToastPrimitives.Close.displayName;
143
+
144
+ const ToastTitle = React.forwardRef<
145
+ React.ElementRef<typeof ToastPrimitives.Title>,
146
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
147
+ >(({ className, ...props }, ref) => (
148
+ <ToastPrimitives.Title
149
+ ref={ref}
150
+ className={cn('text-sm font-semibold', className)}
151
+ {...props}
152
+ />
153
+ ));
154
+ ToastTitle.displayName = ToastPrimitives.Title.displayName;
155
+
156
+ const ToastDescription = React.forwardRef<
157
+ React.ElementRef<typeof ToastPrimitives.Description>,
158
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
159
+ >(({ className, ...props }, ref) => (
160
+ <ToastPrimitives.Description
161
+ ref={ref}
162
+ className={cn('text-sm opacity-90', className)}
163
+ {...props}
164
+ />
165
+ ));
166
+ ToastDescription.displayName = ToastPrimitives.Description.displayName;
167
+
168
+ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
169
+
170
+ type ToastActionElement = React.ReactElement<typeof ToastAction>;
171
+
172
+ export {
173
+ type ToastProps,
174
+ type ToastActionElement,
175
+ ToastProvider,
176
+ ToastViewport,
177
+ Toast,
178
+ ToastTitle,
179
+ ToastDescription,
180
+ ToastClose,
181
+ ToastAction,
182
+ };
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+
3
+ import CheckCircleIcon from '@heroicons/react/24/outline/esm/CheckCircleIcon';
4
+ import ExclamationCircleIcon from '@heroicons/react/24/outline/esm/ExclamationCircleIcon';
5
+ import InformationCircleIcon from '@heroicons/react/24/outline/esm/InformationCircleIcon';
6
+ import XCircleIcon from '@heroicons/react/24/outline/esm/XCircleIcon';
7
+
8
+ import {
9
+ Toast,
10
+ ToastClose,
11
+ ToastDescription,
12
+ ToastProvider,
13
+ ToastTitle,
14
+ ToastViewport,
15
+ } from './toast';
16
+ import { useToast } from './use-toast';
17
+ import { cn } from '@/lib/utils';
18
+ import React from 'react';
19
+
20
+ type ToastVariant = 'info' | 'success' | 'warning' | 'error' | null | undefined;
21
+
22
+ export interface ToasterProps {
23
+ position?:
24
+ | 'top-left'
25
+ | 'top-center'
26
+ | 'top-right'
27
+ | 'bottom-left'
28
+ | 'bottom-center'
29
+ | 'bottom-right'
30
+ | 'left-center'
31
+ | 'right-center';
32
+ }
33
+
34
+ export function Toaster({ position = 'top-right' }: ToasterProps = {}) {
35
+ const { toasts } = useToast();
36
+
37
+ // 按位置分组
38
+ const positionGroups = React.useMemo(() => {
39
+ const groups: Record<string, typeof toasts> = {};
40
+
41
+ const allPositions = [
42
+ 'top-left',
43
+ 'top-center',
44
+ 'top-right',
45
+ 'bottom-left',
46
+ 'bottom-center',
47
+ 'bottom-right',
48
+ 'left-center',
49
+ 'right-center',
50
+ ];
51
+
52
+ // 初始化所有位置组
53
+ allPositions.forEach((pos) => {
54
+ groups[pos] = [];
55
+ });
56
+
57
+ // 将每个Toast按其position分组,如果没有指定则使用默认位置
58
+ toasts.forEach((toast) => {
59
+ const toastPosition = toast.position || position;
60
+ if (groups[toastPosition]) {
61
+ groups[toastPosition].push(toast);
62
+ } else {
63
+ // 如果位置不在预定义列表中,则使用默认位置
64
+ groups[position].push(toast);
65
+ }
66
+ });
67
+
68
+ return groups;
69
+ }, [toasts, position]);
70
+
71
+ const renderToast = (toast: any) => {
72
+ const {
73
+ id,
74
+ title,
75
+ description,
76
+ action,
77
+ variant,
78
+ position: toastPosition,
79
+ } = toast;
80
+
81
+ const renderIcon = (variant: ToastVariant) => {
82
+ switch (variant) {
83
+ case 'info':
84
+ return (
85
+ <InformationCircleIcon className="h-6 w-6 text-cc-Icon-Featured-icon-fg-Info" />
86
+ );
87
+ case 'success':
88
+ return (
89
+ <CheckCircleIcon className="h-6 w-6 text-cc-Icon-Featured-icon-fg-Success" />
90
+ );
91
+ case 'warning':
92
+ return (
93
+ <ExclamationCircleIcon className="h-6 w-6 text-cc-Icon-Featured-icon-fg-Warning" />
94
+ );
95
+ case 'error':
96
+ return (
97
+ <XCircleIcon className="h-6 w-6 text-cc-Icon-Featured-icon-fg-Error" />
98
+ );
99
+ }
100
+ };
101
+
102
+ const getBackgroundColor = (variant: ToastVariant) => {
103
+ switch (variant) {
104
+ case 'info':
105
+ return 'bg-Colors-Background-Info-Default';
106
+ case 'success':
107
+ return 'bg-Colors-Background-Success-Default';
108
+ case 'warning':
109
+ return 'bg-Colors-Background-Warning-Default';
110
+ case 'error':
111
+ return 'bg-Colors-Background-Critical-Default';
112
+ }
113
+ };
114
+
115
+ return (
116
+ <Toast key={id} {...toast} position={toastPosition}>
117
+ <div className="flex flex-col gap-3">
118
+ <div className="flex items-start gap-3">
119
+ <div
120
+ className={cn(
121
+ 'flex h-10 w-10 flex-shrink-0 flex-grow-0 items-center justify-center rounded-full',
122
+ getBackgroundColor(variant)
123
+ )}
124
+ >
125
+ {renderIcon(variant)}
126
+ </div>
127
+ <div className="grid gap-1">
128
+ {title && (
129
+ <ToastTitle className="text-base text-Colors-Text-Default">
130
+ {title}
131
+ </ToastTitle>
132
+ )}
133
+ {description && (
134
+ <ToastDescription className="text-sm text-Colors-Text-Subtle">
135
+ {description}
136
+ </ToastDescription>
137
+ )}
138
+ {action}
139
+ </div>
140
+ </div>
141
+ </div>
142
+ <ToastClose />
143
+ </Toast>
144
+ );
145
+ };
146
+
147
+ return (
148
+ <ToastProvider>
149
+ {/* 为每个位置渲染一个ToastViewport */}
150
+ {Object.entries(positionGroups).map(([pos, toastsForPosition]) => (
151
+ <React.Fragment key={pos}>
152
+ {toastsForPosition.map(renderToast)}
153
+ {toastsForPosition.length > 0 && (
154
+ <ToastViewport position={pos as any} />
155
+ )}
156
+ </React.Fragment>
157
+ ))}
158
+ </ToastProvider>
159
+ );
160
+ }
@@ -0,0 +1,248 @@
1
+ 'use client';
2
+
3
+ // Inspired by react-hot-toast library
4
+ import * as React from 'react';
5
+ import { ToastActionElement, ToastProps } from './toast';
6
+ import { Text } from '../typography';
7
+ import Link from '../link';
8
+ const TOAST_LIMIT = 8;
9
+ const TOAST_REMOVE_DELAY = 1000;
10
+
11
+ type ToasterToast = ToastProps & {
12
+ id: string;
13
+ title?: React.ReactNode;
14
+ description?: React.ReactNode;
15
+ action?: ToastActionElement;
16
+ };
17
+
18
+ const actionTypes = {
19
+ ADD_TOAST: 'ADD_TOAST',
20
+ UPDATE_TOAST: 'UPDATE_TOAST',
21
+ DISMISS_TOAST: 'DISMISS_TOAST',
22
+ REMOVE_TOAST: 'REMOVE_TOAST',
23
+ } as const;
24
+
25
+ let count = 0;
26
+
27
+ function genId() {
28
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
29
+
30
+ return count.toString();
31
+ }
32
+
33
+ type ActionType = typeof actionTypes;
34
+
35
+ type Action =
36
+ | {
37
+ type: ActionType['ADD_TOAST'];
38
+ toast: ToasterToast;
39
+ }
40
+ | {
41
+ type: ActionType['UPDATE_TOAST'];
42
+ toast: Partial<ToasterToast>;
43
+ }
44
+ | {
45
+ type: ActionType['DISMISS_TOAST'];
46
+ toastId?: ToasterToast['id'];
47
+ }
48
+ | {
49
+ type: ActionType['REMOVE_TOAST'];
50
+ toastId?: ToasterToast['id'];
51
+ };
52
+
53
+ interface State {
54
+ toasts: ToasterToast[];
55
+ }
56
+
57
+ const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
58
+
59
+ const addToRemoveQueue = (toastId: string) => {
60
+ if (toastTimeouts.has(toastId)) {
61
+ return;
62
+ }
63
+
64
+ const timeout = setTimeout(() => {
65
+ toastTimeouts.delete(toastId);
66
+ dispatch({
67
+ type: 'REMOVE_TOAST',
68
+ toastId,
69
+ });
70
+ }, TOAST_REMOVE_DELAY);
71
+
72
+ toastTimeouts.set(toastId, timeout);
73
+ };
74
+
75
+ export const reducer = (state: State, action: Action): State => {
76
+ switch (action.type) {
77
+ case 'ADD_TOAST':
78
+ return {
79
+ ...state,
80
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
81
+ };
82
+
83
+ case 'UPDATE_TOAST':
84
+ return {
85
+ ...state,
86
+ toasts: state.toasts.map((t) =>
87
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
88
+ ),
89
+ };
90
+
91
+ case 'DISMISS_TOAST': {
92
+ const { toastId } = action;
93
+
94
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
95
+ // but I'll keep it here for simplicity
96
+ if (toastId) {
97
+ addToRemoveQueue(toastId);
98
+ } else {
99
+ state.toasts.forEach((toast) => {
100
+ addToRemoveQueue(toast.id);
101
+ });
102
+ }
103
+
104
+ return {
105
+ ...state,
106
+ toasts: state.toasts.map((t) =>
107
+ t.id === toastId || toastId === undefined
108
+ ? {
109
+ ...t,
110
+ open: false,
111
+ }
112
+ : t
113
+ ),
114
+ };
115
+ }
116
+ case 'REMOVE_TOAST':
117
+ if (action.toastId === undefined) {
118
+ return {
119
+ ...state,
120
+ toasts: [],
121
+ };
122
+ }
123
+
124
+ return {
125
+ ...state,
126
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
127
+ };
128
+ }
129
+ };
130
+
131
+ const listeners: Array<(state: State) => void> = [];
132
+
133
+ let memoryState: State = { toasts: [] };
134
+
135
+ function dispatch(action: Action) {
136
+ memoryState = reducer(memoryState, action);
137
+ listeners.forEach((listener) => {
138
+ listener(memoryState);
139
+ });
140
+ }
141
+
142
+ type Toast = Omit<ToasterToast, 'id'> & {
143
+ actions?: {
144
+ dismissText?: string;
145
+ view?: string;
146
+ viewUrl?: string;
147
+ };
148
+ toastId?: string;
149
+ position?:
150
+ | 'top-left'
151
+ | 'top-center'
152
+ | 'top-right'
153
+ | 'bottom-left'
154
+ | 'bottom-center'
155
+ | 'bottom-right'
156
+ | 'left-center'
157
+ | 'right-center';
158
+ };
159
+
160
+ function toast({ actions, toastId, position, ...props }: Toast) {
161
+ const id = toastId || genId();
162
+
163
+ const update = (props: ToasterToast) =>
164
+ dispatch({
165
+ type: 'UPDATE_TOAST',
166
+ toast: { ...props, id },
167
+ });
168
+ const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
169
+
170
+ dispatch({
171
+ type: 'ADD_TOAST',
172
+ toast: {
173
+ ...props,
174
+ position,
175
+ ...(actions && {
176
+ action: (
177
+ <div className="flex justify-start items-center gap-2">
178
+ {actions?.dismissText && (
179
+ <Text
180
+ size="sm"
181
+ weight="medium"
182
+ color="brand"
183
+ className="cursor-pointer"
184
+ onClick={() => {
185
+ dismiss();
186
+ }}
187
+ >
188
+ {actions?.dismissText}
189
+ </Text>
190
+ )}
191
+ {actions?.view && actions.viewUrl && (
192
+ <Link
193
+ href={actions.viewUrl}
194
+ target="_blank"
195
+ rel="noreferrer noopener"
196
+ >
197
+ <div className="flex items-center gap-1.5">
198
+ <Text
199
+ size="sm"
200
+ weight="medium"
201
+ color="default"
202
+ className="cursor-pointer"
203
+ >
204
+ {actions.view}
205
+ </Text>
206
+ </div>
207
+ </Link>
208
+ )}
209
+ </div>
210
+ ),
211
+ }),
212
+ id,
213
+ open: true,
214
+ onOpenChange: (open) => {
215
+ if (!open) dismiss();
216
+ },
217
+ },
218
+ });
219
+
220
+ return {
221
+ id,
222
+ dismiss,
223
+ update,
224
+ };
225
+ }
226
+
227
+ function useToast() {
228
+ const [state, setState] = React.useState<State>(memoryState);
229
+
230
+ React.useEffect(() => {
231
+ listeners.push(setState);
232
+
233
+ return () => {
234
+ const index = listeners.indexOf(setState);
235
+ if (index > -1) {
236
+ listeners.splice(index, 1);
237
+ }
238
+ };
239
+ }, [state]);
240
+
241
+ return {
242
+ ...state,
243
+ toast,
244
+ dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
245
+ };
246
+ }
247
+
248
+ export { toast, useToast };