@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,186 @@
1
+ // UI Components Export
2
+ // 统一导出所有组件,方便使用
3
+
4
+ // Basic Components
5
+ export { Button } from './Button'
6
+ export type { ButtonProps } from './Button'
7
+
8
+ export { Avatar } from './Avatar'
9
+ export type { AvatarProps } from './Avatar'
10
+
11
+ export { Badge } from './Badge'
12
+ export type { BadgeProps } from './Badge'
13
+
14
+ export { Input } from './Input'
15
+ export type { InputProps } from './Input'
16
+
17
+ export { IconButton } from './IconButton'
18
+ export type { IconButtonProps } from './IconButton'
19
+
20
+ export { Tooltip } from './Tooltip'
21
+ export type { TooltipProps } from './Tooltip'
22
+
23
+ // Popover Components
24
+ export {
25
+ Popover,
26
+ PopoverTrigger,
27
+ PopoverContent,
28
+ PopoverClose,
29
+ PopoverHeader,
30
+ PopoverBody,
31
+ PopoverFooter,
32
+ ImperativePopoverProvider,
33
+ useImperativePopover,
34
+ } from './Popover'
35
+ export type {
36
+ PopoverProps,
37
+ PopoverTriggerProps,
38
+ PopoverContentProps,
39
+ PopoverCloseProps,
40
+ ImperativePopoverOptions,
41
+ UseImperativePopoverReturn,
42
+ } from './Popover'
43
+
44
+ // Menu Components
45
+ export {
46
+ Menu,
47
+ MenuTrigger,
48
+ MenuContent,
49
+ MenuItem,
50
+ MenuCheckboxItem,
51
+ MenuRadioGroup,
52
+ MenuRadioItem,
53
+ MenuLabel,
54
+ MenuSeparator,
55
+ MenuSub,
56
+ MenuSubTrigger,
57
+ MenuSubContent,
58
+ MenuItemWithIcon
59
+ } from './Menu'
60
+ export type {
61
+ MenuProps,
62
+ MenuTriggerProps,
63
+ MenuContentProps,
64
+ MenuItemProps,
65
+ MenuCheckboxItemProps,
66
+ MenuRadioGroupProps,
67
+ MenuRadioItemProps,
68
+ MenuLabelProps,
69
+ MenuSeparatorProps,
70
+ MenuSubProps,
71
+ MenuSubTriggerProps,
72
+ MenuSubContentProps
73
+ } from './Menu'
74
+
75
+ // Context Menu Components
76
+ export {
77
+ ContextMenu,
78
+ ContextMenuTrigger,
79
+ ContextMenuContent,
80
+ ContextMenuItem,
81
+ ContextMenuCheckboxItem,
82
+ ContextMenuRadioGroup,
83
+ ContextMenuRadioItem,
84
+ ContextMenuLabel,
85
+ ContextMenuSeparator,
86
+ ContextMenuSub,
87
+ ContextMenuSubTrigger,
88
+ ContextMenuSubContent,
89
+ ContextMenuItemWithIcon
90
+ } from './ContextMenu'
91
+ export type {
92
+ ContextMenuProps,
93
+ ContextMenuTriggerProps,
94
+ ContextMenuContentProps,
95
+ ContextMenuItemProps,
96
+ ContextMenuCheckboxItemProps,
97
+ ContextMenuRadioGroupProps,
98
+ ContextMenuRadioItemProps,
99
+ ContextMenuLabelProps,
100
+ ContextMenuSeparatorProps,
101
+ ContextMenuSubProps,
102
+ ContextMenuSubTriggerProps,
103
+ ContextMenuSubContentProps
104
+ } from './ContextMenu'
105
+
106
+ // Dialog Components
107
+ export {
108
+ Dialog,
109
+ DialogHeader,
110
+ DialogBody,
111
+ DialogFooter,
112
+ DialogTrigger,
113
+ DialogClose,
114
+ DialogProvider,
115
+ useDialog,
116
+ } from './Dialog'
117
+ export type {
118
+ DialogProps,
119
+ DialogTriggerProps,
120
+ DialogSize,
121
+ ShowDialogOptions,
122
+ ConfirmDialogOptions,
123
+ AlertDialogOptions,
124
+ UseDialogReturn,
125
+ } from './Dialog'
126
+
127
+ export { AlertDialog, AlertDialogTrigger } from './AlertDialog'
128
+ export type { AlertDialogProps, AlertDialogTriggerProps } from './AlertDialog'
129
+
130
+ // Form Components
131
+ export {
132
+ Form,
133
+ FormField,
134
+ FormInput,
135
+ FormTextarea,
136
+ FormSelect,
137
+ FormCheckbox,
138
+ FormActions,
139
+ useFormContext
140
+ } from './Form'
141
+ export type {
142
+ FormProps,
143
+ FormFieldProps,
144
+ FormInputProps,
145
+ FormTextareaProps,
146
+ FormSelectProps,
147
+ FormCheckboxProps,
148
+ FormActionsProps
149
+ } from './Form'
150
+
151
+ // Select Component
152
+ export { Select } from './Select'
153
+ export type { SelectProps, SelectOption, SelectGroup } from './Select'
154
+
155
+ // Checkbox Component
156
+ export { Checkbox } from './Checkbox'
157
+ export type { CheckboxProps } from './Checkbox'
158
+
159
+ // Radio Components
160
+ export { Radio, RadioGroup } from './Radio'
161
+ export type { RadioProps, RadioGroupProps } from './Radio'
162
+
163
+ // Switch Component
164
+ export { Switch } from './Switch'
165
+ export type { SwitchProps } from './Switch'
166
+
167
+ // Tabs Components
168
+ export { Tabs, TabList, Tab, TabPanel } from './Tabs'
169
+ export type { TabsProps, TabListProps, TabProps, TabPanelProps } from './Tabs'
170
+
171
+ // Progress Component
172
+ export { Progress } from './Progress'
173
+ export type { ProgressProps } from './Progress'
174
+
175
+ // Toast Components
176
+ export { ToastProvider, useToast } from './Toast'
177
+ export type { ToastProviderProps, ToastOptions, ToastType } from './Toast'
178
+
179
+ // Loading Component
180
+ export { Loading } from './Loading'
181
+ export type { LoadingProps, LoadingVariant, LoadingSize } from './Loading'
182
+
183
+ // AutoComplete Component
184
+ export { AutoComplete } from './AutoComplete'
185
+ export type { AutoCompleteProps, AutoCompleteOption } from './AutoComplete'
186
+
@@ -0,0 +1,53 @@
1
+ import React, { createContext, useEffect, useState } from 'react';
2
+
3
+ type Theme = 'light' | 'dark';
4
+ type ThemeColor = 'slack' | 'blue' | 'green' | 'aubergine';
5
+
6
+ interface ThemeContextType {
7
+ theme: Theme;
8
+ themeColor: ThemeColor;
9
+ toggleTheme: () => void;
10
+ setThemeColor: (color: ThemeColor) => void;
11
+ }
12
+
13
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
14
+
15
+ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
16
+ const [theme, setTheme] = useState<Theme>(() => {
17
+ // Check local storage or system preference
18
+ const savedTheme = localStorage.getItem('theme') as Theme;
19
+ if (savedTheme) {
20
+ return savedTheme;
21
+ }
22
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
23
+ return 'dark';
24
+ }
25
+ return 'light';
26
+ });
27
+
28
+ const [themeColor, setThemeColor] = useState<ThemeColor>(() => {
29
+ const savedThemeColor = localStorage.getItem('themeColor') as ThemeColor;
30
+ return savedThemeColor || 'slack';
31
+ });
32
+
33
+ useEffect(() => {
34
+ const root = window.document.documentElement;
35
+ root.classList.remove('light', 'dark');
36
+ root.classList.add(theme);
37
+ root.setAttribute('data-theme-color', themeColor);
38
+ localStorage.setItem('theme', theme);
39
+ localStorage.setItem('themeColor', themeColor);
40
+ }, [theme, themeColor]);
41
+
42
+ const toggleTheme = () => {
43
+ setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
44
+ };
45
+
46
+ return (
47
+ <ThemeContext.Provider value={{ theme, themeColor, toggleTheme, setThemeColor }}>
48
+ {children}
49
+ </ThemeContext.Provider>
50
+ );
51
+ };
52
+
53
+ export { ThemeContext };
@@ -0,0 +1,11 @@
1
+ import { useContext } from 'react';
2
+ import { ThemeContext } from './ThemeContext';
3
+
4
+ export const useTheme = () => {
5
+ const context = useContext(ThemeContext);
6
+ if (context === undefined) {
7
+ throw new Error('useTheme must be used within a ThemeProvider');
8
+ }
9
+ return context;
10
+ };
11
+
@@ -0,0 +1,94 @@
1
+ import { Layout } from './layout/Layout'
2
+ import { Message } from './components/Message'
3
+ import { Composer } from './components/Composer'
4
+ import { ChannelHeader } from './components/ChannelHeader'
5
+
6
+ function SlackApp () {
7
+ return (
8
+ <Layout>
9
+ <ChannelHeader/>
10
+
11
+ {/* Main Scroll Area */}
12
+ <div className="flex-1 overflow-y-auto px-5 custom-scrollbar flex flex-col pt-4">
13
+
14
+ <div className="flex-1"/>
15
+ {/* Spacer to push content down if empty, or just normal flow */}
16
+
17
+ {/* Mock Messages */}
18
+ <div className="pb-4">
19
+
20
+ {/* Welcome Message */}
21
+ <div className="mb-8 mt-4 px-5">
22
+ <h1 className="text-3xl font-black mb-2 flex items-center gap-2">
23
+ 👋 Welcome to #design-system!
24
+ </h1>
25
+ <p className="text-(--text-secondary)">
26
+ This channel is for everything related to our new Base UI implementation.
27
+ </p>
28
+ </div>
29
+
30
+ <div className="border-t border-(--border-light) mb-4 flex items-center gap-4 py-2 px-5 text-[13px]">
31
+ <span className="bg-white text-(--text-secondary) -mt-5 px-2">Yesterday</span>
32
+ </div>
33
+
34
+ <Message
35
+ id="1"
36
+ author={{ name: 'Alice Smith', avatar: 'https://i.pravatar.cc/150?u=4', status: 'online' }}
37
+ timestamp="10:23 AM"
38
+ content="Hey everyone! I just pushed the new Button component tokens. Let me know what you think."
39
+ reactions={[{ emoji: '🚀', count: 4, reacted: true }, { emoji: '🔥', count: 2 }]}
40
+ />
41
+
42
+ <Message
43
+ id="2"
44
+ author={{ name: 'Bob Jones', avatar: 'https://i.pravatar.cc/150?u=5', status: 'away' }}
45
+ timestamp="10:25 AM"
46
+ content="Looks great! I love the new hover states. Are we using CSS variables for the focus rings?"
47
+ isFirst={true}
48
+ />
49
+
50
+ <Message
51
+ id="3"
52
+ author={{ name: 'Alice Smith', avatar: 'https://i.pravatar.cc/150?u=4', status: 'online' }}
53
+ timestamp="10:26 AM"
54
+ content="Yes, `var(--slack-blue)` is attached to the `ring` classes in Tailwind."
55
+ isFirst={true}
56
+ />
57
+
58
+ <div className="border-t border-(--border-light) my-4 flex items-center gap-4 py-2 px-5 text-[13px]">
59
+ <span className="bg-(--bg-primary) text-(--text-secondary) -mt-5 px-2">Today</span>
60
+ </div>
61
+
62
+ <Message
63
+ id="4"
64
+ author={{ name: 'Charlie Day', avatar: 'https://i.pravatar.cc/150?u=8', status: 'dnd' }}
65
+ timestamp="9:01 AM"
66
+ content={
67
+ <div className="space-y-2">
68
+ <p>I've updated the <strong>Composer</strong> component to support rich text (conceptually). Here is
69
+ a list of changes:</p>
70
+ <ul className="list-disc list-inside">
71
+ <li>Added bold/italic support keys</li>
72
+ <li>Integrated the emoji picker icon</li>
73
+ <li>Fixed the resizing issue</li>
74
+ </ul>
75
+ <div
76
+ className="p-3 bg-(--bg-secondary) rounded border border-(--border-light) mt-2 font-mono text-sm text-(--slack-red)">
77
+ npm run build
78
+ </div>
79
+ </div>
80
+ }
81
+ isFirst={true}
82
+ reactions={[{ emoji: '👀', count: 1 }]}
83
+ />
84
+
85
+ </div>
86
+
87
+ </div>
88
+
89
+ <Composer/>
90
+ </Layout>
91
+ )
92
+ }
93
+
94
+ export default SlackApp
@@ -0,0 +1,34 @@
1
+ import { Hash, ChevronDown, UserPlus, Info } from 'lucide-react';
2
+ import { Avatar } from '../../../components/Avatar';
3
+
4
+ export const ChannelHeader = ({ title = "design-system", memberCount = 24 }) => {
5
+ return (
6
+ <header className="h-[49px] border-b border-(--border-light) flex items-center justify-between px-5 shrink-0 bg-(--bg-primary) shadow-sm z-10">
7
+ <div className="flex items-center gap-1 font-bold text-(--text-primary) cursor-pointer hover:bg-(--bg-hover) py-1 px-2 -ml-2 rounded transition-colors group">
8
+ <Hash className="w-5 h-5 text-(--text-secondary)" />
9
+ <span className="text-lg">{title}</span>
10
+ <ChevronDown className="w-3.5 h-3.5 text-(--text-secondary) group-hover:block hidden" />
11
+ </div>
12
+
13
+ <div className="flex items-center">
14
+ <div className="flex items-center -space-x-2 mr-4 cursor-pointer hover:opacity-80">
15
+ <Avatar size="xs" src="https://i.pravatar.cc/150?u=1" className="ring-2 ring-(--bg-primary)" />
16
+ <Avatar size="xs" src="https://i.pravatar.cc/150?u=2" className="ring-2 ring-(--bg-primary)" />
17
+ <Avatar size="xs" src="https://i.pravatar.cc/150?u=3" className="ring-2 ring-(--bg-primary)" />
18
+ <div className="w-6 h-6 rounded bg-(--bg-secondary) flex items-center justify-center text-[10px] ring-2 ring-(--bg-primary) font-medium text-(--text-secondary)">
19
+ +{memberCount - 3}
20
+ </div>
21
+ </div>
22
+
23
+ <div className="border-l pl-4 flex items-center gap-1 text-(--text-secondary)">
24
+ <button className="p-1 hover:bg-(--bg-hover) rounded" title="Add people">
25
+ <UserPlus className="w-5 h-5 opacity-80" />
26
+ </button>
27
+ <button className="p-1 hover:bg-(--bg-hover) rounded" title="Channel details">
28
+ <Info className="w-5 h-5 opacity-80" />
29
+ </button>
30
+ </div>
31
+ </div>
32
+ </header>
33
+ );
34
+ };
@@ -0,0 +1,42 @@
1
+ import { Bold, Italic, Link, List, Smile, Send, Plus, AtSign, Video } from 'lucide-react';
2
+ import { Button } from '../../../components/Button';
3
+ import { IconButton } from '../../../components/IconButton';
4
+
5
+ export const Composer = () => {
6
+ return (
7
+ <div className="p-5 pb-6">
8
+ <div className="border border-(--border-light) rounded-lg overflow-hidden focus-within:ring-1 focus-within:ring-(--slack-blue) focus-within:border-(--slack-blue) transition-shadow bg-(--bg-primary) shadow-sm">
9
+ {/* Toolbar */}
10
+ <div className="bg-(--bg-hover) px-2 py-1 flex items-center gap-0.5 border-b border-(--border-light) overflow-x-auto">
11
+ <IconButton size="sm" variant="ghost" title="Bold"><Bold className="w-4 h-4" /></IconButton>
12
+ <IconButton size="sm" variant="ghost" title="Italic"><Italic className="w-4 h-4" /></IconButton>
13
+ <IconButton size="sm" variant="ghost" title="Link"><Link className="w-4 h-4" /></IconButton>
14
+ <div className="w-px h-4 bg-(--border-light) mx-1"></div>
15
+ <IconButton size="sm" variant="ghost" title="List"><List className="w-4 h-4" /></IconButton>
16
+ </div>
17
+
18
+ {/* Text Area */}
19
+ <textarea
20
+ className="w-full p-3 min-h-[40px] max-h-[40vh] outline-none text-[15px] resize-none font-[lato] bg-(--bg-primary) text-(--text-primary) placeholder:text-(--text-muted)"
21
+ placeholder="Message #design-system"
22
+ rows={1}
23
+ style={{ minHeight: '80px' }}
24
+ />
25
+
26
+ {/* Footer Actions */}
27
+ <div className="flex justify-between items-center p-2 bg-(--bg-primary)">
28
+ <div className="flex gap-1">
29
+ <IconButton size="sm" variant="ghost"><Plus className="w-4 h-4" /></IconButton>
30
+ <IconButton size="sm" variant="ghost"><Video className="w-4 h-4" /></IconButton>
31
+ <IconButton size="sm" variant="ghost"><Smile className="w-4 h-4" /></IconButton>
32
+ <IconButton size="sm" variant="ghost"><AtSign className="w-4 h-4" /></IconButton>
33
+ </div>
34
+ <div className="flex items-center gap-2">
35
+ <span className="text-[11px] text-(--text-muted) hidden sm:inline-block">Press <strong>Enter</strong> to send</span>
36
+ <Button variant="primary" size="sm" className="px-4 h-8"><Send className="w-3.5 h-3.5" /></Button>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ );
42
+ };
@@ -0,0 +1,97 @@
1
+ import React from 'react';
2
+ import { Avatar } from '../../../components/Avatar';
3
+ import { Smile, MessageSquare, Share, MoreHorizontal } from 'lucide-react';
4
+ import { IconButton } from '../../../components/IconButton';
5
+ import { UserProfile } from './UserProfile';
6
+ import clsx from 'clsx';
7
+
8
+ export interface MessageProps {
9
+ id: string;
10
+ author: {
11
+ name: string;
12
+ avatar: string;
13
+ status?: 'online' | 'away' | 'dnd' | 'offline';
14
+ };
15
+ content: React.ReactNode;
16
+ timestamp: string;
17
+ isFirst?: boolean; // If false, squashes the avatar/header
18
+ reactions?: Array<{ emoji: string; count: number; reacted?: boolean }>;
19
+ }
20
+
21
+ export const Message = ({ author, content, timestamp, isFirst = true, reactions }: MessageProps) => {
22
+ return (
23
+ <div className={clsx(
24
+ "group flex gap-2 py-0.5 px-5 hover:bg-(--bg-hover) -mx-5 relative",
25
+ isFirst ? "mt-2 pt-2" : ""
26
+ )}>
27
+
28
+ {/* Gutter / Avatar */}
29
+ <div className="w-9 shrink-0">
30
+ {isFirst ? (
31
+ <UserProfile user={{ ...author, title: "Product Designer", localTime: "4:43 PM local time", email: "user@example.com" }}>
32
+ <button className="block">
33
+ <Avatar
34
+ src={author.avatar}
35
+ alt={author.name}
36
+ size="md"
37
+ status={author.status}
38
+ className="cursor-pointer"
39
+ />
40
+ </button>
41
+ </UserProfile>
42
+ ) : (
43
+ <div className="w-[36px] text-right text-[11px] text-(--text-muted) opacity-0 group-hover:opacity-100 mt-1 select-none">
44
+ {timestamp.split(' ')[0]}
45
+ </div>
46
+ )}
47
+ </div>
48
+
49
+ {/* Content */}
50
+ <div className="flex-1 min-w-0">
51
+ {isFirst && (
52
+ <div className="flex items-baseline gap-2 mb-0.5">
53
+ <UserProfile user={{ ...author, title: "Product Designer", localTime: "4:43 PM local time", email: "user@example.com" }}>
54
+ <button className="font-bold text-[15px] cursor-pointer hover:underline text-(--text-primary) hover:text-(--text-primary) bg-transparent border-none p-0">
55
+ {author.name}
56
+ </button>
57
+ </UserProfile>
58
+ <span className="text-[12px] text-(--text-muted) cursor-pointer hover:underline">{timestamp}</span>
59
+ </div>
60
+ )}
61
+
62
+ <div className="text-[15px] leading-relaxed text-(--text-primary) wrap-break-word">
63
+ {content}
64
+ </div>
65
+
66
+ {/* Reactions */}
67
+ {reactions && reactions.length > 0 && (
68
+ <div className="flex flex-wrap gap-1 mt-1">
69
+ {reactions.map((r, i) => (
70
+ <button
71
+ key={i}
72
+ className={clsx(
73
+ "flex items-center gap-1.5 px-1.5 py-0.5 rounded-full border text-[13px] hover:bg-white transition-colors",
74
+ r.reacted
75
+ ? "bg-[rgba(29,28,29,0.05)] border-(--slack-blue) text-(--slack-blue)"
76
+ : "bg-[rgba(29,28,29,0.05)] border-transparent text-(--text-secondary)"
77
+ )}
78
+ >
79
+ <span>{r.emoji}</span>
80
+ <span className="font-medium text-[11px]">{r.count}</span>
81
+ </button>
82
+ ))}
83
+ </div>
84
+ )}
85
+ </div>
86
+
87
+ {/* Hover Actions (Floating Toolbar) */}
88
+ <div className="absolute -top-4 right-4 border border-(--border-light) shadow-sm bg-(--bg-primary) rounded-md p-1 opacity-0 group-hover:opacity-100 transition-opacity z-10 hidden group-hover:flex">
89
+ <IconButton size="sm" variant="ghost" title="Add reaction"><Smile className="w-4 h-4" /></IconButton>
90
+ <IconButton size="sm" variant="ghost" title="Reply"><MessageSquare className="w-4 h-4" /></IconButton>
91
+ <IconButton size="sm" variant="ghost" title="Share"><Share className="w-4 h-4" /></IconButton>
92
+ <IconButton size="sm" variant="ghost" title="More"><MoreHorizontal className="w-4 h-4" /></IconButton>
93
+ </div>
94
+
95
+ </div>
96
+ );
97
+ };
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { Popover as BasePopover } from '@base-ui/react';
3
+ import { Avatar } from '../../../components/Avatar';
4
+ import { Button } from '../../../components/Button';
5
+ import { Mail, Clock } from 'lucide-react';
6
+
7
+ export interface UserProfileProps {
8
+ user: {
9
+ name: string;
10
+ avatar: string;
11
+ status?: 'online' | 'away' | 'dnd' | 'offline';
12
+ title?: string;
13
+ email?: string;
14
+ localTime?: string;
15
+ };
16
+ children: React.ReactNode;
17
+ }
18
+
19
+ export const UserProfile = ({ user, children }: UserProfileProps) => {
20
+ return (
21
+ <BasePopoverProvider>
22
+ <BasePopover.Trigger className="cursor-pointer appearance-none bg-transparent border-none p-0 text-left">
23
+ {children}
24
+ </BasePopover.Trigger>
25
+ <BasePopover.Portal>
26
+ <BasePopover.Positioner side="right" align="start" sideOffset={10}>
27
+ <BasePopover.Popup className="w-[300px] bg-white rounded-lg shadow-xl border border-(--border-light) overflow-hidden z-50 animate-in fade-in zoom-in-95 duration-200 focus:outline-none">
28
+
29
+ {/* Header Image / Avatar */}
30
+ <div className="h-24 bg-(--slack-aubergine) relative">
31
+ <div className="absolute -bottom-8 left-6">
32
+ <Avatar
33
+ src={user.avatar}
34
+ size="xl"
35
+ status={user.status}
36
+ className="ring-4 ring-white rounded-md"
37
+ rounded={true}
38
+ />
39
+ </div>
40
+ </div>
41
+
42
+ <div className="pt-10 px-6 pb-6">
43
+ <h2 className="text-xl font-bold text-(--text-primary)">{user.name}</h2>
44
+ <p className="text-(--text-secondary) text-[15px]">{user.title || "Member"}</p>
45
+
46
+ <div className="mt-4 flex gap-2">
47
+ <Button size="sm" variant="secondary" fullWidth>Message</Button>
48
+ <Button size="sm" variant="secondary" fullWidth>Huddle</Button>
49
+ </div>
50
+
51
+ <div className="mt-6 space-y-3">
52
+ {user.localTime && (
53
+ <div className="flex items-center gap-3 text-[15px] text-(--text-primary)">
54
+ <Clock className="w-4 h-4 text-(--text-secondary)" />
55
+ <span>{user.localTime}</span>
56
+ </div>
57
+ )}
58
+ {user.email && (
59
+ <div className="flex items-center gap-3 text-[15px] text-(--text-primary)">
60
+ <Mail className="w-4 h-4 text-(--text-secondary)" />
61
+ <a href={`mailto:${user.email}`} className="text-[#1264a3] hover:underline decoration-1">{user.email}</a>
62
+ </div>
63
+ )}
64
+ </div>
65
+ </div>
66
+
67
+ </BasePopover.Popup>
68
+ </BasePopover.Positioner>
69
+ </BasePopover.Portal>
70
+ </BasePopoverProvider>
71
+ );
72
+ };
73
+
74
+ // Wrapper for Base UI Popover Structure (similar to Tooltip assumption)
75
+ function BasePopoverProvider({ children }: { children: React.ReactNode }) {
76
+ // Creating a safer flexible wrapper in case exports differ
77
+ return <BasePopover.Root>{children}</BasePopover.Root>;
78
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { Sidebar } from './Sidebar';
3
+ import { TopBar } from './TopBar';
4
+
5
+ interface LayoutProps {
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ export const Layout = ({ children }: LayoutProps) => {
10
+ return (
11
+ <div className="flex flex-col h-screen w-full bg-(--bg-primary) overflow-hidden">
12
+ {/* Top Navigation */}
13
+ <TopBar />
14
+
15
+ {/* Main Workspace Area */}
16
+ <div className="flex flex-1 overflow-hidden">
17
+ {/* Sidebar (expandable/collapsible logic could go here) */}
18
+ <Sidebar />
19
+
20
+ {/* Main Content View */}
21
+ <main className="flex-1 flex flex-col bg-(--bg-primary) min-w-0 relative">
22
+ {children}
23
+ </main>
24
+ </div>
25
+ </div>
26
+ );
27
+ };