@oscarrf2/goo-ds 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 (70) hide show
  1. package/README.md +163 -0
  2. package/package.json +60 -0
  3. package/src/components/Button/Button.css +107 -0
  4. package/src/components/Button/Button.tsx +82 -0
  5. package/src/components/Button/Button.types.ts +62 -0
  6. package/src/components/Button/index.ts +3 -0
  7. package/src/components/Cell/Cell.css +64 -0
  8. package/src/components/Cell/Cell.tsx +42 -0
  9. package/src/components/Cell/Cell.types.ts +42 -0
  10. package/src/components/Cell/index.ts +3 -0
  11. package/src/components/Codeblock/Codeblock.css +90 -0
  12. package/src/components/Codeblock/Codeblock.tsx +88 -0
  13. package/src/components/Codeblock/Codeblock.types.ts +42 -0
  14. package/src/components/Codeblock/index.ts +3 -0
  15. package/src/components/CoreText/CoreText.tsx +43 -0
  16. package/src/components/CoreText/CoreText.types.ts +56 -0
  17. package/src/components/CoreText/index.ts +2 -0
  18. package/src/components/Divider/Divider.css +38 -0
  19. package/src/components/Divider/Divider.tsx +35 -0
  20. package/src/components/Divider/Divider.types.ts +19 -0
  21. package/src/components/Divider/index.ts +3 -0
  22. package/src/components/InputImage/InputImage.css +212 -0
  23. package/src/components/InputImage/InputImage.tsx +314 -0
  24. package/src/components/InputImage/InputImage.types.ts +86 -0
  25. package/src/components/InputImage/index.ts +2 -0
  26. package/src/components/Sidebar/Sidebar.css +35 -0
  27. package/src/components/Sidebar/Sidebar.tsx +42 -0
  28. package/src/components/Sidebar/Sidebar.types.ts +24 -0
  29. package/src/components/Sidebar/index.ts +3 -0
  30. package/src/components/SidebarItem/SidebarItem.css +70 -0
  31. package/src/components/SidebarItem/SidebarItem.tsx +55 -0
  32. package/src/components/SidebarItem/SidebarItem.types.ts +39 -0
  33. package/src/components/SidebarItem/index.ts +3 -0
  34. package/src/components/Skeleton/Skeleton.css +25 -0
  35. package/src/components/Skeleton/Skeleton.tsx +41 -0
  36. package/src/components/Skeleton/Skeleton.types.ts +65 -0
  37. package/src/components/Skeleton/index.ts +5 -0
  38. package/src/components/Spacer/Spacer.tsx +31 -0
  39. package/src/components/Spacer/Spacer.types.ts +58 -0
  40. package/src/components/Spacer/index.ts +3 -0
  41. package/src/components/TabItem/TabItem.css +67 -0
  42. package/src/components/TabItem/TabItem.tsx +45 -0
  43. package/src/components/TabItem/TabItem.types.ts +35 -0
  44. package/src/components/TabItem/index.ts +3 -0
  45. package/src/components/Table/Table.css +16 -0
  46. package/src/components/Table/Table.tsx +39 -0
  47. package/src/components/Table/Table.types.ts +18 -0
  48. package/src/components/Table/index.ts +3 -0
  49. package/src/components/TableRow/TableRow.css +53 -0
  50. package/src/components/TableRow/TableRow.tsx +53 -0
  51. package/src/components/TableRow/TableRow.types.ts +41 -0
  52. package/src/components/TableRow/index.ts +3 -0
  53. package/src/components/Tabs/Tabs.css +11 -0
  54. package/src/components/Tabs/Tabs.tsx +37 -0
  55. package/src/components/Tabs/Tabs.types.ts +18 -0
  56. package/src/components/Tabs/index.ts +3 -0
  57. package/src/components/index.ts +15 -0
  58. package/src/compositions/index.ts +3 -0
  59. package/src/index.css +68 -0
  60. package/src/index.ts +4 -0
  61. package/src/styles/component-tokens.css +270 -0
  62. package/src/styles/component-tokens.current.css +3 -0
  63. package/src/styles/fonts.css +11 -0
  64. package/src/styles/global-tokens.css +257 -0
  65. package/src/styles/index.css +20 -0
  66. package/src/styles/number-tokens.css +72 -0
  67. package/src/styles/semantic-tokens.css +84 -0
  68. package/src/styles/style-tokens.css +219 -0
  69. package/src/styles/typography-tokens.css +50 -0
  70. package/src/styles.css +2 -0
@@ -0,0 +1,314 @@
1
+ import type { InputImageProps } from "./InputImage.types";
2
+ import { Button } from "../Button/Button";
3
+ import { CoreText } from "../CoreText/CoreText";
4
+ import "./InputImage.css";
5
+
6
+ /**
7
+ * InputImage - Image upload input component with drag-and-drop support
8
+ *
9
+ * Provides a file upload interface with multiple visual states including:
10
+ * - Default: Empty state with drag-and-drop zone
11
+ * - Hover: Visual feedback during drag-over
12
+ * - Filled: Shows uploaded image with remove button
13
+ * - Loading: Loading skeleton state
14
+ * - Generating: Blurred preview during processing
15
+ * - Generated: Final result with optional comparison slider
16
+ * - Disabled: Non-interactive state
17
+ *
18
+ * Token-first per Constitution (no hardcoded values).
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * <InputImage state="Default" onUploadClick={handleUpload} />
23
+ * <InputImage state="Filled" imageSrc="/path/to/image.jpg" onRemoveClick={handleRemove} />
24
+ * <InputImage state="Generated" imageSrc="/path/to/image.jpg" comparisonSlider={true} />
25
+ * ```
26
+ */
27
+ export function InputImage({
28
+ state = "Default",
29
+ comparisonSlider = true,
30
+ imageSrc,
31
+ imageAlt = "",
32
+ onUploadClick,
33
+ onRemoveClick,
34
+ onDragEnter,
35
+ onDragLeave,
36
+ onDrop,
37
+ className = "",
38
+ children,
39
+ sliderPosition = 50,
40
+ // onSliderDrag not yet implemented
41
+ }: InputImageProps) {
42
+ // Default state
43
+ if (state === "Default") {
44
+ return (
45
+ <div
46
+ className={`gds-input-image gds-input-image-default ${className}`.trim()}
47
+ onDragEnter={onDragEnter}
48
+ onDragLeave={onDragLeave}
49
+ onDrop={onDrop}
50
+ data-component="InputImage"
51
+ data-state={state}
52
+ >
53
+ {children || (
54
+ <>
55
+ <CoreText
56
+ variant="label"
57
+ size="lg"
58
+ weight="medium"
59
+ className="gds-input-image-label"
60
+ >
61
+ Drag and drop
62
+ </CoreText>
63
+ <CoreText
64
+ variant="body"
65
+ size="sm"
66
+ weight="regular"
67
+ className="gds-input-image-separator"
68
+ >
69
+ or
70
+ </CoreText>
71
+ <Button
72
+ variant="primary"
73
+ size="small"
74
+ onClick={onUploadClick}
75
+ className="gds-input-image-button"
76
+ >
77
+ Click to upload
78
+ </Button>
79
+ </>
80
+ )}
81
+ </div>
82
+ );
83
+ }
84
+
85
+ // Hover state
86
+ if (state === "Hover") {
87
+ return (
88
+ <div
89
+ className={`gds-input-image gds-input-image-hover ${className}`.trim()}
90
+ onDragEnter={onDragEnter}
91
+ onDragLeave={onDragLeave}
92
+ onDrop={onDrop}
93
+ data-component="InputImage"
94
+ data-state={state}
95
+ >
96
+ {children || (
97
+ <>
98
+ <CoreText
99
+ variant="label"
100
+ size="lg"
101
+ weight="medium"
102
+ className="gds-input-image-label"
103
+ >
104
+ Drag and drop
105
+ </CoreText>
106
+ <CoreText
107
+ variant="body"
108
+ size="sm"
109
+ weight="regular"
110
+ className="gds-input-image-separator"
111
+ >
112
+ or
113
+ </CoreText>
114
+ <Button
115
+ variant="primary"
116
+ size="small"
117
+ onClick={onUploadClick}
118
+ className="gds-input-image-button"
119
+ >
120
+ Click to upload
121
+ </Button>
122
+ </>
123
+ )}
124
+ </div>
125
+ );
126
+ }
127
+
128
+ // Filled state
129
+ if (state === "Filled") {
130
+ return (
131
+ <div
132
+ className={`gds-input-image gds-input-image-filled ${className}`.trim()}
133
+ data-component="InputImage"
134
+ data-state={state}
135
+ >
136
+ {imageSrc && (
137
+ <img
138
+ src={imageSrc}
139
+ alt={imageAlt}
140
+ className="gds-input-image-preview"
141
+ />
142
+ )}
143
+ <Button
144
+ variant="primary"
145
+ size="small"
146
+ onClick={onRemoveClick}
147
+ className="gds-input-image-remove-button"
148
+ iconLeft={
149
+ <svg
150
+ width="16"
151
+ height="16"
152
+ viewBox="0 0 16 16"
153
+ fill="none"
154
+ xmlns="http://www.w3.org/2000/svg"
155
+ aria-hidden="true"
156
+ >
157
+ <path
158
+ d="M12 4L4 12M4 4L12 12"
159
+ stroke="currentColor"
160
+ strokeWidth="2"
161
+ strokeLinecap="round"
162
+ strokeLinejoin="round"
163
+ />
164
+ </svg>
165
+ }
166
+ />
167
+ </div>
168
+ );
169
+ }
170
+
171
+ // Loading state
172
+ if (state === "Loading") {
173
+ return (
174
+ <div
175
+ className={`gds-input-image gds-input-image-loading ${className}`.trim()}
176
+ data-component="InputImage"
177
+ data-state={state}
178
+ />
179
+ );
180
+ }
181
+
182
+ // Generating state
183
+ if (state === "Generating") {
184
+ return (
185
+ <div
186
+ className={`gds-input-image gds-input-image-generating ${className}`.trim()}
187
+ data-component="InputImage"
188
+ data-state={state}
189
+ >
190
+ <div className="gds-input-image-blur-container">
191
+ {imageSrc && (
192
+ <img
193
+ src={imageSrc}
194
+ alt={imageAlt}
195
+ className="gds-input-image-blur-preview"
196
+ />
197
+ )}
198
+ </div>
199
+ </div>
200
+ );
201
+ }
202
+
203
+ // Generated state
204
+ if (state === "Generated") {
205
+ return (
206
+ <div
207
+ className={`gds-input-image gds-input-image-generated ${className}`.trim()}
208
+ data-component="InputImage"
209
+ data-state={state}
210
+ >
211
+ {imageSrc && (
212
+ <img
213
+ src={imageSrc}
214
+ alt={imageAlt}
215
+ className="gds-input-image-result"
216
+ />
217
+ )}
218
+ {comparisonSlider && (
219
+ <div
220
+ className="gds-input-image-comparison-slider"
221
+ style={{
222
+ left: `${sliderPosition}%`,
223
+ }}
224
+ >
225
+ <div className="gds-input-image-slider-line" />
226
+ <Button
227
+ variant="secondary"
228
+ size="small"
229
+ className="gds-input-image-slider-button"
230
+ iconLeft={
231
+ <svg
232
+ width="16"
233
+ height="16"
234
+ viewBox="0 0 16 16"
235
+ fill="none"
236
+ xmlns="http://www.w3.org/2000/svg"
237
+ aria-hidden="true"
238
+ >
239
+ <path
240
+ d="M6 12L2 8L6 4"
241
+ stroke="currentColor"
242
+ strokeWidth="2"
243
+ strokeLinecap="round"
244
+ strokeLinejoin="round"
245
+ />
246
+ <path
247
+ d="M10 4L14 8L10 12"
248
+ stroke="currentColor"
249
+ strokeWidth="2"
250
+ strokeLinecap="round"
251
+ strokeLinejoin="round"
252
+ />
253
+ </svg>
254
+ }
255
+ />
256
+ </div>
257
+ )}
258
+ </div>
259
+ );
260
+ }
261
+
262
+ // Disabled state
263
+ if (state === "Disabled") {
264
+ return (
265
+ <div
266
+ className={`gds-input-image gds-input-image-disabled ${className}`.trim()}
267
+ data-component="InputImage"
268
+ data-state={state}
269
+ >
270
+ {children || (
271
+ <>
272
+ <CoreText
273
+ variant="label"
274
+ size="lg"
275
+ weight="medium"
276
+ className="gds-input-image-label"
277
+ >
278
+ Drag and drop
279
+ </CoreText>
280
+ <CoreText
281
+ variant="body"
282
+ size="sm"
283
+ weight="regular"
284
+ className="gds-input-image-separator"
285
+ >
286
+ or
287
+ </CoreText>
288
+ <Button
289
+ variant="primary"
290
+ size="small"
291
+ disabled
292
+ className="gds-input-image-button"
293
+ >
294
+ Click to upload
295
+ </Button>
296
+ </>
297
+ )}
298
+ </div>
299
+ );
300
+ }
301
+
302
+ // Fallback to default
303
+ return (
304
+ <div
305
+ className={`gds-input-image gds-input-image-default ${className}`.trim()}
306
+ data-component="InputImage"
307
+ data-state="Default"
308
+ >
309
+ {children}
310
+ </div>
311
+ );
312
+ }
313
+
314
+ export default InputImage;
@@ -0,0 +1,86 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ /**
4
+ * InputImage component props
5
+ * Provides image upload input with drag-and-drop, multiple states, and comparison slider
6
+ */
7
+
8
+ export type InputImageState =
9
+ | "Default"
10
+ | "Hover"
11
+ | "Filled"
12
+ | "Generating"
13
+ | "Generated"
14
+ | "Loading"
15
+ | "Disabled";
16
+
17
+ export interface InputImageProps {
18
+ /**
19
+ * The current state of the input
20
+ * @default "Default"
21
+ */
22
+ state?: InputImageState;
23
+
24
+ /**
25
+ * Whether to show the comparison slider in Generated state
26
+ * @default true
27
+ */
28
+ comparisonSlider?: boolean;
29
+
30
+ /**
31
+ * Position of the comparison slider (0-100 percentage)
32
+ * @default 50
33
+ */
34
+ sliderPosition?: number;
35
+
36
+ /**
37
+ * Callback when slider is being dragged
38
+ */
39
+ onSliderDrag?: (position: number) => void;
40
+
41
+ /**
42
+ * The image source URL for Filled, Generating, and Generated states
43
+ */
44
+ imageSrc?: string;
45
+
46
+ /**
47
+ * Alt text for the image
48
+ * @default ""
49
+ */
50
+ imageAlt?: string;
51
+
52
+ /**
53
+ * Callback when the upload button is clicked
54
+ */
55
+ onUploadClick?: () => void;
56
+
57
+ /**
58
+ * Callback when the remove/close button is clicked (in Filled state)
59
+ */
60
+ onRemoveClick?: () => void;
61
+
62
+ /**
63
+ * Callback when drag enter occurs
64
+ */
65
+ onDragEnter?: (e: React.DragEvent<HTMLDivElement>) => void;
66
+
67
+ /**
68
+ * Callback when drag leave occurs
69
+ */
70
+ onDragLeave?: (e: React.DragEvent<HTMLDivElement>) => void;
71
+
72
+ /**
73
+ * Callback when drop occurs
74
+ */
75
+ onDrop?: (e: React.DragEvent<HTMLDivElement>) => void;
76
+
77
+ /**
78
+ * Additional CSS class names
79
+ */
80
+ className?: string;
81
+
82
+ /**
83
+ * Custom children to replace default content (advanced use case)
84
+ */
85
+ children?: ReactNode;
86
+ }
@@ -0,0 +1,2 @@
1
+ export { InputImage } from "./InputImage";
2
+ export type { InputImageProps, InputImageState } from "./InputImage.types";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Sidebar component styles
3
+ * Token-driven sidebar container styling for the goo-ds design system
4
+ */
5
+
6
+ .gds-sidebar {
7
+ display: flex;
8
+ align-items: stretch;
9
+ position: relative;
10
+ background-color: var(--sidebar-background-default);
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ /* Sidebar content area */
15
+ .gds-sidebar-content {
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: flex-start;
19
+ min-width: var(--sidebar-medium-minWidth);
20
+ width: var(--sidebar-medium-minWidth);
21
+ padding-top: var(--sidebar-medium-paddingTop);
22
+ padding-bottom: var(--sidebar-medium-paddingBottom);
23
+ padding-left: var(--sidebar-medium-paddingHorizontal);
24
+ padding-right: var(--sidebar-medium-paddingHorizontal);
25
+ flex-shrink: 0;
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ /* Vertical divider on the right */
30
+ .gds-sidebar-divider {
31
+ width: 1px;
32
+ align-self: stretch;
33
+ flex-shrink: 0;
34
+ background-color: var(--divider-background-default);
35
+ }
@@ -0,0 +1,42 @@
1
+ import type { SidebarProps } from "./Sidebar.types";
2
+ import "./Sidebar.css";
3
+
4
+ /**
5
+ * Sidebar - Sidebar container component for the goo-ds design system
6
+ *
7
+ * Provides consistent sidebar layout with proper spacing and background.
8
+ * Designed to contain SidebarItem components, titles, and other navigation elements.
9
+ * Token-first per Constitution (no hardcoded values).
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <Sidebar>
14
+ * <CoreText style="headline-xs-bold">Navigation</CoreText>
15
+ * <Spacer size="16" />
16
+ * <SidebarItem icon={<HomeIcon />}>Home</SidebarItem>
17
+ * <SidebarItem icon={<SettingsIcon />}>Settings</SidebarItem>
18
+ * </Sidebar>
19
+ * ```
20
+ */
21
+ export function Sidebar({
22
+ children,
23
+ showDivider = true,
24
+ className = "",
25
+ ...props
26
+ }: SidebarProps) {
27
+ return (
28
+ <div
29
+ className={`gds-sidebar ${className}`.trim()}
30
+ data-component="Sidebar"
31
+ data-show-divider={showDivider}
32
+ {...props}
33
+ >
34
+ <div className="gds-sidebar-content">
35
+ {children}
36
+ </div>
37
+ {showDivider && <div className="gds-sidebar-divider" aria-hidden="true" />}
38
+ </div>
39
+ );
40
+ }
41
+
42
+ export default Sidebar;
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ /**
4
+ * Sidebar component props
5
+ * Provides sidebar container using design system tokens
6
+ */
7
+
8
+ export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
9
+ /**
10
+ * Sidebar content (typically title, SidebarItem components, etc.)
11
+ */
12
+ children?: ReactNode;
13
+
14
+ /**
15
+ * Whether to show the divider on the right edge
16
+ * @default true
17
+ */
18
+ showDivider?: boolean;
19
+
20
+ /**
21
+ * Additional CSS class names
22
+ */
23
+ className?: string;
24
+ }
@@ -0,0 +1,3 @@
1
+ export { Sidebar } from "./Sidebar";
2
+ export type { SidebarProps } from "./Sidebar.types";
3
+ export { default } from "./Sidebar";
@@ -0,0 +1,70 @@
1
+ /**
2
+ * SidebarItem component styles
3
+ * Token-driven sidebar navigation item styling for the goo-ds design system
4
+ */
5
+
6
+ .gds-sidebar-item {
7
+ display: flex;
8
+ align-items: center;
9
+ gap: var(--size-8);
10
+ padding: var(--sidebar-item-medium-paddingVertical) var(--sidebar-item-medium-paddingHorizontal);
11
+ border-radius: var(--sidebar-item-medium-radius);
12
+ font-family: var(--coreText-family-primary);
13
+ font-size: var(--font-size-sm);
14
+ font-weight: var(--font-weight-regular);
15
+ line-height: var(--font-lineHeight-sm);
16
+ color: var(--sidebar-item-text-default);
17
+ transition: all 0.2s ease;
18
+ box-sizing: border-box;
19
+ position: relative;
20
+ width: 100%;
21
+ }
22
+
23
+ /* Interactive items */
24
+ .gds-sidebar-item-interactive {
25
+ cursor: pointer;
26
+ user-select: none;
27
+ }
28
+
29
+ .gds-sidebar-item-interactive:hover:not(.gds-sidebar-item-active) {
30
+ background-color: var(--sidebar-item-background-hover);
31
+ }
32
+
33
+ .gds-sidebar-item-interactive:active:not(.gds-sidebar-item-active) {
34
+ background-color: var(--sidebar-item-background-pressed);
35
+ }
36
+
37
+ .gds-sidebar-item-interactive:focus-visible {
38
+ outline: 2px solid var(--color-stroke-focus);
39
+ outline-offset: 2px;
40
+ }
41
+
42
+ /* Active state */
43
+ .gds-sidebar-item-active {
44
+ background-color: var(--sidebar-item-background-active);
45
+ color: var(--sidebar-item-text-active);
46
+ font-weight: var(--font-weight-medium);
47
+ }
48
+
49
+ /* Icon styling */
50
+ .gds-sidebar-item-icon {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ flex-shrink: 0;
55
+ }
56
+
57
+ /* Text styling */
58
+ .gds-sidebar-item-text {
59
+ flex: 1;
60
+ white-space: nowrap;
61
+ overflow: hidden;
62
+ text-overflow: ellipsis;
63
+ }
64
+
65
+ /* Respect user's motion preferences */
66
+ @media (prefers-reduced-motion: reduce) {
67
+ .gds-sidebar-item {
68
+ transition: none;
69
+ }
70
+ }
@@ -0,0 +1,55 @@
1
+ import type { SidebarItemProps } from "./SidebarItem.types";
2
+ import "./SidebarItem.css";
3
+
4
+ /**
5
+ * SidebarItem - Sidebar navigation item component for the goo-ds design system
6
+ *
7
+ * Provides consistent sidebar navigation item with interactive states.
8
+ * Supports icons and active state indication.
9
+ * Token-first per Constitution (no hardcoded values).
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <SidebarItem>Dashboard</SidebarItem>
14
+ * <SidebarItem active icon={<HomeIcon />}>Home</SidebarItem>
15
+ * <SidebarItem onClick={() => navigate('/settings')}>Settings</SidebarItem>
16
+ * ```
17
+ */
18
+ export function SidebarItem({
19
+ active = false,
20
+ icon,
21
+ children,
22
+ className = "",
23
+ onClick,
24
+ href,
25
+ ...props
26
+ }: SidebarItemProps) {
27
+ const isInteractive = Boolean(onClick || href);
28
+
29
+ const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
30
+ if (onClick) {
31
+ onClick(event);
32
+ }
33
+ if (href && !onClick) {
34
+ window.location.href = href;
35
+ }
36
+ };
37
+
38
+ return (
39
+ <div
40
+ className={`gds-sidebar-item ${active ? "gds-sidebar-item-active" : ""} ${isInteractive ? "gds-sidebar-item-interactive" : ""} ${className}`.trim()}
41
+ onClick={isInteractive ? handleClick : undefined}
42
+ role={isInteractive ? "button" : undefined}
43
+ tabIndex={isInteractive ? 0 : undefined}
44
+ aria-current={active ? "page" : undefined}
45
+ data-component="SidebarItem"
46
+ data-active={active}
47
+ {...props}
48
+ >
49
+ {icon && <span className="gds-sidebar-item-icon">{icon}</span>}
50
+ <span className="gds-sidebar-item-text">{children}</span>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ export default SidebarItem;
@@ -0,0 +1,39 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ /**
4
+ * SidebarItem component props
5
+ * Provides sidebar navigation items using design system tokens
6
+ */
7
+
8
+ export interface SidebarItemProps extends React.HTMLAttributes<HTMLDivElement> {
9
+ /**
10
+ * Whether the item is currently active/selected
11
+ * @default false
12
+ */
13
+ active?: boolean;
14
+
15
+ /**
16
+ * Icon to display before the text
17
+ */
18
+ icon?: ReactNode;
19
+
20
+ /**
21
+ * Item content (text or other elements)
22
+ */
23
+ children?: ReactNode;
24
+
25
+ /**
26
+ * Additional CSS class names
27
+ */
28
+ className?: string;
29
+
30
+ /**
31
+ * Click handler for navigation
32
+ */
33
+ onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
34
+
35
+ /**
36
+ * href for link behavior (optional)
37
+ */
38
+ href?: string;
39
+ }
@@ -0,0 +1,3 @@
1
+ export { SidebarItem } from "./SidebarItem";
2
+ export type { SidebarItemProps } from "./SidebarItem.types";
3
+ export { default } from "./SidebarItem";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Skeleton pulse animation
3
+ * Provides a smooth opacity pulse to indicate loading state
4
+ */
5
+
6
+ @keyframes skeleton-pulse {
7
+ 0%,
8
+ 100% {
9
+ opacity: 1;
10
+ }
11
+ 50% {
12
+ opacity: 0.5;
13
+ }
14
+ }
15
+
16
+ .skeleton-pulse {
17
+ animation: skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
18
+ }
19
+
20
+ /* Respect user's motion preferences */
21
+ @media (prefers-reduced-motion: reduce) {
22
+ .skeleton-pulse {
23
+ animation: none;
24
+ }
25
+ }