laif-ds 0.2.75 → 0.2.77
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.
- package/dist/CHANGELOG.md +27 -0
- package/dist/_virtual/index6.js +2 -2
- package/dist/_virtual/index7.js +2 -2
- package/dist/agent-docs/adoption-report.json +43 -39
- package/dist/agent-docs/components/AppCard.md +308 -0
- package/dist/agent-docs/components/AppForm.md +169 -358
- package/dist/agent-docs/components/DataTable.md +3 -0
- package/dist/agent-docs/components/FileUploader.md +7 -1
- package/dist/agent-docs/components-list.md +2 -0
- package/dist/agent-docs/manifest.json +110 -27
- package/dist/agent-docs/truncated-cell.md +342 -0
- package/dist/components/ui/app-card.js +115 -0
- package/dist/components/ui/app-form.js +322 -289
- package/dist/components/ui/card.js +20 -6
- package/dist/components/ui/file-uploader.js +86 -78
- package/dist/components/ui/tables/data-table/components/data-table-body.js +123 -115
- package/dist/components/ui/tables/data-table/components/data-table-header.js +6 -5
- package/dist/components/ui/tables/data-table/data-table.js +71 -69
- package/dist/components/ui/tables/data-table/data-table.utils.js +23 -15
- package/dist/components/ui/truncated-cell.js +100 -0
- package/dist/index.d.ts +212 -11
- package/dist/index.js +377 -373
- package/dist/node_modules/eventemitter3/index2.js +1 -1
- package/dist/node_modules/style-to-object/cjs/index.js +1 -1
- package/dist/styles.v3.css +1 -1
- package/package.json +1 -1
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.1.0",
|
|
3
|
-
"generatedAt": "2026-03-
|
|
3
|
+
"generatedAt": "2026-03-24T15:46:53.701Z",
|
|
4
4
|
"package": {
|
|
5
5
|
"name": "laif-ds",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.77"
|
|
7
7
|
},
|
|
8
8
|
"stats": {
|
|
9
|
-
"documentedComponentCount":
|
|
10
|
-
"catalogedComponentCount":
|
|
11
|
-
"missingFromDocsCount":
|
|
9
|
+
"documentedComponentCount": 92,
|
|
10
|
+
"catalogedComponentCount": 92,
|
|
11
|
+
"missingFromDocsCount": 1,
|
|
12
12
|
"deprecatedComponentCount": 2,
|
|
13
|
-
"averageAiReadinessScore": 54.
|
|
14
|
-
"highAiReadinessCount":
|
|
13
|
+
"averageAiReadinessScore": 54.73,
|
|
14
|
+
"highAiReadinessCount": 36,
|
|
15
15
|
"lowAiReadinessCount": 42
|
|
16
16
|
},
|
|
17
|
-
"missingFromDocs": [
|
|
17
|
+
"missingFromDocs": [
|
|
18
|
+
"TruncatedCell"
|
|
19
|
+
],
|
|
18
20
|
"components": [
|
|
19
21
|
{
|
|
20
22
|
"name": "Accordion",
|
|
@@ -246,6 +248,92 @@
|
|
|
246
248
|
"type AlertDialogTriggerProps = React_2.ComponentProps<typeof AlertDialogPrimitive.Trigger>;\r"
|
|
247
249
|
]
|
|
248
250
|
},
|
|
251
|
+
{
|
|
252
|
+
"name": "AppCard",
|
|
253
|
+
"markdownPath": "components/AppCard.md",
|
|
254
|
+
"importPath": "laif-ds",
|
|
255
|
+
"deprecated": false,
|
|
256
|
+
"replacement": null,
|
|
257
|
+
"tags": {
|
|
258
|
+
"cataloged": true,
|
|
259
|
+
"aiReady": true
|
|
260
|
+
},
|
|
261
|
+
"docs": {
|
|
262
|
+
"sections": [
|
|
263
|
+
{
|
|
264
|
+
"title": "Overview",
|
|
265
|
+
"slug": "overview"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"title": "Props",
|
|
269
|
+
"slug": "props"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"title": "Exports",
|
|
273
|
+
"slug": "exports"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"title": "Behavior",
|
|
277
|
+
"slug": "behavior"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"title": "Examples",
|
|
281
|
+
"slug": "examples"
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"title": "Notes",
|
|
285
|
+
"slug": "notes"
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
"hasPropsTable": true,
|
|
289
|
+
"hasExamples": true,
|
|
290
|
+
"exampleTitles": [
|
|
291
|
+
"AppCardProps",
|
|
292
|
+
"Variant Reference",
|
|
293
|
+
"Size Reference",
|
|
294
|
+
"Basic",
|
|
295
|
+
"Semantic Variants",
|
|
296
|
+
"Interactive Card with onClick",
|
|
297
|
+
"Loading State",
|
|
298
|
+
"Nested Flat Cards",
|
|
299
|
+
"Size Variants",
|
|
300
|
+
"Children as Fallback Body",
|
|
301
|
+
"Advanced Composition with Sub-components"
|
|
302
|
+
],
|
|
303
|
+
"controlledPattern": "likely-controlled",
|
|
304
|
+
"requiredProps": []
|
|
305
|
+
},
|
|
306
|
+
"metadata": {
|
|
307
|
+
"props": {
|
|
308
|
+
"totalProps": 23,
|
|
309
|
+
"requiredPropsCount": 0,
|
|
310
|
+
"typedPropsCount": 23,
|
|
311
|
+
"describedPropsCount": 23
|
|
312
|
+
},
|
|
313
|
+
"accessibility": {
|
|
314
|
+
"hasCoverage": true
|
|
315
|
+
},
|
|
316
|
+
"states": {
|
|
317
|
+
"coveredStates": [
|
|
318
|
+
"hover",
|
|
319
|
+
"focus",
|
|
320
|
+
"loading",
|
|
321
|
+
"error",
|
|
322
|
+
"empty"
|
|
323
|
+
],
|
|
324
|
+
"coveredStateCount": 5
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
"aiReadiness": {
|
|
328
|
+
"score": 100,
|
|
329
|
+
"tier": "high"
|
|
330
|
+
},
|
|
331
|
+
"typeReferences": [
|
|
332
|
+
"interface AppCardProps extends Omit<React_2.ComponentProps<\"div\">, \"title\"> {\r",
|
|
333
|
+
"type AppCardSize = \"sm\" | \"default\" | \"lg\" | \"none\";\r",
|
|
334
|
+
"type AppCardVariant = \"default\" | \"elevated\" | \"outlined\" | \"ghost\" | \"flat\" | \"interactive\" | \"success\" | \"warning\" | \"destructive\" | \"info\";\r"
|
|
335
|
+
]
|
|
336
|
+
},
|
|
249
337
|
{
|
|
250
338
|
"name": "AppCheckbox",
|
|
251
339
|
"markdownPath": "components/AppCheckbox.md",
|
|
@@ -522,19 +610,13 @@
|
|
|
522
610
|
"AppFormItem",
|
|
523
611
|
"Submit Button Inside vs Outside",
|
|
524
612
|
"input",
|
|
525
|
-
"
|
|
526
|
-
"
|
|
527
|
-
"multiselect",
|
|
613
|
+
"select / multiselect",
|
|
614
|
+
"custom",
|
|
528
615
|
"async / async-multiple",
|
|
529
616
|
"datepicker",
|
|
530
|
-
"radio",
|
|
531
|
-
"
|
|
532
|
-
"
|
|
533
|
-
"slider",
|
|
534
|
-
"Basic Form",
|
|
535
|
-
"Full Feature Form",
|
|
536
|
-
"With Date Range",
|
|
537
|
-
"External Form Control"
|
|
617
|
+
"radio / checkbox / switch / slider",
|
|
618
|
+
"Grid and Icons Example",
|
|
619
|
+
"Advanced componentProps"
|
|
538
620
|
],
|
|
539
621
|
"controlledPattern": "likely-controlled",
|
|
540
622
|
"requiredProps": [
|
|
@@ -557,10 +639,9 @@
|
|
|
557
639
|
"disabled",
|
|
558
640
|
"loading",
|
|
559
641
|
"error",
|
|
560
|
-
"empty",
|
|
561
642
|
"selected"
|
|
562
643
|
],
|
|
563
|
-
"coveredStateCount":
|
|
644
|
+
"coveredStateCount": 4
|
|
564
645
|
}
|
|
565
646
|
},
|
|
566
647
|
"aiReadiness": {
|
|
@@ -568,8 +649,10 @@
|
|
|
568
649
|
"tier": "high"
|
|
569
650
|
},
|
|
570
651
|
"typeReferences": [
|
|
571
|
-
"interface
|
|
572
|
-
"interface AppFormProps<TFormValues extends FieldValues = FieldValues, TAsyncOption = unknown> {\r"
|
|
652
|
+
"interface AppFormItemComponentProps<TAsyncOption = unknown> {\r",
|
|
653
|
+
"interface AppFormProps<TFormValues extends FieldValues = FieldValues, TAsyncOption = unknown> {\r",
|
|
654
|
+
"type AppFormComponentType = \"input\" | \"select\" | \"textarea\" | \"checkbox\" | \"multiselect\" | \"datepicker\" | \"radio\" | \"switch\" | \"slider\" | \"async\" | \"async-multiple\" | \"custom\";\r",
|
|
655
|
+
"type AppFormItem<TAsyncOption = unknown, TComponent extends AppFormComponentType = AppFormComponentType> = {\r"
|
|
573
656
|
]
|
|
574
657
|
},
|
|
575
658
|
{
|
|
@@ -2585,10 +2668,10 @@
|
|
|
2585
2668
|
},
|
|
2586
2669
|
"metadata": {
|
|
2587
2670
|
"props": {
|
|
2588
|
-
"totalProps":
|
|
2671
|
+
"totalProps": 23,
|
|
2589
2672
|
"requiredPropsCount": 0,
|
|
2590
|
-
"typedPropsCount":
|
|
2591
|
-
"describedPropsCount":
|
|
2673
|
+
"typedPropsCount": 23,
|
|
2674
|
+
"describedPropsCount": 23
|
|
2592
2675
|
},
|
|
2593
2676
|
"accessibility": {
|
|
2594
2677
|
"hasCoverage": true
|
|
@@ -5975,5 +6058,5 @@
|
|
|
5975
6058
|
]
|
|
5976
6059
|
}
|
|
5977
6060
|
],
|
|
5978
|
-
"checksum": "
|
|
6061
|
+
"checksum": "04503a20ecd4bab54c8394f25ac6f42427d60e457070009b8248a22c8747a5de"
|
|
5979
6062
|
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# TruncatedCell
|
|
2
|
+
|
|
3
|
+
The TruncatedCell component is designed to display text that may be too long for its container. It automatically detects when text is truncated and provides a popover to show the full content.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Flexible Content**: Accepts plain text or rich ReactNode content
|
|
8
|
+
- **Automatic Truncation Detection**: Uses scrollWidth vs clientWidth comparison to detect if content is truncated
|
|
9
|
+
- **Responsive**: Re-checks truncation on resize using ResizeObserver for better performance
|
|
10
|
+
- **Controlled Popover**: Manages popover state programmatically for better UX
|
|
11
|
+
- **Eye Icon Hint**: Displays an eye icon hint only when content is truncated, with smooth fade-in animation
|
|
12
|
+
- **Button Visibility Control**: Optional `showButton` prop to control eye button visibility
|
|
13
|
+
- **Custom Popover Styling**: `popoverClassName` prop for custom popover content styling
|
|
14
|
+
- **Keyboard Accessibility**: Full keyboard navigation support (Enter/Space to open popover)
|
|
15
|
+
- **Screen Reader Support**: Proper ARIA labels and roles for accessibility
|
|
16
|
+
- **Empty State**: Shows "-" with muted styling when no content is provided
|
|
17
|
+
- **Custom Empty Fallback**: Supports a custom placeholder for empty values
|
|
18
|
+
- **Customizable Styling**: Supports custom CSS classes for container, cell, and popover
|
|
19
|
+
- **Dialog Title**: Optional title for the popover header
|
|
20
|
+
- **Custom Content**: Full JSX support for custom popover content
|
|
21
|
+
- **Hover Effects**: Smooth underline on content hover and button appearance
|
|
22
|
+
- **Scrollable Content**: Long content in popover is scrollable with max height
|
|
23
|
+
- **DX-first API**: Rich children work out of the box, and `text` is only needed when you want explicit popover/accessibility text
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { TruncatedCell } from "laif-ds";
|
|
29
|
+
|
|
30
|
+
// Basic usage with string content
|
|
31
|
+
<TruncatedCell text="Short text" />
|
|
32
|
+
|
|
33
|
+
// Long text that will be truncated
|
|
34
|
+
<TruncatedCell
|
|
35
|
+
text="This is a very long text that will be truncated because it exceeds the container width"
|
|
36
|
+
wrapperClassName="w-56"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
// String children also work without text
|
|
40
|
+
<TruncatedCell wrapperClassName="w-56">
|
|
41
|
+
This is a string content
|
|
42
|
+
</TruncatedCell>
|
|
43
|
+
|
|
44
|
+
// Rich content works without duplicating text
|
|
45
|
+
<TruncatedCell wrapperClassName="w-56">
|
|
46
|
+
<div className="flex items-center gap-2">
|
|
47
|
+
<Avatar src="/avatar.jpg" />
|
|
48
|
+
<div className="min-w-0">
|
|
49
|
+
<p className="font-semibold">John Doe</p>
|
|
50
|
+
<p className="text-sm text-gray-600">john@example.com</p>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</TruncatedCell>
|
|
54
|
+
|
|
55
|
+
// Provide text when you want explicit popover or accessibility content
|
|
56
|
+
<TruncatedCell
|
|
57
|
+
text="John Doe - john@example.com"
|
|
58
|
+
title="User details"
|
|
59
|
+
wrapperClassName="w-56"
|
|
60
|
+
>
|
|
61
|
+
<UserSummary />
|
|
62
|
+
</TruncatedCell>
|
|
63
|
+
|
|
64
|
+
// With custom styling and title
|
|
65
|
+
<TruncatedCell
|
|
66
|
+
text="Custom styled truncated text"
|
|
67
|
+
title="Full Content"
|
|
68
|
+
wrapperClassName="w-40"
|
|
69
|
+
className="max-w-40"
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
// With custom popover content and styling
|
|
73
|
+
<TruncatedCell
|
|
74
|
+
text="Text with custom popover content"
|
|
75
|
+
title="Custom Content"
|
|
76
|
+
popoverContent={
|
|
77
|
+
<div className="space-y-3">
|
|
78
|
+
<p>Custom JSX content here</p>
|
|
79
|
+
<button>Action Button</button>
|
|
80
|
+
</div>
|
|
81
|
+
}
|
|
82
|
+
popoverClassName="max-w-md"
|
|
83
|
+
showButton={true}
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
// Hide the eye hint
|
|
87
|
+
<TruncatedCell
|
|
88
|
+
text="Text without eye button"
|
|
89
|
+
showButton={false}
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
// Empty text
|
|
93
|
+
<TruncatedCell text="" />
|
|
94
|
+
|
|
95
|
+
// Custom empty fallback
|
|
96
|
+
<TruncatedCell text="" emptyFallback="No value" />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Props
|
|
100
|
+
|
|
101
|
+
| Prop | Type | Default | Description |
|
|
102
|
+
| ------------------ | ----------- | ----------- | ---------------------------------------------------------------------------- |
|
|
103
|
+
| `children` | `ReactNode` | `undefined` | Content to display |
|
|
104
|
+
| `text` | `string` | `undefined` | Optional text used for truncation detection, accessibility, and popover copy |
|
|
105
|
+
| `title` | `string` | `undefined` | Optional title for the popover dialog |
|
|
106
|
+
| `popoverContent` | `ReactNode` | `undefined` | Custom popover content (JSX) |
|
|
107
|
+
| `wrapperClassName` | `string` | `""` | Preferred CSS classes for the wrapper container |
|
|
108
|
+
| `className` | `string` | `""` | CSS classes for the cell content |
|
|
109
|
+
| `popoverClassName` | `string` | `""` | CSS classes for the popover content |
|
|
110
|
+
| `showButton` | `boolean` | `true` | Flag used to show/hide the eye hint |
|
|
111
|
+
| `emptyFallback` | `ReactNode` | `"-"` | Custom placeholder rendered when no content is available |
|
|
112
|
+
|
|
113
|
+
## Behavior
|
|
114
|
+
|
|
115
|
+
### Truncation Detection
|
|
116
|
+
|
|
117
|
+
- Component monitors the text element's scrollWidth vs clientWidth
|
|
118
|
+
- If scrollWidth > clientWidth, the text is considered truncated
|
|
119
|
+
- Detection runs on mount and on resize
|
|
120
|
+
- When `text` is not provided, the component also extracts `textContent` from the rendered node to power the default popover content
|
|
121
|
+
|
|
122
|
+
### Visual States
|
|
123
|
+
|
|
124
|
+
1. **Normal State**: Text is displayed with truncation (CSS `truncate`)
|
|
125
|
+
2. **Truncated State**: Text is truncated + eye icon hint appears
|
|
126
|
+
3. **Empty State**: Shows "-" when text is empty/undefined
|
|
127
|
+
4. **Popover State**: Opens when there is content to inspect, and the eye icon hint appears only when the rendered value is truncated
|
|
128
|
+
|
|
129
|
+
### Styling
|
|
130
|
+
|
|
131
|
+
- Container: `flex max-w-60 gap-2` + custom classes
|
|
132
|
+
- Text container: `flex min-w-0 flex-1`
|
|
133
|
+
- Text element: `w-full min-w-0 truncate whitespace-nowrap`
|
|
134
|
+
- Popover: `w-96` with `whitespace-pre-wrap` for multiline support
|
|
135
|
+
|
|
136
|
+
### Content Types
|
|
137
|
+
|
|
138
|
+
The TruncatedCell component supports two main usage patterns:
|
|
139
|
+
|
|
140
|
+
#### 1. String Content (Simple)
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
// Direct text prop
|
|
144
|
+
<TruncatedCell text="Simple text content" />
|
|
145
|
+
|
|
146
|
+
// String children (text prop optional)
|
|
147
|
+
<TruncatedCell wrapperClassName="w-56">
|
|
148
|
+
String content as children
|
|
149
|
+
</TruncatedCell>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### 2. ReactNode Content (Advanced)
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
// Rich children work without text
|
|
156
|
+
<TruncatedCell wrapperClassName="w-56">
|
|
157
|
+
<div className="flex items-center gap-2">
|
|
158
|
+
<Avatar src="/avatar.jpg" />
|
|
159
|
+
<div className="min-w-0">
|
|
160
|
+
<p className="font-semibold">John Doe</p>
|
|
161
|
+
<p className="text-sm text-gray-600">john@example.com</p>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</TruncatedCell>
|
|
165
|
+
|
|
166
|
+
// Add text when you want explicit popover content
|
|
167
|
+
<TruncatedCell text="User profile - John Doe" wrapperClassName="w-56">
|
|
168
|
+
<UserSummary />
|
|
169
|
+
</TruncatedCell>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Choosing Between `children` and `text`
|
|
173
|
+
|
|
174
|
+
- Use `text` for the simplest plain-text case
|
|
175
|
+
- Use `children` when you need custom rendering inside the truncated area
|
|
176
|
+
- Add `text` together with `children` when the rendered content is visual or abbreviated and you want more descriptive text in the popover
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
// Plain text
|
|
180
|
+
<TruncatedCell text="Contract pending signature" />
|
|
181
|
+
|
|
182
|
+
// Rich UI with automatic text extraction
|
|
183
|
+
<TruncatedCell wrapperClassName="w-56">
|
|
184
|
+
<div>Complex content</div>
|
|
185
|
+
</TruncatedCell>
|
|
186
|
+
|
|
187
|
+
// Rich UI with explicit popover and a11y text
|
|
188
|
+
<TruncatedCell text="Complex content description" wrapperClassName="w-56">
|
|
189
|
+
<div>Complex content</div>
|
|
190
|
+
</TruncatedCell>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Custom Popover Content
|
|
194
|
+
|
|
195
|
+
The `popoverContent` prop allows you to override the default text display with custom JSX content:
|
|
196
|
+
|
|
197
|
+
- **Full Flexibility**: Any React components can be used as popover content
|
|
198
|
+
- **Backward Compatible**: When not provided, defaults to displaying the text with Typo component
|
|
199
|
+
- **Title Support**: Works seamlessly with the `title` prop for popover headers
|
|
200
|
+
- **Custom Styling**: Use `popoverClassName` to style the popover container
|
|
201
|
+
- **Button Control**: Use `showButton` to control eye hint visibility
|
|
202
|
+
- **Open Behavior**: Custom popover content can open even when the displayed value itself is not truncated
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<TruncatedCell
|
|
206
|
+
text="User profile"
|
|
207
|
+
title="User Details"
|
|
208
|
+
popoverContent={
|
|
209
|
+
<div className="space-y-2">
|
|
210
|
+
<div className="flex items-center gap-2">
|
|
211
|
+
<Avatar src="/avatar.jpg" />
|
|
212
|
+
<div>
|
|
213
|
+
<p className="font-semibold">John Doe</p>
|
|
214
|
+
<p className="text-sm text-gray-600">john@example.com</p>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
<div className="flex gap-2">
|
|
218
|
+
<Button size="sm">Edit</Button>
|
|
219
|
+
<Button size="sm" variant="outline">
|
|
220
|
+
View Profile
|
|
221
|
+
</Button>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
}
|
|
225
|
+
popoverClassName="max-w-md"
|
|
226
|
+
showButton={true}
|
|
227
|
+
/>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Button Visibility Control
|
|
231
|
+
|
|
232
|
+
The `showButton` prop gives you control over the eye hint visibility:
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
// Show eye hint (default)
|
|
236
|
+
<TruncatedCell text="Long text that will be truncated" showButton={true} />
|
|
237
|
+
|
|
238
|
+
// Hide eye hint
|
|
239
|
+
<TruncatedCell text="Long text without eye hint" showButton={false} />
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Empty Fallback
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
// Default placeholder
|
|
246
|
+
<TruncatedCell text="" />
|
|
247
|
+
|
|
248
|
+
// Custom placeholder
|
|
249
|
+
<TruncatedCell text="" emptyFallback="No value" />
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Popover Styling
|
|
253
|
+
|
|
254
|
+
The `popoverClassName` prop allows custom styling of the popover container:
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<TruncatedCell
|
|
258
|
+
text="Custom styled popover"
|
|
259
|
+
popoverClassName="max-w-md bg-blue-50 border-blue-200"
|
|
260
|
+
/>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Technical Details
|
|
264
|
+
|
|
265
|
+
### Dependencies
|
|
266
|
+
|
|
267
|
+
- React hooks: `useState`, `useEffect`, `useRef`, `useCallback`
|
|
268
|
+
- Laif-DS components: `Popover`, `PopoverContent`, `PopoverTrigger`, `Typo`, `Icon`
|
|
269
|
+
- Utility: `cn` for class merging
|
|
270
|
+
|
|
271
|
+
### Performance Optimizations
|
|
272
|
+
|
|
273
|
+
- **ResizeObserver**: Uses modern ResizeObserver API for better performance than window resize events
|
|
274
|
+
- **useCallback**: Memoizes truncation check function to prevent unnecessary re-renders
|
|
275
|
+
- **Controlled State**: Manages popover state programmatically to avoid prop drilling
|
|
276
|
+
- **Cleanup**: Proper cleanup of observers and event listeners
|
|
277
|
+
- **Optimized Re-renders**: Only re-checks truncation when content changes
|
|
278
|
+
|
|
279
|
+
### Event Handling
|
|
280
|
+
|
|
281
|
+
- ResizeObserver for element-specific resize detection
|
|
282
|
+
- Fallback window resize listener for older browsers
|
|
283
|
+
- Click on the cell trigger opens the popover when available
|
|
284
|
+
- Keyboard support (Enter/Space keys)
|
|
285
|
+
- Hover effects with smooth transitions
|
|
286
|
+
|
|
287
|
+
### Accessibility Features
|
|
288
|
+
|
|
289
|
+
- **Semantic Trigger**: Uses a real `button` element as the interactive trigger
|
|
290
|
+
- **Keyboard Navigation**: Full support for Enter and Space keys
|
|
291
|
+
- **Screen Reader Labels**: Dynamic `aria-label` based on truncation state
|
|
292
|
+
- **Focus Management**: Native button focus behavior
|
|
293
|
+
- **Semantic HTML**: Popover content remains accessible even for multiline values
|
|
294
|
+
|
|
295
|
+
## Accessibility
|
|
296
|
+
|
|
297
|
+
- Text container has cursor pointer to indicate interactivity
|
|
298
|
+
- Hover state provides visual feedback
|
|
299
|
+
- Popover provides full text access for screen readers
|
|
300
|
+
- Eye icon provides a clear visual hint that more content is available
|
|
301
|
+
|
|
302
|
+
## Examples
|
|
303
|
+
|
|
304
|
+
### In a Table Cell
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
const TableCell = ({ value }: { value: string }) => (
|
|
308
|
+
<TruncatedCell text={value} wrapperClassName="w-56" className="py-2" />
|
|
309
|
+
);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### With Maximum Width Constraint
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
<TruncatedCell
|
|
316
|
+
text="Constrained width text that will truncate"
|
|
317
|
+
wrapperClassName="w-32"
|
|
318
|
+
/>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Multiline Text Support
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
<TruncatedCell text="Line 1\nLine 2\nLine 3" className="max-w-48" />
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Comparison with Alternatives
|
|
328
|
+
|
|
329
|
+
### vs Tooltip
|
|
330
|
+
|
|
331
|
+
- **TruncatedCell**: Supports click-to-inspect content, and visually hints with the eye icon when text is actually truncated
|
|
332
|
+
- **Tooltip**: Always shows on hover regardless of truncation
|
|
333
|
+
|
|
334
|
+
### vs CSS text-overflow alone
|
|
335
|
+
|
|
336
|
+
- **TruncatedCell**: Provides access to full content
|
|
337
|
+
- **CSS only**: Content is inaccessible when truncated
|
|
338
|
+
|
|
339
|
+
### vs Expandable Text
|
|
340
|
+
|
|
341
|
+
- **TruncatedCell**: Compact, popover-based approach
|
|
342
|
+
- **Expandable**: Takes more space, inline expansion
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as r, jsxs as e, Fragment as c } from "react/jsx-runtime";
|
|
3
|
+
import { cva as A } from "../../node_modules/class-variance-authority/dist/index.js";
|
|
4
|
+
import { designTokens as j } from "../design-tokens.js";
|
|
5
|
+
import { cn as t } from "../../lib/utils.js";
|
|
6
|
+
import { Card as y, CardHeader as b, CardTitle as S, CardDescription as T, CardAction as V, CardContent as g, CardFooter as D } from "./card.js";
|
|
7
|
+
import { Skeleton as s } from "./skeleton.js";
|
|
8
|
+
const F = A(
|
|
9
|
+
["flex flex-col border shadow-sm rounded-xl", j.focusRingWithin],
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "bg-d-card text-d-card-foreground border-d-border",
|
|
14
|
+
elevated: "bg-d-card text-d-card-foreground border-d-border shadow-md",
|
|
15
|
+
outlined: "bg-transparent text-d-foreground border-d-border shadow-none",
|
|
16
|
+
ghost: "bg-transparent text-d-foreground border-transparent shadow-none",
|
|
17
|
+
flat: "bg-d-muted text-d-foreground border-transparent shadow-none",
|
|
18
|
+
interactive: "bg-d-card text-d-card-foreground border-d-border cursor-pointer transition-shadow duration-200 hover:shadow-md hover:border-d-primary/40",
|
|
19
|
+
success: "bg-d-success-background text-d-success-foreground border-d-success/30 shadow-none",
|
|
20
|
+
warning: "bg-d-warning-background text-d-warning-foreground border-d-warning/30 shadow-none",
|
|
21
|
+
destructive: "bg-d-destructive/10 text-d-destructive border-d-destructive/30 shadow-none",
|
|
22
|
+
info: "bg-d-primary/10 text-d-primary border-d-primary/30 shadow-none"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "default"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
function R() {
|
|
31
|
+
return /* @__PURE__ */ e(c, { children: [
|
|
32
|
+
/* @__PURE__ */ r(b, { children: /* @__PURE__ */ e("div", { className: "flex flex-col gap-2", children: [
|
|
33
|
+
/* @__PURE__ */ r(s, { className: "h-4 w-1/2" }),
|
|
34
|
+
/* @__PURE__ */ r(s, { className: "h-3 w-3/4" })
|
|
35
|
+
] }) }),
|
|
36
|
+
/* @__PURE__ */ e(g, { className: "flex flex-col gap-2", children: [
|
|
37
|
+
/* @__PURE__ */ r(s, { className: "h-3 w-full" }),
|
|
38
|
+
/* @__PURE__ */ r(s, { className: "h-3 w-full" }),
|
|
39
|
+
/* @__PURE__ */ r(s, { className: "h-3 w-4/5" })
|
|
40
|
+
] })
|
|
41
|
+
] });
|
|
42
|
+
}
|
|
43
|
+
function I({
|
|
44
|
+
header: d,
|
|
45
|
+
title: a,
|
|
46
|
+
description: n,
|
|
47
|
+
body: l,
|
|
48
|
+
actions: o,
|
|
49
|
+
footer: i,
|
|
50
|
+
variant: h = "default",
|
|
51
|
+
size: p,
|
|
52
|
+
isLoading: x = !1,
|
|
53
|
+
showHeaderBorder: f = !1,
|
|
54
|
+
showFooterBorder: u = !1,
|
|
55
|
+
className: w,
|
|
56
|
+
headerClassName: v,
|
|
57
|
+
bodyClassName: N,
|
|
58
|
+
footerClassName: C,
|
|
59
|
+
children: m,
|
|
60
|
+
...k
|
|
61
|
+
}) {
|
|
62
|
+
return /* @__PURE__ */ r(
|
|
63
|
+
y,
|
|
64
|
+
{
|
|
65
|
+
className: t(F({ variant: h }), w),
|
|
66
|
+
size: p,
|
|
67
|
+
...k,
|
|
68
|
+
children: x ? /* @__PURE__ */ r(R, {}) : /* @__PURE__ */ e(c, { children: [
|
|
69
|
+
(d || a || n || o) && /* @__PURE__ */ r(
|
|
70
|
+
b,
|
|
71
|
+
{
|
|
72
|
+
className: t(
|
|
73
|
+
f && "border-b border-inherit",
|
|
74
|
+
v
|
|
75
|
+
),
|
|
76
|
+
children: d || /* @__PURE__ */ e(c, { children: [
|
|
77
|
+
/* @__PURE__ */ e("div", { className: "flex flex-col gap-1.5", children: [
|
|
78
|
+
a && /* @__PURE__ */ r(S, { className: "text-base font-semibold", children: a }),
|
|
79
|
+
n && /* @__PURE__ */ r(T, { children: n })
|
|
80
|
+
] }),
|
|
81
|
+
o && /* @__PURE__ */ r(V, { children: o })
|
|
82
|
+
] })
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
(l ?? m) && /* @__PURE__ */ r(
|
|
86
|
+
g,
|
|
87
|
+
{
|
|
88
|
+
className: t(
|
|
89
|
+
"flex flex-col gap-2",
|
|
90
|
+
(a || n || d || o) && !f && "pt-0",
|
|
91
|
+
u && "border-b border-inherit",
|
|
92
|
+
N
|
|
93
|
+
),
|
|
94
|
+
children: l ?? m
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
i && /* @__PURE__ */ r(
|
|
98
|
+
D,
|
|
99
|
+
{
|
|
100
|
+
className: t(
|
|
101
|
+
"gap-2",
|
|
102
|
+
(a || n || d || o || l) && !u && "pt-0",
|
|
103
|
+
C
|
|
104
|
+
),
|
|
105
|
+
children: i
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
] })
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
I as AppCard,
|
|
114
|
+
I as default
|
|
115
|
+
};
|