@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,371 @@
1
+ import clsx from 'clsx'
2
+ import { Dialog as BaseDialog } from '@base-ui/react'
3
+ import React, {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useMemo,
8
+ useState,
9
+ } from 'react'
10
+ import { X } from 'lucide-react'
11
+ import { Button } from './Button'
12
+
13
+ export type DialogSize = 'sm' | 'md' | 'lg' | 'xl';
14
+
15
+ export interface DialogProps {
16
+ open?: boolean;
17
+ onOpenChange?: (open: boolean) => void;
18
+ title?: string;
19
+ description?: string;
20
+ children?: React.ReactNode;
21
+ size?: DialogSize;
22
+ showCloseButton?: boolean;
23
+ className?: string;
24
+ }
25
+
26
+ export const Dialog = ({
27
+ open,
28
+ onOpenChange,
29
+ title,
30
+ description,
31
+ children,
32
+ size = 'md',
33
+ showCloseButton = true,
34
+ className,
35
+ }: DialogProps) => {
36
+ const sizeClasses = {
37
+ sm: 'max-w-sm',
38
+ md: 'max-w-md',
39
+ lg: 'max-w-lg',
40
+ xl: 'max-w-xl',
41
+ }
42
+
43
+ return (
44
+ <BaseDialog.Root open={open} onOpenChange={onOpenChange}>
45
+ <BaseDialog.Portal>
46
+ {/* Backdrop */}
47
+ <BaseDialog.Backdrop
48
+ className="fixed inset-0 bg-black/50 z-40 transition-opacity duration-200"
49
+ />
50
+
51
+ {/* Dialog Content */}
52
+ <BaseDialog.Popup
53
+ className={clsx(
54
+ 'fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]',
55
+ 'w-full',
56
+ sizeClasses[size],
57
+ 'bg-(--bg-primary) rounded-xl border border-(--border-gray)',
58
+ 'transition-all duration-200',
59
+ 'outline-none focus-visible:outline-none',
60
+ className,
61
+ )}
62
+ >
63
+ {/* Header */}
64
+ {(title || showCloseButton) && (
65
+ <div className="flex items-start justify-between p-6 border-b border-(--border-light)">
66
+ <div className="flex-1">
67
+ {title && (
68
+ <BaseDialog.Title className="text-2xl font-black text-(--text-primary) leading-tight">
69
+ {title}
70
+ </BaseDialog.Title>
71
+ )}
72
+ {description && (
73
+ <BaseDialog.Description className="text-(--text-secondary) mt-2 text-sm">
74
+ {description}
75
+ </BaseDialog.Description>
76
+ )}
77
+ </div>
78
+ {showCloseButton && (
79
+ <BaseDialog.Close
80
+ className="ml-4 p-2 rounded hover:bg-(--bg-hover) text-(--text-secondary) hover:text-(--text-primary) transition-colors focus-visible:ring-2 focus-visible:ring-(--slack-blue) focus-visible:ring-offset-2 outline-none"
81
+ aria-label="Close dialog"
82
+ >
83
+ <X className="w-5 h-5"/>
84
+ </BaseDialog.Close>
85
+ )}
86
+ </div>
87
+ )}
88
+
89
+ {/* Body */}
90
+ <div className="p-6">{children}</div>
91
+ </BaseDialog.Popup>
92
+ </BaseDialog.Portal>
93
+ </BaseDialog.Root>
94
+ )
95
+ }
96
+
97
+ Dialog.displayName = 'Dialog'
98
+
99
+ // Sub-components for custom layouts
100
+ export const DialogHeader = ({ children, className }: { children: React.ReactNode; className?: string }) => (
101
+ <div className={clsx('mb-4', className)}>{children}</div>
102
+ )
103
+
104
+ export const DialogBody = ({ children, className }: { children: React.ReactNode; className?: string }) => (
105
+ <div className={clsx('text-(--text-primary)', className)}>{children}</div>
106
+ )
107
+
108
+ export const DialogFooter = ({ children, className }: { children: React.ReactNode; className?: string }) => (
109
+ <div className={clsx('flex items-center justify-end gap-3 mt-6 pt-4 border-t border-(--border-light)', className)}>
110
+ {children}
111
+ </div>
112
+ )
113
+
114
+ // Trigger component for opening dialogs
115
+ export interface DialogTriggerProps extends React.ComponentPropsWithoutRef<'button'> {
116
+ asChild?: boolean;
117
+ }
118
+
119
+ export const DialogTrigger = React.forwardRef<HTMLButtonElement, DialogTriggerProps>(
120
+ ({ children, asChild, ...props }, ref) => {
121
+ if (asChild) {
122
+ return <BaseDialog.Trigger ref={ref}>{children}</BaseDialog.Trigger>
123
+ }
124
+
125
+ return (
126
+ <BaseDialog.Trigger ref={ref} {...props}>
127
+ {children}
128
+ </BaseDialog.Trigger>
129
+ )
130
+ },
131
+ )
132
+
133
+ DialogTrigger.displayName = 'DialogTrigger'
134
+
135
+ // Re-export Close for custom usage
136
+ export const DialogClose = BaseDialog.Close
137
+
138
+ // ─── Imperative Dialog API ────────────────────────────────────────────────────
139
+
140
+ export interface ShowDialogOptions {
141
+ title?: string
142
+ description?: string
143
+ /** Custom content rendered inside the dialog body */
144
+ content?: React.ReactNode
145
+ size?: DialogSize
146
+ showCloseButton?: boolean
147
+ className?: string
148
+ }
149
+
150
+ export interface ConfirmDialogOptions {
151
+ title?: string
152
+ description?: string
153
+ content?: React.ReactNode
154
+ size?: DialogSize
155
+ confirmLabel?: string
156
+ cancelLabel?: string
157
+ confirmVariant?: 'primary' | 'danger' | 'secondary'
158
+ }
159
+
160
+ export interface AlertDialogOptions {
161
+ title?: string
162
+ description?: string
163
+ content?: React.ReactNode
164
+ size?: DialogSize
165
+ confirmLabel?: string
166
+ }
167
+
168
+ export interface UseDialogReturn {
169
+ /** Show a generic dialog. Resolves when the dialog is closed. */
170
+ show: (options: ShowDialogOptions) => Promise<void>
171
+ /** Show a confirm dialog. Resolves `true` when confirmed, `false` when cancelled. */
172
+ confirm: (options: ConfirmDialogOptions) => Promise<boolean>
173
+ /** Show an alert dialog with a single OK button. Resolves when dismissed. */
174
+ alert: (options: AlertDialogOptions) => Promise<void>
175
+ }
176
+
177
+ interface DialogEntry {
178
+ id: string
179
+ type: 'show' | 'confirm' | 'alert'
180
+ title?: string
181
+ description?: string
182
+ content?: React.ReactNode
183
+ size?: DialogSize
184
+ showCloseButton?: boolean
185
+ confirmLabel?: string
186
+ cancelLabel?: string
187
+ confirmVariant?: 'primary' | 'danger' | 'secondary'
188
+ className?: string
189
+ resolve: (value: boolean) => void
190
+ }
191
+
192
+ const DialogImperativeContext = createContext<UseDialogReturn | null>(null)
193
+
194
+ /**
195
+ * Provides the imperative dialog API to all descendant components.
196
+ * Must wrap any component that calls `useDialog()`.
197
+ *
198
+ * @example
199
+ * ```tsx
200
+ * // main.tsx
201
+ * <DialogProvider>
202
+ * <App />
203
+ * </DialogProvider>
204
+ *
205
+ * // Any component
206
+ * const { show, confirm, alert } = useDialog()
207
+ * await confirm({ title: 'Delete?', confirmLabel: 'Delete', confirmVariant: 'danger' })
208
+ * ```
209
+ */
210
+ export const DialogProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
211
+ const [dialogs, setDialogs] = useState<DialogEntry[]>([])
212
+
213
+ const removeDialog = useCallback((id: string) => {
214
+ setDialogs(prev => prev.filter(d => d.id !== id))
215
+ }, [])
216
+
217
+ const show = useCallback((options: ShowDialogOptions): Promise<void> => {
218
+ return new Promise(resolve => {
219
+ const id = Math.random().toString(36).slice(2, 9)
220
+ setDialogs(prev => [
221
+ ...prev,
222
+ { ...options, id, type: 'show', resolve: () => resolve() },
223
+ ])
224
+ })
225
+ }, [])
226
+
227
+ const confirm = useCallback((options: ConfirmDialogOptions): Promise<boolean> => {
228
+ return new Promise(resolve => {
229
+ const id = Math.random().toString(36).slice(2, 9)
230
+ setDialogs(prev => [...prev, { ...options, id, type: 'confirm', resolve }])
231
+ })
232
+ }, [])
233
+
234
+ const alert = useCallback((options: AlertDialogOptions): Promise<void> => {
235
+ return new Promise(resolve => {
236
+ const id = Math.random().toString(36).slice(2, 9)
237
+ setDialogs(prev => [
238
+ ...prev,
239
+ { ...options, id, type: 'alert', resolve: () => resolve() },
240
+ ])
241
+ })
242
+ }, [])
243
+
244
+ const value = useMemo<UseDialogReturn>(
245
+ () => ({ show, confirm, alert }),
246
+ [show, confirm, alert],
247
+ )
248
+
249
+ return (
250
+ <DialogImperativeContext.Provider value={value}>
251
+ {children}
252
+ {dialogs.map(dialog => (
253
+ <ImperativeDialogItem
254
+ key={dialog.id}
255
+ dialog={dialog}
256
+ onRemove={() => removeDialog(dialog.id)}
257
+ />
258
+ ))}
259
+ </DialogImperativeContext.Provider>
260
+ )
261
+ }
262
+
263
+ DialogProvider.displayName = 'DialogProvider'
264
+
265
+ function ImperativeDialogItem({
266
+ dialog,
267
+ onRemove,
268
+ }: {
269
+ dialog: DialogEntry
270
+ onRemove: () => void
271
+ }) {
272
+ const [open, setOpen] = useState(true)
273
+
274
+ const handleClose = useCallback(
275
+ (result: boolean) => {
276
+ setOpen(false)
277
+ dialog.resolve(result)
278
+ // Allow close animation to finish before unmounting
279
+ setTimeout(onRemove, 300)
280
+ },
281
+ [dialog, onRemove],
282
+ )
283
+
284
+ const commonProps = {
285
+ open,
286
+ title: dialog.title,
287
+ description: dialog.description,
288
+ size: dialog.size,
289
+ className: dialog.className,
290
+ }
291
+
292
+ if (dialog.type === 'show') {
293
+ return (
294
+ <Dialog
295
+ {...commonProps}
296
+ showCloseButton={dialog.showCloseButton ?? true}
297
+ onOpenChange={o => !o && handleClose(false)}
298
+ >
299
+ {dialog.content}
300
+ </Dialog>
301
+ )
302
+ }
303
+
304
+ if (dialog.type === 'confirm') {
305
+ return (
306
+ <Dialog
307
+ {...commonProps}
308
+ size={dialog.size ?? 'sm'}
309
+ showCloseButton={false}
310
+ onOpenChange={o => !o && handleClose(false)}
311
+ >
312
+ {dialog.content}
313
+ <DialogFooter>
314
+ <Button variant="secondary" onClick={() => handleClose(false)}>
315
+ {dialog.cancelLabel ?? 'Cancel'}
316
+ </Button>
317
+ <Button
318
+ variant={dialog.confirmVariant ?? 'primary'}
319
+ onClick={() => handleClose(true)}
320
+ >
321
+ {dialog.confirmLabel ?? 'Confirm'}
322
+ </Button>
323
+ </DialogFooter>
324
+ </Dialog>
325
+ )
326
+ }
327
+
328
+ // alert
329
+ return (
330
+ <Dialog
331
+ {...commonProps}
332
+ size={dialog.size ?? 'sm'}
333
+ showCloseButton={false}
334
+ onOpenChange={o => !o && handleClose(true)}
335
+ >
336
+ {dialog.content}
337
+ <DialogFooter>
338
+ <Button variant="primary" onClick={() => handleClose(true)}>
339
+ {dialog.confirmLabel ?? 'OK'}
340
+ </Button>
341
+ </DialogFooter>
342
+ </Dialog>
343
+ )
344
+ }
345
+
346
+ /**
347
+ * Returns imperative methods for showing dialogs from anywhere in the component tree.
348
+ *
349
+ * Must be used inside `<DialogProvider>`.
350
+ *
351
+ * @example
352
+ * ```tsx
353
+ * const { show, confirm, alert } = useDialog()
354
+ *
355
+ * // Generic dialog
356
+ * await show({ title: 'Welcome', content: <p>Hello world</p> })
357
+ *
358
+ * // Confirm
359
+ * const ok = await confirm({ title: 'Delete item?', confirmLabel: 'Delete', confirmVariant: 'danger' })
360
+ * if (ok) deleteItem()
361
+ *
362
+ * // Alert
363
+ * await alert({ title: 'Error', description: 'Something went wrong.' })
364
+ * ```
365
+ */
366
+ export function useDialog(): UseDialogReturn {
367
+ const ctx = useContext(DialogImperativeContext)
368
+ if (!ctx) throw new Error('useDialog must be used within <DialogProvider>')
369
+ return ctx
370
+ }
371
+