@papernote/ui 1.2.0 → 1.3.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 (52) hide show
  1. package/dist/components/Box.d.ts +2 -1
  2. package/dist/components/Box.d.ts.map +1 -1
  3. package/dist/components/Button.d.ts +10 -1
  4. package/dist/components/Button.d.ts.map +1 -1
  5. package/dist/components/Card.d.ts +11 -2
  6. package/dist/components/Card.d.ts.map +1 -1
  7. package/dist/components/DataTable.d.ts +17 -3
  8. package/dist/components/DataTable.d.ts.map +1 -1
  9. package/dist/components/EmptyState.d.ts +3 -1
  10. package/dist/components/EmptyState.d.ts.map +1 -1
  11. package/dist/components/Grid.d.ts +4 -2
  12. package/dist/components/Grid.d.ts.map +1 -1
  13. package/dist/components/Input.d.ts +2 -0
  14. package/dist/components/Input.d.ts.map +1 -1
  15. package/dist/components/MultiSelect.d.ts +13 -1
  16. package/dist/components/MultiSelect.d.ts.map +1 -1
  17. package/dist/components/Stack.d.ts +25 -5
  18. package/dist/components/Stack.d.ts.map +1 -1
  19. package/dist/components/Text.d.ts +20 -4
  20. package/dist/components/Text.d.ts.map +1 -1
  21. package/dist/components/Textarea.d.ts +2 -0
  22. package/dist/components/Textarea.d.ts.map +1 -1
  23. package/dist/components/index.d.ts +1 -3
  24. package/dist/components/index.d.ts.map +1 -1
  25. package/dist/index.d.ts +110 -48
  26. package/dist/index.esm.js +144 -138
  27. package/dist/index.esm.js.map +1 -1
  28. package/dist/index.js +143 -138
  29. package/dist/index.js.map +1 -1
  30. package/dist/styles.css +8 -51
  31. package/package.json +1 -1
  32. package/src/components/Box.stories.tsx +377 -0
  33. package/src/components/Box.tsx +8 -4
  34. package/src/components/Button.tsx +23 -10
  35. package/src/components/Card.tsx +20 -5
  36. package/src/components/DataTable.stories.tsx +36 -25
  37. package/src/components/DataTable.tsx +95 -5
  38. package/src/components/EmptyState.stories.tsx +124 -72
  39. package/src/components/EmptyState.tsx +10 -0
  40. package/src/components/Grid.stories.tsx +348 -0
  41. package/src/components/Grid.tsx +12 -5
  42. package/src/components/Input.tsx +12 -2
  43. package/src/components/MultiSelect.tsx +41 -10
  44. package/src/components/Stack.stories.tsx +24 -1
  45. package/src/components/Stack.tsx +40 -10
  46. package/src/components/Text.stories.tsx +273 -0
  47. package/src/components/Text.tsx +33 -8
  48. package/src/components/Textarea.tsx +32 -21
  49. package/src/components/index.ts +1 -4
  50. package/dist/components/Table.d.ts +0 -26
  51. package/dist/components/Table.d.ts.map +0 -1
  52. package/src/components/Table.tsx +0 -239
package/dist/styles.css CHANGED
@@ -969,49 +969,6 @@ h4{
969
969
  border-color: rgb(231 229 228 / var(--tw-divide-opacity, 1));
970
970
  }
971
971
 
972
- .table-header{
973
- --tw-bg-opacity: 1;
974
- background-color: rgb(250 250 249 / var(--tw-bg-opacity, 1));
975
- }
976
-
977
- .table-header-cell{
978
- padding-left: 1.5rem;
979
- padding-right: 1.5rem;
980
- padding-top: 0.75rem;
981
- padding-bottom: 0.75rem;
982
- text-align: left;
983
- font-size: 0.75rem;
984
- line-height: 1.125rem;
985
- font-weight: 500;
986
- text-transform: uppercase;
987
- letter-spacing: 0.05em;
988
- --tw-text-opacity: 1;
989
- color: rgb(120 113 108 / var(--tw-text-opacity, 1));
990
- }
991
-
992
- .table-cell{
993
- white-space: nowrap;
994
- padding-left: 1.5rem;
995
- padding-right: 1.5rem;
996
- padding-top: 1rem;
997
- padding-bottom: 1rem;
998
- font-size: 0.875rem;
999
- line-height: 1.375rem;
1000
- --tw-text-opacity: 1;
1001
- color: rgb(68 64 60 / var(--tw-text-opacity, 1));
1002
- }
1003
-
1004
- .table-row{
1005
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
1006
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1007
- transition-duration: 150ms;
1008
- }
1009
-
1010
- .table-row:hover{
1011
- --tw-bg-opacity: 1;
1012
- background-color: rgb(250 250 249 / var(--tw-bg-opacity, 1));
1013
- }
1014
-
1015
972
  /* Navigation Styles - Notebook-like */
1016
973
 
1017
974
  /* Switch Toggle Styles - Refined */
@@ -1396,6 +1353,10 @@ input:checked + .slider:before{
1396
1353
  top: 5rem;
1397
1354
  }
1398
1355
 
1356
+ .top-3{
1357
+ top: 0.75rem;
1358
+ }
1359
+
1399
1360
  .top-4{
1400
1361
  top: 1rem;
1401
1362
  }
@@ -1739,14 +1700,6 @@ input:checked + .slider:before{
1739
1700
  display: table;
1740
1701
  }
1741
1702
 
1742
- .table-cell{
1743
- display: table-cell;
1744
- }
1745
-
1746
- .table-row{
1747
- display: table-row;
1748
- }
1749
-
1750
1703
  .grid{
1751
1704
  display: grid;
1752
1705
  }
@@ -1755,6 +1708,10 @@ input:checked + .slider:before{
1755
1708
  display: none;
1756
1709
  }
1757
1710
 
1711
+ .aspect-square{
1712
+ aspect-ratio: 1 / 1;
1713
+ }
1714
+
1758
1715
  .h-0{
1759
1716
  height: 0px;
1760
1717
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papernote/ui",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "A modern React component library with a paper notebook aesthetic - minimal, professional, and expressive",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,377 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useRef, useEffect, useState } from 'react';
3
+ import { Box } from './Box';
4
+ import Stack from './Stack';
5
+ import Text from './Text';
6
+ import Button from './Button';
7
+
8
+ const meta = {
9
+ title: 'Layout/Box',
10
+ component: Box,
11
+ parameters: {
12
+ layout: 'centered',
13
+ docs: {
14
+ description: {
15
+ component: `
16
+ Box is a generic container component with design system spacing and borders.
17
+
18
+ ## Features
19
+ - **Padding**: none, xs, sm, md, lg, xl (with directional variants)
20
+ - **Margin**: none, xs, sm, md, lg, xl, auto (with directional variants)
21
+ - **Border**: none, top, bottom, left, right, all
22
+ - **Border color**: default, primary, accent
23
+ - **Border radius**: none, sm, md, lg, xl, full
24
+ - **Width/Height**: auto, full, fit, screen
25
+ - **Ref forwarding**: Supports refs for DOM access
26
+
27
+ ## Usage
28
+
29
+ \`\`\`tsx
30
+ import { Box } from 'notebook-ui';
31
+
32
+ // Basic container with padding
33
+ <Box padding="md">
34
+ Content here
35
+ </Box>
36
+
37
+ // With border and rounded corners
38
+ <Box padding="lg" border="all" rounded="md">
39
+ Card-like container
40
+ </Box>
41
+
42
+ // With ref for DOM access
43
+ const boxRef = useRef<HTMLDivElement>(null);
44
+ <Box ref={boxRef} padding="md">
45
+ Measurable content
46
+ </Box>
47
+ \`\`\`
48
+ `,
49
+ },
50
+ },
51
+ },
52
+ tags: ['autodocs'],
53
+ argTypes: {
54
+ padding: {
55
+ control: 'select',
56
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
57
+ description: 'Padding on all sides',
58
+ },
59
+ paddingTop: {
60
+ control: 'select',
61
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
62
+ },
63
+ paddingBottom: {
64
+ control: 'select',
65
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
66
+ },
67
+ paddingLeft: {
68
+ control: 'select',
69
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
70
+ },
71
+ paddingRight: {
72
+ control: 'select',
73
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl'],
74
+ },
75
+ margin: {
76
+ control: 'select',
77
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'auto'],
78
+ description: 'Margin on all sides',
79
+ },
80
+ marginTop: {
81
+ control: 'select',
82
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'auto'],
83
+ },
84
+ marginBottom: {
85
+ control: 'select',
86
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'auto'],
87
+ },
88
+ marginLeft: {
89
+ control: 'select',
90
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'auto'],
91
+ },
92
+ marginRight: {
93
+ control: 'select',
94
+ options: ['none', 'xs', 'sm', 'md', 'lg', 'xl', 'auto'],
95
+ },
96
+ border: {
97
+ control: 'select',
98
+ options: ['none', 'top', 'bottom', 'left', 'right', 'all'],
99
+ description: 'Border style',
100
+ },
101
+ borderColor: {
102
+ control: 'select',
103
+ options: ['default', 'primary', 'accent'],
104
+ description: 'Border color',
105
+ },
106
+ rounded: {
107
+ control: 'select',
108
+ options: ['none', 'sm', 'md', 'lg', 'xl', 'full'],
109
+ description: 'Border radius',
110
+ },
111
+ width: {
112
+ control: 'select',
113
+ options: [undefined, 'auto', 'full', 'fit', 'screen'],
114
+ description: 'Width',
115
+ },
116
+ height: {
117
+ control: 'select',
118
+ options: [undefined, 'auto', 'full', 'screen'],
119
+ description: 'Height',
120
+ },
121
+ },
122
+ } satisfies Meta<typeof Box>;
123
+
124
+ export default meta;
125
+ type Story = StoryObj<typeof meta>;
126
+
127
+ export const Default: Story = {
128
+ args: {
129
+ padding: 'md',
130
+ children: 'Basic box with medium padding',
131
+ },
132
+ };
133
+
134
+ export const Padding: Story = {
135
+ render: () => (
136
+ <Stack spacing="md">
137
+ <Box padding="xs" className="bg-paper-100">
138
+ <Text size="sm">padding="xs"</Text>
139
+ </Box>
140
+ <Box padding="sm" className="bg-paper-100">
141
+ <Text size="sm">padding="sm"</Text>
142
+ </Box>
143
+ <Box padding="md" className="bg-paper-100">
144
+ <Text size="sm">padding="md"</Text>
145
+ </Box>
146
+ <Box padding="lg" className="bg-paper-100">
147
+ <Text size="sm">padding="lg"</Text>
148
+ </Box>
149
+ <Box padding="xl" className="bg-paper-100">
150
+ <Text size="sm">padding="xl"</Text>
151
+ </Box>
152
+ </Stack>
153
+ ),
154
+ };
155
+
156
+ export const DirectionalPadding: Story = {
157
+ render: () => (
158
+ <Stack spacing="md">
159
+ <Box paddingTop="lg" className="bg-paper-100">
160
+ <Text size="sm">paddingTop="lg"</Text>
161
+ </Box>
162
+ <Box paddingBottom="lg" className="bg-paper-100">
163
+ <Text size="sm">paddingBottom="lg"</Text>
164
+ </Box>
165
+ <Box paddingLeft="lg" className="bg-paper-100">
166
+ <Text size="sm">paddingLeft="lg"</Text>
167
+ </Box>
168
+ <Box paddingRight="lg" className="bg-paper-100">
169
+ <Text size="sm">paddingRight="lg"</Text>
170
+ </Box>
171
+ </Stack>
172
+ ),
173
+ };
174
+
175
+ export const WithBorder: Story = {
176
+ render: () => (
177
+ <Stack spacing="md">
178
+ <Box padding="md" border="all">
179
+ <Text size="sm">border="all"</Text>
180
+ </Box>
181
+ <Box padding="md" border="top">
182
+ <Text size="sm">border="top"</Text>
183
+ </Box>
184
+ <Box padding="md" border="bottom">
185
+ <Text size="sm">border="bottom"</Text>
186
+ </Box>
187
+ <Box padding="md" border="left">
188
+ <Text size="sm">border="left"</Text>
189
+ </Box>
190
+ <Box padding="md" border="right">
191
+ <Text size="sm">border="right"</Text>
192
+ </Box>
193
+ </Stack>
194
+ ),
195
+ };
196
+
197
+ export const BorderColors: Story = {
198
+ render: () => (
199
+ <Stack spacing="md">
200
+ <Box padding="md" border="all" borderColor="default">
201
+ <Text size="sm">borderColor="default"</Text>
202
+ </Box>
203
+ <Box padding="md" border="all" borderColor="primary">
204
+ <Text size="sm">borderColor="primary"</Text>
205
+ </Box>
206
+ <Box padding="md" border="all" borderColor="accent">
207
+ <Text size="sm">borderColor="accent"</Text>
208
+ </Box>
209
+ </Stack>
210
+ ),
211
+ };
212
+
213
+ export const Rounded: Story = {
214
+ render: () => (
215
+ <Stack spacing="md">
216
+ <Box padding="md" border="all" rounded="none">
217
+ <Text size="sm">rounded="none"</Text>
218
+ </Box>
219
+ <Box padding="md" border="all" rounded="sm">
220
+ <Text size="sm">rounded="sm"</Text>
221
+ </Box>
222
+ <Box padding="md" border="all" rounded="md">
223
+ <Text size="sm">rounded="md"</Text>
224
+ </Box>
225
+ <Box padding="md" border="all" rounded="lg">
226
+ <Text size="sm">rounded="lg"</Text>
227
+ </Box>
228
+ <Box padding="md" border="all" rounded="xl">
229
+ <Text size="sm">rounded="xl"</Text>
230
+ </Box>
231
+ <Box padding="md" border="all" rounded="full" style={{ width: '150px', textAlign: 'center' }}>
232
+ <Text size="sm">rounded="full"</Text>
233
+ </Box>
234
+ </Stack>
235
+ ),
236
+ };
237
+
238
+ export const CardLike: Story = {
239
+ render: () => (
240
+ <Box padding="lg" border="all" rounded="lg" className="bg-white shadow-sm" style={{ width: '300px' }}>
241
+ <Stack spacing="sm">
242
+ <Text as="h3" size="lg" weight="semibold">Card Title</Text>
243
+ <Text color="secondary">
244
+ This box is styled to look like a card with padding, border, and rounded corners.
245
+ </Text>
246
+ <Box marginTop="sm">
247
+ <Button variant="primary" size="sm">Action</Button>
248
+ </Box>
249
+ </Stack>
250
+ </Box>
251
+ ),
252
+ };
253
+
254
+ export const CenteredWithMargin: Story = {
255
+ render: () => (
256
+ <div style={{ width: '400px', border: '1px dashed #ccc' }}>
257
+ <Box
258
+ padding="md"
259
+ margin="auto"
260
+ border="all"
261
+ rounded="md"
262
+ style={{ width: 'fit-content' }}
263
+ >
264
+ <Text>Centered with margin="auto"</Text>
265
+ </Box>
266
+ </div>
267
+ ),
268
+ };
269
+
270
+ /**
271
+ * Box supports ref forwarding, allowing you to access the underlying DOM element.
272
+ * This is useful for measuring dimensions, handling scroll, or integrating with
273
+ * third-party libraries that need DOM access.
274
+ */
275
+ export const WithRef: Story = {
276
+ render: function RefExample() {
277
+ const boxRef = useRef<HTMLDivElement>(null);
278
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
279
+
280
+ useEffect(() => {
281
+ if (boxRef.current) {
282
+ const { offsetWidth, offsetHeight } = boxRef.current;
283
+ setDimensions({ width: offsetWidth, height: offsetHeight });
284
+ }
285
+ }, []);
286
+
287
+ return (
288
+ <Stack spacing="md">
289
+ <Box
290
+ ref={boxRef}
291
+ padding="lg"
292
+ border="all"
293
+ rounded="md"
294
+ className="bg-paper-50"
295
+ >
296
+ <Text>This box's dimensions are measured using a ref</Text>
297
+ </Box>
298
+ <Box padding="sm" border="all" rounded="sm" className="bg-success-50">
299
+ <Text size="sm" color="success">
300
+ Measured: {dimensions.width}px x {dimensions.height}px
301
+ </Text>
302
+ </Box>
303
+ </Stack>
304
+ );
305
+ },
306
+ };
307
+
308
+ /**
309
+ * Another ref example showing focus management.
310
+ */
311
+ export const RefForFocus: Story = {
312
+ render: function FocusExample() {
313
+ const boxRef = useRef<HTMLDivElement>(null);
314
+ const [isFocused, setIsFocused] = useState(false);
315
+
316
+ const handleFocus = () => {
317
+ if (boxRef.current) {
318
+ boxRef.current.focus();
319
+ setIsFocused(true);
320
+ }
321
+ };
322
+
323
+ return (
324
+ <Stack spacing="md">
325
+ <Box
326
+ ref={boxRef}
327
+ tabIndex={0}
328
+ padding="lg"
329
+ border="all"
330
+ borderColor={isFocused ? 'accent' : 'default'}
331
+ rounded="md"
332
+ onFocus={() => setIsFocused(true)}
333
+ onBlur={() => setIsFocused(false)}
334
+ className="transition-colors outline-none focus:ring-2 focus:ring-accent-400"
335
+ >
336
+ <Text>Click the button or press Tab to focus this box</Text>
337
+ </Box>
338
+ <Button variant="secondary" onClick={handleFocus}>
339
+ Focus the Box
340
+ </Button>
341
+ <Text size="sm" color="muted">
342
+ Status: {isFocused ? 'Focused' : 'Not focused'}
343
+ </Text>
344
+ </Stack>
345
+ );
346
+ },
347
+ };
348
+
349
+ export const NestedBoxes: Story = {
350
+ render: () => (
351
+ <Box padding="lg" border="all" rounded="lg" className="bg-paper-50">
352
+ <Text weight="semibold" size="lg">Outer Box</Text>
353
+ <Box padding="md" marginTop="md" border="all" rounded="md" className="bg-white">
354
+ <Text>Inner Box 1</Text>
355
+ </Box>
356
+ <Box padding="md" marginTop="md" border="all" rounded="md" className="bg-white">
357
+ <Text>Inner Box 2</Text>
358
+ </Box>
359
+ </Box>
360
+ ),
361
+ };
362
+
363
+ export const WidthOptions: Story = {
364
+ render: () => (
365
+ <Stack spacing="md" style={{ width: '400px' }}>
366
+ <Box padding="sm" border="all" width="auto">
367
+ <Text size="sm">width="auto" (shrinks to content)</Text>
368
+ </Box>
369
+ <Box padding="sm" border="all" width="full">
370
+ <Text size="sm">width="full" (100% of parent)</Text>
371
+ </Box>
372
+ <Box padding="sm" border="all" width="fit">
373
+ <Text size="sm">width="fit"</Text>
374
+ </Box>
375
+ </Stack>
376
+ ),
377
+ };
@@ -1,7 +1,7 @@
1
1
  // Box Component - Generic container with design system styling
2
2
  // Provides consistent padding, borders, and other container styles
3
3
 
4
- import React from 'react';
4
+ import React, { forwardRef } from 'react';
5
5
 
6
6
  export interface BoxProps extends React.HTMLAttributes<HTMLDivElement> {
7
7
  /** Content */
@@ -42,8 +42,9 @@ export interface BoxProps extends React.HTMLAttributes<HTMLDivElement> {
42
42
 
43
43
  /**
44
44
  * Box component for generic containers with design system spacing and borders.
45
+ * Supports ref forwarding for DOM access.
45
46
  */
46
- export const Box: React.FC<BoxProps> = ({
47
+ export const Box = forwardRef<HTMLDivElement, BoxProps>(({
47
48
  children,
48
49
  padding,
49
50
  paddingTop,
@@ -62,7 +63,7 @@ export const Box: React.FC<BoxProps> = ({
62
63
  height,
63
64
  className = '',
64
65
  ...htmlProps
65
- }) => {
66
+ }, ref) => {
66
67
  const spacingMap = {
67
68
  none: '0',
68
69
  xs: '2',
@@ -148,6 +149,7 @@ export const Box: React.FC<BoxProps> = ({
148
149
 
149
150
  return (
150
151
  <div
152
+ ref={ref}
151
153
  {...htmlProps}
152
154
  className={`
153
155
  ${getPaddingClass()}
@@ -163,6 +165,8 @@ export const Box: React.FC<BoxProps> = ({
163
165
  {children}
164
166
  </div>
165
167
  );
166
- };
168
+ });
169
+
170
+ Box.displayName = 'Box';
167
171
 
168
172
  export default Box;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { forwardRef } from 'react';
2
2
  import { Loader2 } from 'lucide-react';
3
3
 
4
4
  /**
@@ -31,6 +31,8 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
31
31
  * A versatile button component that supports multiple visual styles, sizes, icons,
32
32
  * loading states, and notification badges.
33
33
  *
34
+ * Supports ref forwarding for DOM access.
35
+ *
34
36
  * @example Basic usage
35
37
  * ```tsx
36
38
  * <Button variant="primary">Click me</Button>
@@ -38,9 +40,9 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
38
40
  *
39
41
  * @example With icon and loading
40
42
  * ```tsx
41
- * <Button
42
- * variant="secondary"
43
- * icon={<Save />}
43
+ * <Button
44
+ * variant="secondary"
45
+ * icon={<Save />}
44
46
  * loading={isSaving}
45
47
  * >
46
48
  * Save Changes
@@ -49,16 +51,22 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
49
51
  *
50
52
  * @example Icon-only with badge
51
53
  * ```tsx
52
- * <Button
53
- * iconOnly
54
- * badge={5}
54
+ * <Button
55
+ * iconOnly
56
+ * badge={5}
55
57
  * badgeVariant="error"
56
58
  * >
57
59
  * <Bell />
58
60
  * </Button>
59
61
  * ```
62
+ *
63
+ * @example With ref
64
+ * ```tsx
65
+ * const buttonRef = useRef<HTMLButtonElement>(null);
66
+ * <Button ref={buttonRef}>Focusable</Button>
67
+ * ```
60
68
  */
61
- export default function Button({
69
+ const Button = forwardRef<HTMLButtonElement, ButtonProps>(({
62
70
  variant = 'primary',
63
71
  size = 'md',
64
72
  loading = false,
@@ -72,7 +80,7 @@ export default function Button({
72
80
  disabled,
73
81
  className = '',
74
82
  ...props
75
- }: ButtonProps) {
83
+ }, ref) => {
76
84
  const baseStyles = 'inline-flex items-center justify-center font-medium rounded-lg border transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-400 disabled:opacity-40 disabled:cursor-not-allowed';
77
85
 
78
86
  const variantStyles = {
@@ -111,6 +119,7 @@ export default function Button({
111
119
 
112
120
  const buttonElement = (
113
121
  <button
122
+ ref={ref}
114
123
  className={`
115
124
  ${baseStyles}
116
125
  ${variantStyles[variant]}
@@ -163,4 +172,8 @@ export default function Button({
163
172
  </span>
164
173
  </div>
165
174
  );
166
- }
175
+ });
176
+
177
+ Button.displayName = 'Button';
178
+
179
+ export default Button;
@@ -1,10 +1,10 @@
1
- import React from 'react';
1
+ import React, { forwardRef } from 'react';
2
2
  import { Skeleton } from './Loading';
3
3
 
4
4
  /**
5
5
  * Card component props
6
6
  */
7
- export interface CardProps {
7
+ export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onClick'> {
8
8
  /** Card content */
9
9
  children: React.ReactNode;
10
10
  /** Visual style variant affecting padding and shadow */
@@ -24,6 +24,8 @@ export interface CardProps {
24
24
  /**
25
25
  * Card - Container component with paper aesthetic and subtle shadow
26
26
  *
27
+ * Supports ref forwarding for DOM access.
28
+ *
27
29
  * A content container with paper texture, border, and shadow effects. Supports
28
30
  * different sizes, variants (padding/shadow levels), and loading states.
29
31
  *
@@ -53,8 +55,14 @@ export interface CardProps {
53
55
  * <p>Content</p>
54
56
  * </Card>
55
57
  * ```
58
+ *
59
+ * @example With ref
60
+ * ```tsx
61
+ * const cardRef = useRef<HTMLDivElement>(null);
62
+ * <Card ref={cardRef}>Content</Card>
63
+ * ```
56
64
  */
57
- export default function Card({
65
+ const Card = forwardRef<HTMLDivElement, CardProps>(({
58
66
  children,
59
67
  variant = 'default',
60
68
  width = 'auto',
@@ -62,7 +70,8 @@ export default function Card({
62
70
  onClick,
63
71
  hoverable = false,
64
72
  loading = false,
65
- }: CardProps) {
73
+ ...htmlProps
74
+ }, ref) => {
66
75
  const baseStyles = 'bg-white bg-subtle-grain border-2 border-paper-300 transition-shadow duration-200';
67
76
 
68
77
  const variantStyles = {
@@ -84,6 +93,8 @@ export default function Card({
84
93
 
85
94
  return (
86
95
  <div
96
+ ref={ref}
97
+ {...htmlProps}
87
98
  className={`${baseStyles} ${variantStyles[variant]} ${widthStyles[width]} ${interactiveStyles} ${className}`}
88
99
  onClick={!loading ? onClick : undefined}
89
100
  role={onClick ? 'button' : undefined}
@@ -101,7 +112,11 @@ export default function Card({
101
112
  )}
102
113
  </div>
103
114
  );
104
- }
115
+ });
116
+
117
+ Card.displayName = 'Card';
118
+
119
+ export default Card;
105
120
 
106
121
  /**
107
122
  * CardHeader component props