@xyhp915/slack-base-ui 0.0.1 → 0.0.3

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 (54) hide show
  1. package/README.md +220 -4
  2. package/agents/slack-base-ui/SKILL.md +137 -0
  3. package/agents/slack-base-ui/checklists/style-review.md +56 -0
  4. package/agents/slack-base-ui/templates/consumer-setup.md +109 -0
  5. package/agents/slack-base-ui/templates/slack-theme.css +152 -0
  6. package/libs/Dialog.d.ts +73 -0
  7. package/libs/Dialog.d.ts.map +1 -1
  8. package/libs/Popover.d.ts +69 -0
  9. package/libs/Popover.d.ts.map +1 -1
  10. package/libs/index.d.ts +4 -4
  11. package/libs/index.d.ts.map +1 -1
  12. package/libs/index.js +2885 -2718
  13. package/package.json +1 -1
  14. package/src/App.css +7 -0
  15. package/src/App.tsx +18 -0
  16. package/src/assets/react.svg +1 -0
  17. package/src/components/AlertDialog.tsx +185 -0
  18. package/src/components/AutoComplete.tsx +311 -0
  19. package/src/components/Avatar.tsx +70 -0
  20. package/src/components/Badge.tsx +48 -0
  21. package/src/components/Button.tsx +53 -0
  22. package/src/components/Checkbox.tsx +109 -0
  23. package/src/components/ContextMenu.tsx +393 -0
  24. package/src/components/Dialog.tsx +371 -0
  25. package/src/components/Form.tsx +409 -0
  26. package/src/components/IconButton.tsx +49 -0
  27. package/src/components/Input.tsx +56 -0
  28. package/src/components/Loading.tsx +123 -0
  29. package/src/components/Menu.tsx +368 -0
  30. package/src/components/Popover.tsx +367 -0
  31. package/src/components/Progress.tsx +89 -0
  32. package/src/components/Radio.tsx +137 -0
  33. package/src/components/Select.tsx +177 -0
  34. package/src/components/Switch.tsx +116 -0
  35. package/src/components/Tabs.tsx +128 -0
  36. package/src/components/Toast.tsx +149 -0
  37. package/src/components/Tooltip.tsx +46 -0
  38. package/src/components/index.ts +186 -0
  39. package/src/context/ThemeContext.tsx +53 -0
  40. package/src/context/useTheme.ts +11 -0
  41. package/src/examples/slack-clone/SlackApp.tsx +94 -0
  42. package/src/examples/slack-clone/components/ChannelHeader.tsx +34 -0
  43. package/src/examples/slack-clone/components/Composer.tsx +42 -0
  44. package/src/examples/slack-clone/components/Message.tsx +97 -0
  45. package/src/examples/slack-clone/components/UserProfile.tsx +78 -0
  46. package/src/examples/slack-clone/layout/Layout.tsx +27 -0
  47. package/src/examples/slack-clone/layout/Sidebar.tsx +67 -0
  48. package/src/examples/slack-clone/layout/SidebarItem.tsx +57 -0
  49. package/src/examples/slack-clone/layout/TopBar.tsx +30 -0
  50. package/src/index.css +240 -0
  51. package/src/main.tsx +22 -0
  52. package/src/pages/ComponentShowcase.tsx +1964 -0
  53. package/src/pages/Dashboard.tsx +87 -0
  54. package/src/pages/QuickStartDemo.tsx +262 -0
@@ -0,0 +1,53 @@
1
+ import clsx from 'clsx'
2
+ import { Button as BaseButton } from '@base-ui/react'
3
+ import type { ButtonProps as BaseButtonProps } from '@base-ui/react'
4
+ import React from 'react'
5
+
6
+ // Slack Button Variants
7
+ type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
8
+ type ButtonSize = 'sm' | 'md' | 'lg';
9
+
10
+ export interface ButtonProps extends BaseButtonProps {
11
+ variant?: ButtonVariant;
12
+ size?: ButtonSize;
13
+ fullWidth?: boolean;
14
+ }
15
+
16
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
17
+ ({ className, variant = 'secondary', size = 'md', fullWidth, children, ...props }, ref) => {
18
+
19
+ // Base styles tailored to match Slack
20
+ const baseStyles = 'inline-flex items-center justify-center font-bold transition-all outline-none focus-visible:ring-2 focus-visible:ring-(--focus-ring) focus-visible:ring-offset-2 focus-visible:ring-offset-(--bg-primary) disabled:opacity-50 disabled:cursor-not-allowed select-none'
21
+
22
+ const variants = {
23
+ primary: 'bg-(--accent-action) text-(--accent-contrast) hover:bg-(--accent-action-hover) border border-transparent shadow-sm active:scale-[0.98]',
24
+ secondary: 'bg-(--bg-secondary) text-(--text-primary) border border-(--border-active) hover:bg-(--bg-hover) hover:shadow-sm active:bg-(--bg-secondary)',
25
+ danger: 'bg-(--danger) text-(--accent-contrast) hover:bg-(--danger-hover) border border-transparent shadow-sm',
26
+ ghost: 'bg-transparent text-(--text-secondary) hover:bg-(--bg-hover) hover:text-(--text-primary)',
27
+ }
28
+
29
+ const sizes = {
30
+ sm: 'h-7 px-3 text-[13px] rounded',
31
+ md: 'h-9 px-4 text-[15px] rounded-md',
32
+ lg: 'h-11 px-6 text-[18px] rounded-lg',
33
+ }
34
+
35
+ return (
36
+ <BaseButton
37
+ ref={ref}
38
+ className={clsx(
39
+ baseStyles,
40
+ variants[variant],
41
+ sizes[size],
42
+ fullWidth && 'w-full',
43
+ className,
44
+ )}
45
+ {...props}
46
+ >
47
+ {children}
48
+ </BaseButton>
49
+ )
50
+ },
51
+ )
52
+
53
+ Button.displayName = 'Button'
@@ -0,0 +1,109 @@
1
+ import React from 'react'
2
+ import { Checkbox as BaseCheckbox } from '@base-ui/react'
3
+ import clsx from 'clsx'
4
+ import { Check, Minus } from 'lucide-react'
5
+
6
+ export interface CheckboxProps {
7
+ checked?: boolean
8
+ defaultChecked?: boolean
9
+ onCheckedChange?: (checked: boolean) => void
10
+ indeterminate?: boolean
11
+ disabled?: boolean
12
+ required?: boolean
13
+ /** Label text shown next to the checkbox */
14
+ label?: string
15
+ /** Helper text shown below the label */
16
+ description?: string
17
+ /** Error message */
18
+ error?: string
19
+ name?: string
20
+ value?: string
21
+ id?: string
22
+ className?: string
23
+ }
24
+
25
+ export const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
26
+ (
27
+ {
28
+ checked,
29
+ defaultChecked,
30
+ onCheckedChange,
31
+ indeterminate,
32
+ disabled,
33
+ required,
34
+ label,
35
+ description,
36
+ error,
37
+ name,
38
+ value,
39
+ id,
40
+ className,
41
+ },
42
+ ref,
43
+ ) => {
44
+ const generatedId = React.useId()
45
+ const checkboxId = id ?? generatedId
46
+
47
+ const rootEl = (
48
+ <BaseCheckbox.Root
49
+ ref={ref}
50
+ id={checkboxId}
51
+ checked={checked}
52
+ defaultChecked={defaultChecked}
53
+ onCheckedChange={onCheckedChange}
54
+ indeterminate={indeterminate}
55
+ disabled={disabled}
56
+ required={required}
57
+ name={name}
58
+ value={value}
59
+ className={clsx(
60
+ 'relative flex h-4 w-4 shrink-0 items-center justify-center rounded',
61
+ 'border-2 border-(--border-gray) bg-(--bg-primary)',
62
+ 'transition-[background-color,border-color] outline-none',
63
+ 'focus-visible:ring-2 focus-visible:ring-(--focus-ring) focus-visible:ring-offset-1',
64
+ 'data-[checked]:border-(--accent) data-[checked]:bg-(--accent)',
65
+ 'data-[indeterminate]:border-(--accent) data-[indeterminate]:bg-(--accent)',
66
+ 'disabled:cursor-not-allowed disabled:opacity-50',
67
+ error && 'border-(--danger)',
68
+ className,
69
+ )}
70
+ >
71
+ <BaseCheckbox.Indicator className="flex items-center justify-center text-white">
72
+ {indeterminate ? <Minus size={10} strokeWidth={3} /> : <Check size={10} strokeWidth={3} />}
73
+ </BaseCheckbox.Indicator>
74
+ </BaseCheckbox.Root>
75
+ )
76
+
77
+ if (!label) return rootEl
78
+
79
+ return (
80
+ <div className="flex flex-col gap-1">
81
+ <div className="flex items-start gap-2">
82
+ {rootEl}
83
+ <div className="flex flex-col">
84
+ <label
85
+ htmlFor={checkboxId}
86
+ className={clsx(
87
+ 'text-[14px] leading-none text-(--text-primary) select-none',
88
+ disabled && 'opacity-50 cursor-not-allowed',
89
+ )}
90
+ >
91
+ {label}
92
+ {required && <span className="ml-0.5 text-(--danger)">*</span>}
93
+ </label>
94
+ {description && (
95
+ <span className="mt-0.5 text-[12px] text-(--text-muted)">{description}</span>
96
+ )}
97
+ </div>
98
+ </div>
99
+ {error && (
100
+ <span className="flex items-center gap-1 text-[12px] font-medium leading-tight text-(--danger)">
101
+ ⚠️ {error}
102
+ </span>
103
+ )}
104
+ </div>
105
+ )
106
+ },
107
+ )
108
+
109
+ Checkbox.displayName = 'Checkbox'
@@ -0,0 +1,393 @@
1
+ import React from 'react'
2
+ import { Menu as BaseMenu } from '@base-ui/react'
3
+ import clsx from 'clsx'
4
+ import { ChevronRight } from 'lucide-react'
5
+ import { MenuTrigger, MenuItem, MenuCheckboxItem, MenuRadioItem } from './Menu'
6
+
7
+ export interface ContextMenuProps {
8
+ children: React.ReactNode
9
+ open?: boolean
10
+ defaultOpen?: boolean
11
+ onOpenChange?: (open: boolean) => void
12
+ }
13
+
14
+ export interface ContextMenuTriggerProps {
15
+ children: React.ReactNode
16
+ className?: string
17
+ disabled?: boolean
18
+ }
19
+
20
+ export interface ContextMenuContentProps {
21
+ children: React.ReactNode
22
+ className?: string
23
+ }
24
+
25
+ export interface ContextMenuItemProps {
26
+ children: React.ReactNode
27
+ className?: string
28
+ disabled?: boolean
29
+ onSelect?: () => void
30
+ destructive?: boolean
31
+ }
32
+
33
+ export interface ContextMenuCheckboxItemProps {
34
+ children: React.ReactNode
35
+ className?: string
36
+ checked?: boolean
37
+ onCheckedChange?: (checked: boolean) => void
38
+ disabled?: boolean
39
+ }
40
+
41
+ export interface ContextMenuRadioGroupProps {
42
+ children: React.ReactNode
43
+ value?: string
44
+ onValueChange?: (value: string) => void
45
+ }
46
+
47
+ export interface ContextMenuRadioItemProps {
48
+ children: React.ReactNode
49
+ value: string
50
+ className?: string
51
+ disabled?: boolean
52
+ }
53
+
54
+ export interface ContextMenuLabelProps {
55
+ children: React.ReactNode
56
+ className?: string
57
+ }
58
+
59
+ export interface ContextMenuSeparatorProps {
60
+ className?: string
61
+ }
62
+
63
+ export interface ContextMenuSubProps {
64
+ children: React.ReactNode
65
+ open?: boolean
66
+ defaultOpen?: boolean
67
+ onOpenChange?: (open: boolean) => void
68
+ }
69
+
70
+ export interface ContextMenuSubTriggerProps {
71
+ children: React.ReactNode
72
+ className?: string
73
+ disabled?: boolean
74
+ }
75
+
76
+ export interface ContextMenuSubContentProps {
77
+ children: React.ReactNode
78
+ className?: string
79
+ }
80
+
81
+ // Context for sharing anchor position between components
82
+ const ContextMenuContext = React.createContext<{
83
+ anchorEl: VirtualElement | null
84
+ setAnchorEl: (el: VirtualElement | null) => void
85
+ } | null>(null)
86
+
87
+ interface VirtualElement {
88
+ getBoundingClientRect: () => DOMRect
89
+ }
90
+
91
+ export const ContextMenu: React.FC<ContextMenuProps> = ({
92
+ children,
93
+ open: controlledOpen,
94
+ defaultOpen,
95
+ onOpenChange,
96
+ }) => {
97
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false)
98
+ const [anchorEl, setAnchorEl] = React.useState<VirtualElement | null>(null)
99
+
100
+ const open = controlledOpen ?? uncontrolledOpen
101
+ const handleOpenChange = (newOpen: boolean) => {
102
+ setUncontrolledOpen(newOpen)
103
+ onOpenChange?.(newOpen)
104
+ }
105
+
106
+ return (
107
+ <ContextMenuContext.Provider value={{ anchorEl, setAnchorEl }}>
108
+ <BaseMenu.Root
109
+ open={open}
110
+ onOpenChange={handleOpenChange}
111
+ >
112
+ {children}
113
+ </BaseMenu.Root>
114
+ </ContextMenuContext.Provider>
115
+ )
116
+ }
117
+
118
+ export const ContextMenuTrigger = React.forwardRef<
119
+ HTMLDivElement,
120
+ ContextMenuTriggerProps
121
+ >(({ children, className, disabled }, ref) => {
122
+ const context = React.useContext(ContextMenuContext)
123
+ const triggerRef = React.useRef<HTMLButtonElement>(null)
124
+
125
+ const handleContextMenu = (e: React.MouseEvent) => {
126
+ e.preventDefault()
127
+ if (disabled) return
128
+
129
+ // Create a virtual element at the mouse position
130
+ const virtualEl: VirtualElement = {
131
+ getBoundingClientRect: () => ({
132
+ width: 0,
133
+ height: 0,
134
+ x: e.clientX,
135
+ y: e.clientY,
136
+ left: e.clientX,
137
+ right: e.clientX,
138
+ top: e.clientY,
139
+ bottom: e.clientY,
140
+ toJSON: () => ({}),
141
+ } as DOMRect),
142
+ }
143
+
144
+ context?.setAnchorEl(virtualEl)
145
+
146
+ // Programmatically trigger the menu
147
+ if (triggerRef.current) {
148
+ triggerRef.current.click()
149
+ }
150
+ }
151
+
152
+ return (
153
+ <div
154
+ ref={ref}
155
+ className={clsx('outline-none focus:outline-none', className)}
156
+ onContextMenu={handleContextMenu}
157
+ >
158
+ {/* Hidden trigger button — must suppress all focus styles */}
159
+ <MenuTrigger
160
+ ref={triggerRef}
161
+ className="!absolute !w-0 !h-0 !p-0 !m-0 !border-0 !outline-none !shadow-none !opacity-0 overflow-hidden pointer-events-none"
162
+ render={
163
+ <button
164
+ disabled={disabled}
165
+ tabIndex={-1}
166
+ aria-hidden="true"
167
+ />
168
+ }
169
+ />
170
+ {children}
171
+ </div>
172
+ )
173
+ })
174
+
175
+ ContextMenuTrigger.displayName = 'ContextMenuTrigger'
176
+
177
+ export const ContextMenuContent = React.forwardRef<
178
+ HTMLDivElement,
179
+ ContextMenuContentProps
180
+ >(({ children, className }, ref) => {
181
+ const context = React.useContext(ContextMenuContext)
182
+
183
+ return (
184
+ <BaseMenu.Portal>
185
+ <BaseMenu.Positioner
186
+ anchor={context?.anchorEl || undefined}
187
+ side="bottom"
188
+ align="start"
189
+ sideOffset={4}
190
+ >
191
+ <BaseMenu.Popup
192
+ ref={ref}
193
+ className={clsx(
194
+ 'z-50 min-w-56 rounded-md border border-(--border-light) bg-(--bg-primary) shadow-lg',
195
+ 'py-1 outline-0',
196
+ className
197
+ )}
198
+ >
199
+ {children}
200
+ </BaseMenu.Popup>
201
+ </BaseMenu.Positioner>
202
+ </BaseMenu.Portal>
203
+
204
+ )
205
+ })
206
+
207
+ ContextMenuContent.displayName = 'ContextMenuContent'
208
+
209
+ export const ContextMenuItem = React.forwardRef<HTMLDivElement, ContextMenuItemProps>(
210
+ ({ children, className, disabled, onSelect, destructive }, ref) => {
211
+ return (
212
+ <MenuItem
213
+ ref={ref}
214
+ className={className}
215
+ disabled={disabled}
216
+ onSelect={onSelect}
217
+ destructive={destructive}
218
+ >
219
+ {children}
220
+ </MenuItem>
221
+ )
222
+ }
223
+ )
224
+
225
+ ContextMenuItem.displayName = 'ContextMenuItem'
226
+
227
+ export const ContextMenuCheckboxItem = React.forwardRef<
228
+ HTMLDivElement,
229
+ ContextMenuCheckboxItemProps
230
+ >(({ children, className, checked, onCheckedChange, disabled }, ref) => {
231
+ return (
232
+ <MenuCheckboxItem
233
+ ref={ref}
234
+ className={className}
235
+ checked={checked}
236
+ onCheckedChange={onCheckedChange}
237
+ disabled={disabled}
238
+ >
239
+ {children}
240
+ </MenuCheckboxItem>
241
+ )
242
+ })
243
+
244
+ ContextMenuCheckboxItem.displayName = 'ContextMenuCheckboxItem'
245
+
246
+ export const ContextMenuRadioGroup: React.FC<ContextMenuRadioGroupProps> = ({
247
+ children,
248
+ value,
249
+ onValueChange,
250
+ }) => {
251
+ return (
252
+ <BaseMenu.RadioGroup value={value} onValueChange={onValueChange}>
253
+ {children}
254
+ </BaseMenu.RadioGroup>
255
+ )
256
+ }
257
+
258
+ export const ContextMenuRadioItem = React.forwardRef<
259
+ HTMLDivElement,
260
+ ContextMenuRadioItemProps
261
+ >(({ children, value, className, disabled }, ref) => {
262
+ return (
263
+ <MenuRadioItem
264
+ ref={ref}
265
+ value={value}
266
+ className={className}
267
+ disabled={disabled}
268
+ >
269
+ {children}
270
+ </MenuRadioItem>
271
+ )
272
+ })
273
+
274
+ ContextMenuRadioItem.displayName = 'ContextMenuRadioItem'
275
+
276
+ export const ContextMenuLabel: React.FC<ContextMenuLabelProps> = ({
277
+ children,
278
+ className,
279
+ }) => {
280
+ return (
281
+ <div
282
+ className={clsx(
283
+ 'px-3 py-2 text-xs font-semibold text-(--text-muted) uppercase tracking-wider',
284
+ className
285
+ )}
286
+ >
287
+ {children}
288
+ </div>
289
+ )
290
+ }
291
+
292
+ export const ContextMenuSeparator: React.FC<ContextMenuSeparatorProps> = ({
293
+ className,
294
+ }) => {
295
+ return (
296
+ <BaseMenu.Separator
297
+ className={clsx('my-1 h-px bg-(--border-light)', className)}
298
+ />
299
+ )
300
+ }
301
+
302
+ export const ContextMenuSub: React.FC<ContextMenuSubProps> = ({
303
+ children,
304
+ open,
305
+ defaultOpen,
306
+ onOpenChange,
307
+ }) => {
308
+ return (
309
+ <BaseMenu.Root
310
+ open={open}
311
+ defaultOpen={defaultOpen}
312
+ onOpenChange={onOpenChange}
313
+ >
314
+ {children}
315
+ </BaseMenu.Root>
316
+ )
317
+ }
318
+
319
+ export const ContextMenuSubTrigger = React.forwardRef<
320
+ HTMLButtonElement,
321
+ ContextMenuSubTriggerProps
322
+ >(({ children, className, disabled }, ref) => {
323
+ return (
324
+ <MenuTrigger
325
+ ref={ref}
326
+ className={clsx(
327
+ 'relative flex items-center justify-between gap-2 px-3 py-2 text-[15px] cursor-pointer select-none',
328
+ 'text-(--text-primary) hover:bg-(--bg-hover)',
329
+ 'data-highlighted:bg-(--bg-hover)',
330
+ 'data-disabled:opacity-50 data-disabled:pointer-events-none w-full',
331
+ className
332
+ )}
333
+ render={
334
+ <button disabled={disabled}>
335
+ {children}
336
+ <ChevronRight className="w-4 h-4 ml-auto"/>
337
+ </button>
338
+ }
339
+ />
340
+ )
341
+ })
342
+
343
+ ContextMenuSubTrigger.displayName = 'ContextMenuSubTrigger'
344
+
345
+ export const ContextMenuSubContent = React.forwardRef<
346
+ HTMLDivElement,
347
+ ContextMenuSubContentProps
348
+ >(({ children, className }, ref) => {
349
+ return (
350
+ <BaseMenu.Portal>
351
+ <BaseMenu.Positioner side="right" align="start" sideOffset={8}>
352
+ <BaseMenu.Popup
353
+ ref={ref}
354
+ className={clsx(
355
+ 'z-50 min-w-45 max-w-80 rounded-md border border-(--border-light) bg-(--bg-primary) shadow-lg',
356
+ 'py-1',
357
+ className
358
+ )}
359
+ >
360
+ {children}
361
+ </BaseMenu.Popup>
362
+ </BaseMenu.Positioner>
363
+ </BaseMenu.Portal>
364
+ )
365
+ })
366
+
367
+ ContextMenuSubContent.displayName = 'ContextMenuSubContent'
368
+
369
+ // Convenience component for context menu items with icons and shortcuts
370
+ export const ContextMenuItemWithIcon: React.FC<{
371
+ icon?: React.ReactNode
372
+ children: React.ReactNode
373
+ shortcut?: string
374
+ className?: string
375
+ disabled?: boolean
376
+ onSelect?: () => void
377
+ destructive?: boolean
378
+ }> = ({ icon, children, shortcut, className, disabled, onSelect, destructive }) => {
379
+ return (
380
+ <ContextMenuItem
381
+ className={className}
382
+ disabled={disabled}
383
+ onSelect={onSelect}
384
+ destructive={destructive}
385
+ >
386
+ {icon && <span className="shrink-0">{icon}</span>}
387
+ <span className="flex-1">{children}</span>
388
+ {shortcut && (
389
+ <span className="ml-auto text-xs text-(--text-muted)">{shortcut}</span>
390
+ )}
391
+ </ContextMenuItem>
392
+ )
393
+ }