@xyhp915/slack-base-ui 0.0.1 → 0.0.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 (42) hide show
  1. package/package.json +1 -1
  2. package/src/App.css +7 -0
  3. package/src/App.tsx +18 -0
  4. package/src/assets/react.svg +1 -0
  5. package/src/components/AlertDialog.tsx +185 -0
  6. package/src/components/AutoComplete.tsx +311 -0
  7. package/src/components/Avatar.tsx +70 -0
  8. package/src/components/Badge.tsx +48 -0
  9. package/src/components/Button.tsx +53 -0
  10. package/src/components/Checkbox.tsx +109 -0
  11. package/src/components/ContextMenu.tsx +393 -0
  12. package/src/components/Dialog.tsx +129 -0
  13. package/src/components/Form.tsx +409 -0
  14. package/src/components/IconButton.tsx +49 -0
  15. package/src/components/Input.tsx +56 -0
  16. package/src/components/Loading.tsx +123 -0
  17. package/src/components/Menu.tsx +368 -0
  18. package/src/components/Popover.tsx +200 -0
  19. package/src/components/Progress.tsx +89 -0
  20. package/src/components/Radio.tsx +137 -0
  21. package/src/components/Select.tsx +177 -0
  22. package/src/components/Switch.tsx +116 -0
  23. package/src/components/Tabs.tsx +128 -0
  24. package/src/components/Toast.tsx +149 -0
  25. package/src/components/Tooltip.tsx +46 -0
  26. package/src/components/index.ts +165 -0
  27. package/src/context/ThemeContext.tsx +53 -0
  28. package/src/context/useTheme.ts +11 -0
  29. package/src/examples/slack-clone/SlackApp.tsx +94 -0
  30. package/src/examples/slack-clone/components/ChannelHeader.tsx +34 -0
  31. package/src/examples/slack-clone/components/Composer.tsx +42 -0
  32. package/src/examples/slack-clone/components/Message.tsx +97 -0
  33. package/src/examples/slack-clone/components/UserProfile.tsx +78 -0
  34. package/src/examples/slack-clone/layout/Layout.tsx +27 -0
  35. package/src/examples/slack-clone/layout/Sidebar.tsx +67 -0
  36. package/src/examples/slack-clone/layout/SidebarItem.tsx +57 -0
  37. package/src/examples/slack-clone/layout/TopBar.tsx +30 -0
  38. package/src/index.css +240 -0
  39. package/src/main.tsx +16 -0
  40. package/src/pages/ComponentShowcase.tsx +1618 -0
  41. package/src/pages/Dashboard.tsx +87 -0
  42. package/src/pages/QuickStartDemo.tsx +262 -0
@@ -0,0 +1,368 @@
1
+ import React from 'react'
2
+ import { Menu as BaseMenu } from '@base-ui/react'
3
+ import clsx from 'clsx'
4
+ import { Check, ChevronRight } from 'lucide-react'
5
+
6
+ export interface MenuProps {
7
+ children: React.ReactNode
8
+ open?: boolean
9
+ defaultOpen?: boolean
10
+ onOpenChange?: (open: boolean) => void
11
+ }
12
+
13
+ export interface MenuTriggerProps {
14
+ children?: React.ReactNode
15
+ className?: string
16
+ render?: React.ReactElement
17
+ }
18
+
19
+ export interface MenuContentProps {
20
+ children: React.ReactNode
21
+ className?: string
22
+ side?: 'top' | 'right' | 'bottom' | 'left'
23
+ align?: 'start' | 'center' | 'end'
24
+ sideOffset?: number
25
+ alignOffset?: number
26
+ }
27
+
28
+ export interface MenuItemProps {
29
+ children: React.ReactNode
30
+ className?: string
31
+ disabled?: boolean
32
+ onSelect?: () => void
33
+ destructive?: boolean
34
+ }
35
+
36
+ export interface MenuCheckboxItemProps {
37
+ children: React.ReactNode
38
+ className?: string
39
+ checked?: boolean
40
+ onCheckedChange?: (checked: boolean) => void
41
+ disabled?: boolean
42
+ }
43
+
44
+ export interface MenuRadioGroupProps {
45
+ children: React.ReactNode
46
+ value?: string
47
+ onValueChange?: (value: string) => void
48
+ }
49
+
50
+ export interface MenuRadioItemProps {
51
+ children: React.ReactNode
52
+ value: string
53
+ className?: string
54
+ disabled?: boolean
55
+ }
56
+
57
+ export interface MenuLabelProps {
58
+ children: React.ReactNode
59
+ className?: string
60
+ }
61
+
62
+ export interface MenuSeparatorProps {
63
+ className?: string
64
+ }
65
+
66
+ export interface MenuSubProps {
67
+ children: React.ReactNode
68
+ open?: boolean
69
+ defaultOpen?: boolean
70
+ onOpenChange?: (open: boolean) => void
71
+ }
72
+
73
+ export interface MenuSubTriggerProps {
74
+ children: React.ReactNode
75
+ className?: string
76
+ disabled?: boolean
77
+ }
78
+
79
+ export interface MenuSubContentProps {
80
+ children: React.ReactNode
81
+ className?: string
82
+ }
83
+
84
+ export const Menu: React.FC<MenuProps> = ({
85
+ children,
86
+ open,
87
+ defaultOpen,
88
+ onOpenChange,
89
+ }) => {
90
+ return (
91
+ <BaseMenu.Root
92
+ open={open}
93
+ defaultOpen={defaultOpen}
94
+ onOpenChange={onOpenChange}
95
+ >
96
+ {children}
97
+ </BaseMenu.Root>
98
+ )
99
+ }
100
+
101
+ export const MenuTrigger = React.forwardRef<HTMLButtonElement, MenuTriggerProps>(
102
+ ({ children, className, render }, ref) => {
103
+ if (render) {
104
+ return (
105
+ <span className="contents">
106
+ <BaseMenu.Trigger
107
+ ref={ref}
108
+ className={clsx('outline-none', className)}
109
+ render={render}
110
+ />
111
+ </span>
112
+ )
113
+ }
114
+
115
+ return (
116
+ <span className="contents">
117
+ <BaseMenu.Trigger
118
+ ref={ref}
119
+ className={clsx('outline-none', className)}
120
+ >
121
+ {children}
122
+ </BaseMenu.Trigger>
123
+ </span>
124
+ )
125
+ }
126
+ )
127
+
128
+ MenuTrigger.displayName = 'MenuTrigger'
129
+
130
+ export const MenuContent = React.forwardRef<HTMLDivElement, MenuContentProps>(
131
+ (
132
+ {
133
+ children,
134
+ className,
135
+ side = 'bottom',
136
+ align = 'start',
137
+ sideOffset = 4,
138
+ alignOffset = 0,
139
+ },
140
+ ref
141
+ ) => {
142
+ return (
143
+ <BaseMenu.Portal>
144
+ <BaseMenu.Positioner
145
+ side={side}
146
+ align={align}
147
+ sideOffset={sideOffset}
148
+ alignOffset={alignOffset}
149
+ >
150
+ <BaseMenu.Popup
151
+ ref={ref}
152
+ className={clsx(
153
+ 'z-50 min-w-45 max-w-80 rounded-md border border-(--border-light) bg-(--bg-primary) shadow-lg',
154
+ 'py-1',
155
+ className
156
+ )}
157
+ >
158
+ {children}
159
+ <BaseMenu.Arrow className="fill-(--bg-primary) stroke-(--border-light)" />
160
+ </BaseMenu.Popup>
161
+ </BaseMenu.Positioner>
162
+ </BaseMenu.Portal>
163
+ )
164
+ }
165
+ )
166
+
167
+ MenuContent.displayName = 'MenuContent'
168
+
169
+ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
170
+ ({ children, className, disabled, onSelect, destructive }, ref) => {
171
+ return (
172
+ <BaseMenu.Item
173
+ ref={ref}
174
+ className={clsx(
175
+ 'relative flex items-center gap-2 px-3 py-1.5 text-[15px] outline-none cursor-pointer select-none',
176
+ 'text-(--text-primary) hover:bg-(--bg-hover)',
177
+ 'data-highlighted:bg-(--bg-hover)',
178
+ 'data-disabled:opacity-50 data-disabled:pointer-events-none',
179
+ destructive && 'text-(--danger) hover:bg-red-50 dark:hover:bg-red-950/20',
180
+ className
181
+ )}
182
+ disabled={disabled}
183
+ onSelect={onSelect}
184
+ >
185
+ {children}
186
+ </BaseMenu.Item>
187
+ )
188
+ }
189
+ )
190
+
191
+ MenuItem.displayName = 'MenuItem'
192
+
193
+ export const MenuCheckboxItem = React.forwardRef<
194
+ HTMLDivElement,
195
+ MenuCheckboxItemProps
196
+ >(({ children, className, checked, onCheckedChange, disabled }, ref) => {
197
+ return (
198
+ <BaseMenu.CheckboxItem
199
+ ref={ref}
200
+ className={clsx(
201
+ 'relative flex items-center gap-2 px-3 py-1.5 pl-8 text-[15px] outline-none cursor-pointer select-none',
202
+ 'text-(--text-primary) hover:bg-(--bg-hover)',
203
+ 'data-highlighted:bg-(--bg-hover)',
204
+ 'data-disabled:opacity-50 data-disabled:pointer-events-none',
205
+ className
206
+ )}
207
+ checked={checked}
208
+ onCheckedChange={onCheckedChange}
209
+ disabled={disabled}
210
+ >
211
+ <BaseMenu.CheckboxItemIndicator className="absolute left-2 flex items-center justify-center">
212
+ <Check className="w-4 h-4" />
213
+ </BaseMenu.CheckboxItemIndicator>
214
+ {children}
215
+ </BaseMenu.CheckboxItem>
216
+ )
217
+ })
218
+
219
+ MenuCheckboxItem.displayName = 'MenuCheckboxItem'
220
+
221
+ export const MenuRadioGroup: React.FC<MenuRadioGroupProps> = ({
222
+ children,
223
+ value,
224
+ onValueChange,
225
+ }) => {
226
+ return (
227
+ <BaseMenu.RadioGroup value={value} onValueChange={onValueChange}>
228
+ {children}
229
+ </BaseMenu.RadioGroup>
230
+ )
231
+ }
232
+
233
+ export const MenuRadioItem = React.forwardRef<HTMLDivElement, MenuRadioItemProps>(
234
+ ({ children, value, className, disabled }, ref) => {
235
+ return (
236
+ <BaseMenu.RadioItem
237
+ ref={ref}
238
+ value={value}
239
+ className={clsx(
240
+ 'relative flex items-center gap-2 px-3 py-1.5 pl-8 text-[15px] outline-none cursor-pointer select-none',
241
+ 'text-(--text-primary) hover:bg-(--bg-hover)',
242
+ 'data-highlighted:bg-(--bg-hover)',
243
+ 'data-disabled:opacity-50 data-disabled:pointer-events-none',
244
+ className
245
+ )}
246
+ disabled={disabled}
247
+ >
248
+ <BaseMenu.RadioItemIndicator className="absolute left-2 flex items-center justify-center">
249
+ <Check className="w-4 h-4" />
250
+ </BaseMenu.RadioItemIndicator>
251
+ {children}
252
+ </BaseMenu.RadioItem>
253
+ )
254
+ }
255
+ )
256
+
257
+ MenuRadioItem.displayName = 'MenuRadioItem'
258
+
259
+ export const MenuLabel: React.FC<MenuLabelProps> = ({ children, className }) => {
260
+ return (
261
+ <div
262
+ className={clsx(
263
+ 'px-3 py-2 text-xs font-semibold text-(--text-muted) uppercase tracking-wider',
264
+ className
265
+ )}
266
+ >
267
+ {children}
268
+ </div>
269
+ )
270
+ }
271
+
272
+ export const MenuSeparator: React.FC<MenuSeparatorProps> = ({ className }) => {
273
+ return (
274
+ <BaseMenu.Separator
275
+ className={clsx('my-1 h-px bg-(--border-light)', className)}
276
+ />
277
+ )
278
+ }
279
+
280
+ export const MenuSub: React.FC<MenuSubProps> = ({
281
+ children,
282
+ open,
283
+ defaultOpen,
284
+ onOpenChange,
285
+ }) => {
286
+ return (
287
+ <BaseMenu.Root
288
+ open={open}
289
+ defaultOpen={defaultOpen}
290
+ onOpenChange={onOpenChange}
291
+ >
292
+ {children}
293
+ </BaseMenu.Root>
294
+ )
295
+ }
296
+
297
+ export const MenuSubTrigger = React.forwardRef<HTMLButtonElement, MenuSubTriggerProps>(
298
+ ({ children, className, disabled }, ref) => {
299
+ return (
300
+ <BaseMenu.Trigger
301
+ ref={ref}
302
+ className={clsx(
303
+ 'relative flex items-center justify-between gap-2 px-3 py-2 text-[15px] outline-none cursor-pointer select-none',
304
+ 'text-(--text-primary) hover:bg-(--bg-hover)',
305
+ 'data-highlighted:bg-(--bg-hover)',
306
+ 'data-disabled:opacity-50 data-disabled:pointer-events-none w-full',
307
+ className
308
+ )}
309
+ disabled={disabled}
310
+ >
311
+ {children}
312
+ <ChevronRight className="w-4 h-4 ml-auto" />
313
+ </BaseMenu.Trigger>
314
+ )
315
+ }
316
+ )
317
+
318
+ MenuSubTrigger.displayName = 'MenuSubTrigger'
319
+
320
+ export const MenuSubContent = React.forwardRef<HTMLDivElement, MenuSubContentProps>(
321
+ ({ children, className }, ref) => {
322
+ return (
323
+ <BaseMenu.Portal>
324
+ <BaseMenu.Positioner side="right" align="start" sideOffset={8}>
325
+ <BaseMenu.Popup
326
+ ref={ref}
327
+ className={clsx(
328
+ 'z-50 min-w-45 max-w-80 rounded-md border border-(--border-light) bg-(--bg-primary) shadow-lg',
329
+ 'py-1',
330
+ className
331
+ )}
332
+ >
333
+ {children}
334
+ </BaseMenu.Popup>
335
+ </BaseMenu.Positioner>
336
+ </BaseMenu.Portal>
337
+ )
338
+ }
339
+ )
340
+
341
+ MenuSubContent.displayName = 'MenuSubContent'
342
+
343
+ // Convenience component for menu items with icons
344
+ export const MenuItemWithIcon: React.FC<{
345
+ icon?: React.ReactNode
346
+ children: React.ReactNode
347
+ shortcut?: string
348
+ className?: string
349
+ disabled?: boolean
350
+ onSelect?: () => void
351
+ destructive?: boolean
352
+ }> = ({ icon, children, shortcut, className, disabled, onSelect, destructive }) => {
353
+ return (
354
+ <MenuItem
355
+ className={className}
356
+ disabled={disabled}
357
+ onSelect={onSelect}
358
+ destructive={destructive}
359
+ >
360
+ {icon && <span className="shrink-0">{icon}</span>}
361
+ <span className="flex-1">{children}</span>
362
+ {shortcut && (
363
+ <span className="ml-auto text-xs text-(--text-muted)">{shortcut}</span>
364
+ )}
365
+ </MenuItem>
366
+ )
367
+ }
368
+
@@ -0,0 +1,200 @@
1
+ import React from 'react'
2
+ import { Popover as BasePopover } from '@base-ui/react'
3
+ import clsx from 'clsx'
4
+
5
+ export interface PopoverProps {
6
+ children: React.ReactNode
7
+ open?: boolean
8
+ defaultOpen?: boolean
9
+ onOpenChange?: (open: boolean) => void
10
+ modal?: boolean
11
+ }
12
+
13
+ export interface PopoverTriggerProps {
14
+ children?: React.ReactNode
15
+ className?: string
16
+ render?: React.ReactElement
17
+ }
18
+
19
+ export interface PopoverContentProps {
20
+ children: React.ReactNode
21
+ className?: string
22
+ side?: 'top' | 'right' | 'bottom' | 'left'
23
+ align?: 'start' | 'center' | 'end'
24
+ sideOffset?: number
25
+ alignOffset?: number
26
+ }
27
+
28
+ export interface PopoverCloseProps {
29
+ children?: React.ReactNode
30
+ className?: string
31
+ render?: React.ReactElement
32
+ }
33
+
34
+ export const Popover: React.FC<PopoverProps> = ({
35
+ children,
36
+ open,
37
+ defaultOpen,
38
+ onOpenChange,
39
+ modal = false,
40
+ }) => {
41
+ return (
42
+ <BasePopover.Root
43
+ open={open}
44
+ defaultOpen={defaultOpen}
45
+ onOpenChange={onOpenChange}
46
+ modal={modal}
47
+ >
48
+ {children}
49
+ </BasePopover.Root>
50
+ )
51
+ }
52
+
53
+ export const PopoverTrigger = React.forwardRef<
54
+ HTMLButtonElement,
55
+ PopoverTriggerProps
56
+ >(({ children, className, render }, ref) => {
57
+ // Wrap with `contents` span to isolate the hidden accessibility <span> nodes
58
+ // that @base-ui/react injects as siblings of the trigger. Without this wrapper,
59
+ // those fixed-position spans become siblings in the parent container and break
60
+ // Tailwind's `space-y-*` selectors (`:not(:last-child)`) causing layout shifts.
61
+ if (render) {
62
+ return (
63
+ <span className="contents">
64
+ <BasePopover.Trigger
65
+ ref={ref}
66
+ className={clsx('outline-none', className)}
67
+ render={render}
68
+ />
69
+ </span>
70
+ )
71
+ }
72
+
73
+ return (
74
+ <span className="contents">
75
+ <BasePopover.Trigger
76
+ ref={ref}
77
+ className={clsx('outline-none', className)}
78
+ >
79
+ {children}
80
+ </BasePopover.Trigger>
81
+ </span>
82
+ )
83
+ })
84
+
85
+ PopoverTrigger.displayName = 'PopoverTrigger'
86
+
87
+ export const PopoverContent: React.FC<PopoverContentProps> = ({
88
+ children,
89
+ className,
90
+ side = 'bottom',
91
+ align = 'center',
92
+ sideOffset = 8,
93
+ alignOffset = 0,
94
+ }) => {
95
+ return (
96
+ <BasePopover.Portal>
97
+ <BasePopover.Positioner
98
+ side={side}
99
+ align={align}
100
+ sideOffset={sideOffset}
101
+ alignOffset={alignOffset}
102
+ >
103
+ <BasePopover.Popup
104
+ className={clsx(
105
+ 'z-50 min-w-50 max-w-90 rounded-lg border border-(--border-light) bg-(--bg-primary) shadow-lg',
106
+ className
107
+ )}
108
+ >
109
+ {children}
110
+ <BasePopover.Arrow className="fill-(--bg-primary) stroke-(--border-light)" />
111
+ </BasePopover.Popup>
112
+ </BasePopover.Positioner>
113
+ </BasePopover.Portal>
114
+ )
115
+ }
116
+
117
+ PopoverContent.displayName = 'PopoverContent'
118
+
119
+ export const PopoverClose = React.forwardRef<
120
+ HTMLButtonElement,
121
+ PopoverCloseProps
122
+ >(({ children, className, render }, ref) => {
123
+ if (render) {
124
+ return (
125
+ <BasePopover.Close
126
+ ref={ref}
127
+ className={clsx('outline-none', className)}
128
+ render={render}
129
+ />
130
+ )
131
+ }
132
+
133
+ return (
134
+ <BasePopover.Close
135
+ ref={ref}
136
+ className={clsx('outline-none', className)}
137
+ >
138
+ {children}
139
+ </BasePopover.Close>
140
+ )
141
+ })
142
+
143
+ PopoverClose.displayName = 'PopoverClose'
144
+
145
+ // Convenience components for common Popover content patterns
146
+ export const PopoverHeader: React.FC<{ children: React.ReactNode; className?: string }> = ({
147
+ children,
148
+ className,
149
+ }) => {
150
+ return (
151
+ <div
152
+ className={clsx(
153
+ 'px-4 py-3 border-b border-(--border-light)',
154
+ 'font-bold text-[15px] text-(--text-primary)',
155
+ className
156
+ )}
157
+ >
158
+ {children}
159
+ </div>
160
+ )
161
+ }
162
+
163
+ export const PopoverBody: React.FC<{ children: React.ReactNode; className?: string }> = ({
164
+ children,
165
+ className,
166
+ }) => {
167
+ return (
168
+ <div
169
+ className={clsx(
170
+ 'px-4 py-3',
171
+ 'text-[15px] text-(--text-primary)',
172
+ className
173
+ )}
174
+ >
175
+ {children}
176
+ </div>
177
+ )
178
+ }
179
+
180
+ export const PopoverFooter: React.FC<{ children: React.ReactNode; className?: string }> = ({
181
+ children,
182
+ className,
183
+ }) => {
184
+ return (
185
+ <div
186
+ className={clsx(
187
+ 'px-4 py-3 border-t border-(--border-light)',
188
+ 'flex items-center justify-end gap-2',
189
+ className
190
+ )}
191
+ >
192
+ {children}
193
+ </div>
194
+ )
195
+ }
196
+
197
+
198
+
199
+
200
+
@@ -0,0 +1,89 @@
1
+ import React from 'react'
2
+ import { Progress as BaseProgress } from '@base-ui/react'
3
+ import clsx from 'clsx'
4
+
5
+ export type ProgressVariant = 'default' | 'success' | 'warning' | 'danger'
6
+ export type ProgressSize = 'sm' | 'md' | 'lg'
7
+
8
+ export interface ProgressProps {
9
+ /** Current value (0–max). Omit for indeterminate state. */
10
+ value?: number
11
+ /** Maximum value. Defaults to 100. */
12
+ max?: number
13
+ variant?: ProgressVariant
14
+ size?: ProgressSize
15
+ /** Label shown above the bar */
16
+ label?: string
17
+ /** Show percentage text on the right */
18
+ showValue?: boolean
19
+ className?: string
20
+ }
21
+
22
+ const variantColor: Record<ProgressVariant, string> = {
23
+ default: 'bg-(--accent)',
24
+ success: 'bg-(--slack-green)',
25
+ warning: 'bg-amber-400',
26
+ danger: 'bg-(--danger)',
27
+ }
28
+
29
+ const trackSizes: Record<ProgressSize, string> = {
30
+ sm: 'h-1',
31
+ md: 'h-2',
32
+ lg: 'h-3',
33
+ }
34
+
35
+ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
36
+ (
37
+ {
38
+ value,
39
+ max = 100,
40
+ variant = 'default',
41
+ size = 'md',
42
+ label,
43
+ showValue,
44
+ className,
45
+ },
46
+ ref,
47
+ ) => {
48
+ return (
49
+ <div className={clsx('flex flex-col gap-1.5', className)}>
50
+ {(label || showValue) && (
51
+ <div className="flex items-center justify-between gap-2">
52
+ {label && (
53
+ <span className="text-[13px] font-medium text-(--text-secondary)">{label}</span>
54
+ )}
55
+ {showValue && value !== undefined && (
56
+ <span className="text-[12px] text-(--text-muted)">
57
+ {Math.round((value / max) * 100)}%
58
+ </span>
59
+ )}
60
+ </div>
61
+ )}
62
+
63
+ <BaseProgress.Root
64
+ ref={ref}
65
+ value={value ?? null}
66
+ max={max}
67
+ className={clsx(
68
+ 'w-full overflow-hidden rounded-full bg-(--bg-hover)',
69
+ trackSizes[size],
70
+ )}
71
+ >
72
+ <BaseProgress.Track className="relative h-full w-full">
73
+ <BaseProgress.Indicator
74
+ className={clsx(
75
+ 'h-full rounded-full transition-[width] duration-300 ease-out',
76
+ variantColor[variant],
77
+ // Indeterminate animation when value is null
78
+ value === undefined &&
79
+ 'w-1/3 animate-[progress-indeterminate_1.5s_ease-in-out_infinite]',
80
+ )}
81
+ />
82
+ </BaseProgress.Track>
83
+ </BaseProgress.Root>
84
+ </div>
85
+ )
86
+ },
87
+ )
88
+
89
+ Progress.displayName = 'Progress'