@brumaombra/ui-vintage 0.9.5 → 0.9.7

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 (75) hide show
  1. package/AGENTS.md +3 -1
  2. package/COMPONENTS.md +128 -14
  3. package/package.json +4 -2
  4. package/src/components/action-bar/ActionBar.vue +38 -0
  5. package/src/components/action-bar/index.ts +2 -0
  6. package/src/components/blog/BlogContentRenderer.vue +3 -3
  7. package/src/components/blog/BlogPostPage.vue +4 -0
  8. package/src/components/blog/BlogSectionTitle.vue +7 -21
  9. package/src/components/blog/BlogSummary.vue +31 -0
  10. package/src/components/blog/blogPageUtils.ts +1 -0
  11. package/src/components/blog/index.ts +1 -0
  12. package/src/components/chip/Chip.vue +1 -1
  13. package/src/components/dashboard-shell/DashboardShell.vue +5 -5
  14. package/src/components/data-list/DataListText.vue +7 -4
  15. package/src/components/language-flag/flags/ChineseFlag.vue +1 -1
  16. package/src/components/language-flag/flags/EnglishFlag.vue +1 -1
  17. package/src/components/language-flag/flags/FrenchFlag.vue +1 -1
  18. package/src/components/language-flag/flags/GermanFlag.vue +1 -1
  19. package/src/components/language-flag/flags/ItalianFlag.vue +1 -1
  20. package/src/components/language-flag/flags/JapaneseFlag.vue +1 -1
  21. package/src/components/language-flag/flags/PortugueseFlag.vue +1 -1
  22. package/src/components/language-flag/flags/RussianFlag.vue +1 -1
  23. package/src/components/language-flag/flags/SpanishFlag.vue +1 -1
  24. package/src/components/language-selector/LanguageSelector.vue +2 -2
  25. package/src/components/section-title/SectionTitle.vue +47 -0
  26. package/src/components/section-title/index.ts +2 -0
  27. package/src/components/single-value-card/SingleValueCard.vue +1 -1
  28. package/src/components/ui/alert-dialog/AlertDialogAction.vue +6 -6
  29. package/src/components/ui/avatar/Avatar.vue +4 -2
  30. package/src/components/ui/avatar/AvatarFallback.vue +25 -5
  31. package/src/components/ui/badge/Badge.vue +6 -2
  32. package/src/components/ui/breadcrumb/BreadcrumbEllipsis.vue +1 -1
  33. package/src/components/ui/breadcrumb/BreadcrumbLink.vue +1 -1
  34. package/src/components/ui/button/Button.vue +18 -2
  35. package/src/components/ui/button/index.ts +4 -0
  36. package/src/components/ui/card/Card.vue +25 -1
  37. package/src/components/ui/card/CardMedia.vue +15 -0
  38. package/src/components/ui/card/CardTitle.vue +8 -3
  39. package/src/components/ui/card/index.ts +1 -0
  40. package/src/components/ui/checkbox/Checkbox.vue +1 -1
  41. package/src/components/ui/combobox/ComboboxSelect.vue +2 -2
  42. package/src/components/ui/combobox/ComboboxTrigger.vue +1 -1
  43. package/src/components/ui/data-table/DataTable.vue +1 -1
  44. package/src/components/ui/dialog/DialogContent.vue +7 -3
  45. package/src/components/ui/dialog/DialogScrollContent.vue +7 -3
  46. package/src/components/ui/dialog/index.ts +11 -1
  47. package/src/components/ui/file-dropzone/FileDropzone.vue +1 -1
  48. package/src/components/ui/input/Input.vue +42 -9
  49. package/src/components/ui/kbd/Kbd.vue +8 -4
  50. package/src/components/ui/number-field/NumberField.vue +2 -2
  51. package/src/components/ui/progress/Progress.vue +17 -5
  52. package/src/components/ui/select/SelectItem.vue +1 -1
  53. package/src/components/ui/skeleton/Skeleton.vue +7 -3
  54. package/src/components/ui/switch/Switch.vue +1 -1
  55. package/src/components/ui/table/TableHeader.vue +1 -1
  56. package/src/components/ui/table/TableRow.vue +11 -4
  57. package/src/components/ui/tabs/Tabs.vue +1 -1
  58. package/src/components/ui/tabs/TabsContent.vue +8 -5
  59. package/src/components/ui/tabs/TabsTrigger.vue +1 -1
  60. package/src/components/ui/tags-input/TagsInputItemDelete.vue +1 -1
  61. package/src/components/ui/textarea/Textarea.vue +1 -1
  62. package/src/components/ui/toggle-group/ToggleGroup.vue +2 -2
  63. package/src/components/ui/toggle-group/ToggleGroupItem.vue +3 -1
  64. package/src/components/ui/toggle-group/index.ts +2 -2
  65. package/src/i18n/de.json +1 -0
  66. package/src/i18n/en.json +1 -0
  67. package/src/i18n/es.json +1 -0
  68. package/src/i18n/fr.json +1 -0
  69. package/src/i18n/it.json +1 -0
  70. package/src/i18n/ja.json +1 -0
  71. package/src/i18n/pt.json +1 -0
  72. package/src/i18n/ru.json +1 -0
  73. package/src/i18n/zh.json +1 -0
  74. package/src/lib/color-tokens.ts +6 -6
  75. package/src/styles.css +11 -6
package/AGENTS.md CHANGED
@@ -28,7 +28,9 @@ Only build something new when no entry point fits, and compose it from library p
28
28
  - Import from explicit subpaths: `import { Button } from '@brumaombra/ui-vintage/button'`. Never import from `@brumaombra/ui-vintage/src/...`.
29
29
  - Style with the semantic tokens (`bg-background`, `bg-card`, `bg-surface`, `bg-accent`, `bg-primary`, `text-foreground`, `text-muted-foreground`, `border-border`, `border-border-strong`, ...). They adapt to dark mode, so don't write `dark:` color variants or hard-coded colors for surfaces and text.
30
30
  - Customize components through their props and the `class` prop (merged with tailwind-merge) rather than wrapping them in extra styled elements.
31
- - Keep corners at `rounded` (the library's square look) and use `shadow-elevated-sm|md|lg|xl` for elevation, without glows or gradients.
31
+ - Keep corners at `rounded` (the library's square look) and use `shadow-elevated-sm|md|lg|xl` for elevation, without glows or gradients. Every radius step resolves to the shared `--uv-radius` token, so change the token instead of passing other radii.
32
+ - Never restyle a library component from an app with `class` (padding, gaps, colors, borders, radius, shadows, typography, `!` modifiers, `[data-slot]` selectors): use its props and variants, and add a prop or variant to the library when one is missing. Layout classes (margins, width, flex/grid placement, visibility, entrance animations) are fine.
33
+ - `SectionTitle` (and `BlogSectionTitle`) owns the space under it (`mb-6 md:mb-8`): don't add margins, padding or `space-y` to the content that follows it.
32
34
  - For motion, reuse the library utilities (`ease-spring`, `ease-out-expo`, `animate-uv-fade-up`, `animate-uv-pop`, `uv-floating-motion`). In custom transitions, name `scale`, `rotate`, and `translate` explicitly; Tailwind v4 doesn't animate them through `transform`.
33
35
  - Icons are Hugeicons definitions: import each one from its own module (`import Rocket01Icon from '@hugeicons/core-free-icons/Rocket01Icon'`, never the `@hugeicons/core-free-icons` barrel, which makes the build parse the whole icon set) and pass them to `:icon` props, or render them with `HugeiconsIcon` from `@hugeicons/vue`. Both packages are peer dependencies, so the app must list them in its own `package.json`.
34
36
  - Form controls use `v-model`. Mark invalid fields with `aria-invalid` and show messages with `FieldError`.
package/COMPONENTS.md CHANGED
@@ -15,13 +15,13 @@ This file lists every public entry point of the package with its components, pro
15
15
 
16
16
  ## Contents
17
17
 
18
- - **Actions:** [button](#brumaombraui-vintagebutton), [dropdown-menu](#brumaombraui-vintagedropdown-menu), [kbd](#brumaombraui-vintagekbd), [load-more-button](#brumaombraui-vintageload-more-button), [text-link](#brumaombraui-vintagetext-link), [toggle-group](#brumaombraui-vintagetoggle-group)
18
+ - **Actions:** [action-bar](#brumaombraui-vintageaction-bar), [button](#brumaombraui-vintagebutton), [dropdown-menu](#brumaombraui-vintagedropdown-menu), [kbd](#brumaombraui-vintagekbd), [load-more-button](#brumaombraui-vintageload-more-button), [text-link](#brumaombraui-vintagetext-link), [toggle-group](#brumaombraui-vintagetoggle-group)
19
19
  - **Forms:** [calendar](#brumaombraui-vintagecalendar), [checkbox](#brumaombraui-vintagecheckbox), [combobox](#brumaombraui-vintagecombobox), [date-picker](#brumaombraui-vintagedate-picker), [date-time-picker](#brumaombraui-vintagedate-time-picker), [field](#brumaombraui-vintagefield), [file-dropzone](#brumaombraui-vintagefile-dropzone), [input](#brumaombraui-vintageinput), [label](#brumaombraui-vintagelabel), [native-select](#brumaombraui-vintagenative-select), [number-field](#brumaombraui-vintagenumber-field), [pin-input](#brumaombraui-vintagepin-input), [radio-group](#brumaombraui-vintageradio-group), [select](#brumaombraui-vintageselect), [slider](#brumaombraui-vintageslider), [slider-form-component](#brumaombraui-vintageslider-form-component), [switch](#brumaombraui-vintageswitch), [switch-form-component](#brumaombraui-vintageswitch-form-component), [tags-input](#brumaombraui-vintagetags-input), [textarea](#brumaombraui-vintagetextarea), [time-picker](#brumaombraui-vintagetime-picker)
20
20
  - **Data display:** [animated-number](#brumaombraui-vintageanimated-number), [avatar](#brumaombraui-vintageavatar), [badge](#brumaombraui-vintagebadge), [card](#brumaombraui-vintagecard), [card-grid](#brumaombraui-vintagecard-grid), [chip](#brumaombraui-vintagechip), [data-list](#brumaombraui-vintagedata-list), [data-table](#brumaombraui-vintagedata-table), [info-card](#brumaombraui-vintageinfo-card), [progress](#brumaombraui-vintageprogress), [progress-component](#brumaombraui-vintageprogress-component), [scroll-area](#brumaombraui-vintagescroll-area), [separator](#brumaombraui-vintageseparator), [single-value-card](#brumaombraui-vintagesingle-value-card), [skeleton](#brumaombraui-vintageskeleton), [stat-strip](#brumaombraui-vintagestat-strip), [table](#brumaombraui-vintagetable)
21
21
  - **Navigation:** [accordion](#brumaombraui-vintageaccordion), [breadcrumb](#brumaombraui-vintagebreadcrumb), [collapsible](#brumaombraui-vintagecollapsible), [pagination](#brumaombraui-vintagepagination), [sidebar](#brumaombraui-vintagesidebar), [stepper](#brumaombraui-vintagestepper), [tabs](#brumaombraui-vintagetabs)
22
22
  - **Overlays:** [alert-dialog](#brumaombraui-vintagealert-dialog), [command](#brumaombraui-vintagecommand), [dialog](#brumaombraui-vintagedialog), [hover-card](#brumaombraui-vintagehover-card), [popover](#brumaombraui-vintagepopover), [sheet](#brumaombraui-vintagesheet), [tooltip](#brumaombraui-vintagetooltip)
23
23
  - **Feedback:** [alert](#brumaombraui-vintagealert), [busy](#brumaombraui-vintagebusy), [busy-indicator](#brumaombraui-vintagebusy-indicator), [confirm-dialog](#brumaombraui-vintageconfirm-dialog), [cookie-consent](#brumaombraui-vintagecookie-consent), [empty-state-card](#brumaombraui-vintageempty-state-card), [loading-state-card](#brumaombraui-vintageloading-state-card), [message-dialog](#brumaombraui-vintagemessage-dialog), [message-toast](#brumaombraui-vintagemessage-toast), [spinner](#brumaombraui-vintagespinner)
24
- - **Layouts and app shell:** [background-grid](#brumaombraui-vintagebackground-grid), [dashboard-shell](#brumaombraui-vintagedashboard-shell), [error-page](#brumaombraui-vintageerror-page), [landing](#brumaombraui-vintagelanding), [landing-content](#brumaombraui-vintagelanding-content), [landing-footer](#brumaombraui-vintagelanding-footer), [landing-navbar](#brumaombraui-vintagelanding-navbar), [landing-shell](#brumaombraui-vintagelanding-shell), [language-flag](#brumaombraui-vintagelanguage-flag), [language-selector](#brumaombraui-vintagelanguage-selector), [page-header](#brumaombraui-vintagepage-header), [theme-selector](#brumaombraui-vintagetheme-selector)
24
+ - **Layouts and app shell:** [background-grid](#brumaombraui-vintagebackground-grid), [dashboard-shell](#brumaombraui-vintagedashboard-shell), [error-page](#brumaombraui-vintageerror-page), [landing](#brumaombraui-vintagelanding), [landing-content](#brumaombraui-vintagelanding-content), [landing-footer](#brumaombraui-vintagelanding-footer), [landing-navbar](#brumaombraui-vintagelanding-navbar), [landing-shell](#brumaombraui-vintagelanding-shell), [language-flag](#brumaombraui-vintagelanguage-flag), [language-selector](#brumaombraui-vintagelanguage-selector), [page-header](#brumaombraui-vintagepage-header), [section-title](#brumaombraui-vintagesection-title), [theme-selector](#brumaombraui-vintagetheme-selector)
25
25
  - **Content and blog:** [blog](#brumaombraui-vintageblog), [content](#brumaombraui-vintagecontent)
26
26
  - **Utilities and types:** [common-types](#brumaombraui-vintagecommon-types), [utils](#brumaombraui-vintageutils)
27
27
 
@@ -29,9 +29,37 @@ This file lists every public entry point of the package with its components, pro
29
29
 
30
30
  Buttons, segmented controls, menus, and keyboard hints.
31
31
 
32
+ ### `@brumaombra/ui-vintage/action-bar`
33
+
34
+ Row of action buttons that turns into a full-width bar of equal tiles (icon above the label) on mobile, like an app toolbar.
35
+
36
+ ```ts
37
+ import { ActionBar } from '@brumaombra/ui-vintage/action-bar';
38
+ ```
39
+
40
+ ```vue
41
+ <ActionBar>
42
+ <Button variant="secondary">
43
+ <HugeiconsIcon :icon="Edit01Icon" />
44
+ Edit
45
+ </Button>
46
+ <Button variant="secondary">
47
+ <HugeiconsIcon :icon="Share08Icon" />
48
+ Share
49
+ </Button>
50
+ </ActionBar>
51
+ ```
52
+
53
+ #### `ActionBar`
54
+
55
+ - **Props:** `class`.
56
+ - **Slots:** `default`.
57
+
58
+ ---
59
+
32
60
  ### `@brumaombra/ui-vintage/button`
33
61
 
34
- Button with primary, secondary, ghost, link, and tone variants. Spring press feedback and a `loading` state that keeps the width stable. Give it a `to` to make it a navigation button (a localized link, external URLs work too).
62
+ Button with primary, secondary, ghost, link, and tone variants. Spring press feedback and a `loading` state that keeps the width stable. Give it a `to` to make it a navigation button (a localized link, external URLs work too), and a `count` to show a notification badge on its corner.
35
63
 
36
64
  ```ts
37
65
  import { Button, buttonVariants } from '@brumaombra/ui-vintage/button';
@@ -47,6 +75,10 @@ import type { ButtonVariants } from '@brumaombra/ui-vintage/button';
47
75
  <Button to="/pricing" variant="secondary">
48
76
  Pricing
49
77
  </Button>
78
+
79
+ <Button variant="secondary" size="icon" :count="unread" aria-label="Notifications">
80
+ <HugeiconsIcon :icon="Notification01Icon" />
81
+ </Button>
50
82
  ```
51
83
 
52
84
  #### `Button`
@@ -58,6 +90,8 @@ import type { ButtonVariants } from '@brumaombra/ui-vintage/button';
58
90
  | `loading` | `boolean` | `false` |
59
91
  | `disabled` | `boolean` | `false` |
60
92
  | `to` | `string` | |
93
+ | `count` | `number \| null` | `null` |
94
+ | `countMax` | `number` | `99` |
61
95
  | `class` | `HTMLAttributes['class']` | |
62
96
 
63
97
  - **Also accepts** all props of Reka `PrimitiveProps`.
@@ -219,6 +253,7 @@ import { Kbd, KbdGroup } from '@brumaombra/ui-vintage/kbd';
219
253
  | --- | --- | --- |
220
254
  | `class` | `HTMLAttributes['class']` | |
221
255
  | `pressed` | `boolean` | |
256
+ | `size` | `'default' \| 'lg'` | `'default'` |
222
257
 
223
258
  - **Slots:** `default`.
224
259
 
@@ -330,7 +365,7 @@ type ToggleVariants = { variant?: 'default' | 'outline' | null | undefined; size
330
365
  type ToggleGroupSize = 'default' | 'sm' | 'lg'
331
366
  const toggleVariants: (props?: ToggleVariants & ClassProp) => string
332
367
  const toggleGroupItemVariants: (props?: { size?: ToggleGroupSize | null } & ClassProp) => string
333
- const toggleGroupContextKey: InjectionKey<{ size: Ref<ToggleGroupSize>; indicator: Ref<boolean> }>
368
+ const toggleGroupContextKey: InjectionKey<{ size: Ref<ToggleGroupSize>; indicator: Ref<boolean>; highlight: Ref<boolean> }>
334
369
  ```
335
370
 
336
371
  ## Forms
@@ -766,9 +801,11 @@ import { Input } from '@brumaombra/ui-vintage/input';
766
801
  | --- | --- | --- |
767
802
  | `defaultValue` | `string \| number` | |
768
803
  | `modelValue` | `string \| number` | |
804
+ | `size` | `'default' \| 'sm'` | |
769
805
  | `class` | `HTMLAttributes['class']` | |
770
806
 
771
807
  - **Emits:** `update:modelValue` (payload: string \| number).
808
+ - **Slots:** `prefix`, `suffix`.
772
809
 
773
810
  ---
774
811
 
@@ -1268,6 +1305,7 @@ import type { AvatarSize, AvatarShape, AvatarStatus } from '@brumaombra/ui-vinta
1268
1305
  | `size` | `AvatarSize` | `'md'` |
1269
1306
  | `shape` | `AvatarShape` | `'circle'` |
1270
1307
  | `status` | `AvatarStatus` | |
1308
+ | `ring` | `boolean` | `false` |
1271
1309
  | `class` | `HTMLAttributes['class']` | |
1272
1310
 
1273
1311
  - **Also accepts** all props of Reka `AvatarRootProps`.
@@ -1278,6 +1316,7 @@ import type { AvatarSize, AvatarShape, AvatarStatus } from '@brumaombra/ui-vinta
1278
1316
  | Prop | Type | Default |
1279
1317
  | --- | --- | --- |
1280
1318
  | `name` | `string` | |
1319
+ | `color` | `'neutral' \| 'auto' \| 'primary'` | `'neutral'` |
1281
1320
  | `class` | `HTMLAttributes['class']` | |
1282
1321
 
1283
1322
  - **Also accepts** all props of Reka `AvatarFallbackProps`.
@@ -1331,7 +1370,7 @@ import { Badge } from '@brumaombra/ui-vintage/badge';
1331
1370
  | --- | --- | --- |
1332
1371
  | `text` *(required)* | `string` | |
1333
1372
  | `icon` | `HugeiconsIconDefinition \| null` | `null` |
1334
- | `color` | `ToneColor` | `'gray'` |
1373
+ | `color` | `ToneColor \| 'primary'` | `'gray'` |
1335
1374
  | `dot` | `boolean` | `false` |
1336
1375
  | `pulse` | `boolean` | `false` |
1337
1376
  | `class` | `HTMLAttributes['class']` | |
@@ -1343,7 +1382,7 @@ import { Badge } from '@brumaombra/ui-vintage/badge';
1343
1382
  Surface container with header, title, description, content, footer, and action slots. `interactive` adds a gentle hover lift with a stronger shadow; `color` switches to a tone surface.
1344
1383
 
1345
1384
  ```ts
1346
- import { Card, CardAction, CardContent, CardText, CardDescription, CardFooter, CardHeader, CardTitle } from '@brumaombra/ui-vintage/card';
1385
+ import { Card, CardAction, CardContent, CardText, CardDescription, CardFooter, CardHeader, CardMedia, CardTitle } from '@brumaombra/ui-vintage/card';
1347
1386
  ```
1348
1387
 
1349
1388
  ```vue
@@ -1361,6 +1400,8 @@ import { Card, CardAction, CardContent, CardText, CardDescription, CardFooter, C
1361
1400
  | Prop | Type | Default |
1362
1401
  | --- | --- | --- |
1363
1402
  | `color` | `CardColor` | |
1403
+ | `padding` | `CardPadding` | `'default'` |
1404
+ | `elevation` | `CardElevation` | |
1364
1405
  | `interactive` | `boolean` | `false` |
1365
1406
  | `class` | `HTMLAttributes['class']` | |
1366
1407
 
@@ -1396,12 +1437,18 @@ import { Card, CardAction, CardContent, CardText, CardDescription, CardFooter, C
1396
1437
  - **Props:** `class`.
1397
1438
  - **Slots:** `default`.
1398
1439
 
1440
+ #### `CardMedia`
1441
+
1442
+ - **Props:** `class`.
1443
+ - **Slots:** `default`.
1444
+
1399
1445
  #### `CardTitle`
1400
1446
 
1401
1447
  | Prop | Type | Default |
1402
1448
  | --- | --- | --- |
1403
1449
  | `class` | `HTMLAttributes['class']` | |
1404
1450
  | `level` | `HeadingLevel` | `3` |
1451
+ | `icon` | `HugeiconsIconDefinition \| null` | `null` |
1405
1452
 
1406
1453
  - **Slots:** `default`.
1407
1454
 
@@ -1477,7 +1524,11 @@ import { DataList, DataListItem, DataListLabel, DataListText, DataListValue } fr
1477
1524
 
1478
1525
  #### `DataListText`
1479
1526
 
1480
- - **Props:** `class`.
1527
+ | Prop | Type | Default |
1528
+ | --- | --- | --- |
1529
+ | `wrap` | `boolean` | `false` |
1530
+ | `class` | `HTMLAttributes['class']` | |
1531
+
1481
1532
  - **Slots:** `default`.
1482
1533
 
1483
1534
  #### `DataListValue`
@@ -1582,6 +1633,7 @@ import { Progress } from '@brumaombra/ui-vintage/progress';
1582
1633
  | Prop | Type | Default |
1583
1634
  | --- | --- | --- |
1584
1635
  | `indeterminate` | `boolean` | `false` |
1636
+ | `color` | `ProgressColor` | `'primary'` |
1585
1637
  | `class` | `HTMLAttributes['class']` | |
1586
1638
 
1587
1639
  - **Also accepts** all props of Reka `ProgressRootProps`.
@@ -1684,7 +1736,10 @@ import { Skeleton } from '@brumaombra/ui-vintage/skeleton';
1684
1736
 
1685
1737
  #### `Skeleton`
1686
1738
 
1687
- - **Props:** `class`.
1739
+ | Prop | Type | Default |
1740
+ | --- | --- | --- |
1741
+ | `circle` | `boolean` | `false` |
1742
+ | `class` | `HTMLAttributes['class']` | |
1688
1743
 
1689
1744
  ---
1690
1745
 
@@ -1768,7 +1823,12 @@ import { Table, TableBody, TableCaption, TableCell, TableEmpty, TableFooter, Tab
1768
1823
 
1769
1824
  #### `TableRow`
1770
1825
 
1771
- - **Props:** `class`.
1826
+ | Prop | Type | Default |
1827
+ | --- | --- | --- |
1828
+ | `static` | `boolean` | `false` |
1829
+ | `interactive` | `boolean` | `false` |
1830
+ | `class` | `HTMLAttributes['class']` | |
1831
+
1772
1832
  - **Slots:** `default`.
1773
1833
 
1774
1834
  ## Navigation
@@ -2252,7 +2312,11 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@brumaombra/ui-vintage
2252
2312
 
2253
2313
  #### `TabsContent`
2254
2314
 
2255
- - **Props:** `class`.
2315
+ | Prop | Type | Default |
2316
+ | --- | --- | --- |
2317
+ | `variant` | `'plain' \| 'boxed'` | `'plain'` |
2318
+ | `class` | `HTMLAttributes['class']` | |
2319
+
2256
2320
  - **Also accepts** all props of Reka `TabsContentProps`.
2257
2321
  - **Slots:** `default`.
2258
2322
 
@@ -2292,7 +2356,11 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
2292
2356
 
2293
2357
  #### `AlertDialogAction`
2294
2358
 
2295
- - **Props:** `class`.
2359
+ | Prop | Type | Default |
2360
+ | --- | --- | --- |
2361
+ | `variant` | `ButtonVariants['variant']` | |
2362
+ | `class` | `HTMLAttributes['class']` | |
2363
+
2296
2364
  - **Also accepts** all props of Reka `AlertDialogActionProps`.
2297
2365
  - **Slots:** `default`.
2298
2366
 
@@ -2437,7 +2505,8 @@ value [useCommandGroup, provideCommandGroupContext]
2437
2505
  Modal dialog. `DialogContent` accepts `show-close-button`; use `DialogScrollContent` for long content.
2438
2506
 
2439
2507
  ```ts
2440
- import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogScrollContent, DialogTitle, DialogTrigger } from '@brumaombra/ui-vintage/dialog';
2508
+ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogScrollContent, DialogTitle, DialogTrigger, dialogSizeClasses } from '@brumaombra/ui-vintage/dialog';
2509
+ import type { DialogSize } from '@brumaombra/ui-vintage/dialog';
2441
2510
  ```
2442
2511
 
2443
2512
  ```vue
@@ -2475,6 +2544,7 @@ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, Di
2475
2544
  | --- | --- | --- |
2476
2545
  | `class` | `HTMLAttributes['class']` | |
2477
2546
  | `showCloseButton` | `boolean` | `false` |
2547
+ | `size` | `DialogSize` | `'sm'` |
2478
2548
 
2479
2549
  - **Also accepts** all props of Reka `DialogContentProps`.
2480
2550
  - **Emits:** all events of Reka `DialogContentEmits`.
@@ -2508,6 +2578,7 @@ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, Di
2508
2578
  | --- | --- | --- |
2509
2579
  | `class` | `HTMLAttributes['class']` | |
2510
2580
  | `showCloseButton` | `boolean` | `false` |
2581
+ | `size` | `DialogSize` | `'sm'` |
2511
2582
 
2512
2583
  - **Also accepts** all props of Reka `DialogContentProps`.
2513
2584
  - **Emits:** all events of Reka `DialogContentEmits`.
@@ -2524,6 +2595,13 @@ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, Di
2524
2595
  - **Also accepts** all props of Reka `DialogTriggerProps`.
2525
2596
  - **Slots:** `default`.
2526
2597
 
2598
+ #### Functions, constants, and types
2599
+
2600
+ ```ts
2601
+ type DialogSize = 'sm' | 'md' | 'lg' | 'xl'
2602
+ const dialogSizeClasses: Record<DialogSize, string>
2603
+ ```
2604
+
2527
2605
  ---
2528
2606
 
2529
2607
  ### `@brumaombra/ui-vintage/hover-card`
@@ -3424,6 +3502,35 @@ import { PageHeader } from '@brumaombra/ui-vintage/page-header';
3424
3502
 
3425
3503
  ---
3426
3504
 
3505
+ ### `@brumaombra/ui-vintage/section-title`
3506
+
3507
+ Section heading over a rule that starts with a primary segment, with an optional icon tile, description, and trailing action (the blog sections use it too).
3508
+
3509
+ ```ts
3510
+ import { SectionTitle } from '@brumaombra/ui-vintage/section-title';
3511
+ ```
3512
+
3513
+ ```vue
3514
+ <SectionTitle title="Market FAQ" :icon="HelpCircleIcon" description="Everything about how this market works">
3515
+ <template #action>
3516
+ <Button variant="ghost" size="sm">View all</Button>
3517
+ </template>
3518
+ </SectionTitle>
3519
+ ```
3520
+
3521
+ #### `SectionTitle`
3522
+
3523
+ | Prop | Type | Default |
3524
+ | --- | --- | --- |
3525
+ | `title` *(required)* | `string` | |
3526
+ | `description` | `string` | `''` |
3527
+ | `icon` | `HugeiconsIconDefinition \| null` | `null` |
3528
+ | `headingLevel` | `HeadingLevel` | `2` |
3529
+
3530
+ - **Slots:** `action`.
3531
+
3532
+ ---
3533
+
3427
3534
  ### `@brumaombra/ui-vintage/theme-selector`
3428
3535
 
3429
3536
  Light, dark, and auto theme menu with a circular reveal (View Transitions API). `applyThemeWithTransition` switches themes from your own controls.
@@ -3461,10 +3568,10 @@ Components for Nuxt Content pages and a complete blog.
3461
3568
 
3462
3569
  ### `@brumaombra/ui-vintage/blog`
3463
3570
 
3464
- Complete blog pages and their building blocks. The page components (`BlogIndexPage`, `BlogPostPage`, `BlogCategoriesPage`, `BlogTagsPage`, `BlogPostsPage` for the posts of a category or a tag) render the whole design: the app keeps one thin file per route that queries Nuxt Content, sets the SEO and passes the data and its own copy (titles and descriptions), with the helpers (`slugify`, `normalizeContentPath`, `buildCategoriesFromPosts`, `buildTagsFromPosts`, `sortByStableSeed`) shaping the data. The post page renders the app's `ProseHr` content component as divider and has an `after-article` slot for a call to action. The building blocks: an animated hero header, a post header (breadcrumbs, category and tags, title, author line, and featured image), a full-bleed featured carousel, post and category cards, tags, table of contents, reading progress, FAQ, share sidebar, and the content renderer. Scroll entrances use AOS: install `aos` in the app and initialize it (the components carry `data-aos="blur-up"`, and the library stylesheet defines that animation). Without AOS the content simply renders without entrances.
3571
+ Complete blog pages and their building blocks. The page components (`BlogIndexPage`, `BlogPostPage`, `BlogCategoriesPage`, `BlogTagsPage`, `BlogPostsPage` for the posts of a category or a tag) render the whole design: the app keeps one thin file per route that queries Nuxt Content, sets the SEO and passes the data and its own copy (titles and descriptions), with the helpers (`slugify`, `normalizeContentPath`, `buildCategoriesFromPosts`, `buildTagsFromPosts`, `sortByStableSeed`) shaping the data. The post page renders the app's `ProseHr` content component as divider and has an `after-article` slot for a call to action. The building blocks: an animated hero header, a post header (breadcrumbs, category and tags, title, author line, and featured image), an "In short" box with the `summary` field of the frontmatter (the post page shows it before the table of contents when the post has one), a full-bleed featured carousel, post and category cards, tags, table of contents, reading progress, FAQ, share sidebar, and the content renderer. Scroll entrances use AOS: install `aos` in the app and initialize it (the components carry `data-aos="blur-up"`, and the library stylesheet defines that animation). Without AOS the content simply renders without entrances.
3465
3572
 
3466
3573
  ```ts
3467
- import { AllPostsSection, BlogContentRenderer, BlogFAQSection, BlogInfoSection, BlogPostHeader, BlogHeaderSection, BlogSectionTitle, BlogTagsSection, CategoriesList, CategoriesSection, CategoryCard, HeaderCarousel, PostCard, PostsList, SocialShareSidebar, TableOfContents, BlogIndexPage, BlogPostPage, BlogCategoriesPage, BlogTagsPage, BlogPostsPage, BlogViewAllLink, slugify, normalizeContentPath, buildCategoriesFromPosts, buildTagsFromPosts, sortByStableSeed } from '@brumaombra/ui-vintage/blog';
3574
+ import { AllPostsSection, BlogContentRenderer, BlogFAQSection, BlogInfoSection, BlogPostHeader, BlogHeaderSection, BlogSectionTitle, BlogSummary, BlogTagsSection, CategoriesList, CategoriesSection, CategoryCard, HeaderCarousel, PostCard, PostsList, SocialShareSidebar, TableOfContents, BlogIndexPage, BlogPostPage, BlogCategoriesPage, BlogTagsPage, BlogPostsPage, BlogViewAllLink, slugify, normalizeContentPath, buildCategoriesFromPosts, buildTagsFromPosts, sortByStableSeed } from '@brumaombra/ui-vintage/blog';
3468
3575
  import type { BlogPostData, BlogCategoryData, BlogTagData } from '@brumaombra/ui-vintage/blog';
3469
3576
  ```
3470
3577
 
@@ -3546,6 +3653,12 @@ import type { BlogPostData, BlogCategoryData, BlogTagData } from '@brumaombra/ui
3546
3653
 
3547
3654
  - **Slots:** `action`.
3548
3655
 
3656
+ #### `BlogSummary`
3657
+
3658
+ | Prop | Type | Default |
3659
+ | --- | --- | --- |
3660
+ | `summary` *(required)* | `string` | |
3661
+
3549
3662
  #### `BlogTagsSection`
3550
3663
 
3551
3664
  | Prop | Type | Default |
@@ -3686,6 +3799,7 @@ interface BlogPostData {
3686
3799
  path?: string;
3687
3800
  title?: string;
3688
3801
  description?: string;
3802
+ summary?: string;
3689
3803
  image?: string;
3690
3804
  categoryText?: string;
3691
3805
  categorySlug?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brumaombra/ui-vintage",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "Source-published Nuxt UI runtime with shared theme tokens and built-in Nuxt integrations.",
5
5
  "private": false,
6
6
  "author": "Mauro Brambilla",
@@ -40,6 +40,7 @@
40
40
  "./package.json": "./package.json",
41
41
  "./style.css": "./src/styles.css",
42
42
  "./accordion": "./src/components/ui/accordion/index.ts",
43
+ "./action-bar": "./src/components/action-bar/index.ts",
43
44
  "./alert": "./src/components/ui/alert/index.ts",
44
45
  "./alert-dialog": "./src/components/ui/alert-dialog/index.ts",
45
46
  "./animated-number": "./src/components/ui/animated-number/index.ts",
@@ -100,6 +101,7 @@
100
101
  "./progress-component": "./src/components/progress-component/index.ts",
101
102
  "./radio-group": "./src/components/ui/radio-group/index.ts",
102
103
  "./scroll-area": "./src/components/ui/scroll-area/index.ts",
104
+ "./section-title": "./src/components/section-title/index.ts",
103
105
  "./select": "./src/components/ui/select/index.ts",
104
106
  "./separator": "./src/components/ui/separator/index.ts",
105
107
  "./sheet": "./src/components/ui/sheet/index.ts",
@@ -173,4 +175,4 @@
173
175
  "glob": "13.0.6"
174
176
  }
175
177
  }
176
- }
178
+ }
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ import type { HTMLAttributes } from 'vue';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ // Props (a row of buttons on desktop; on mobile a full-width bar of equal tiles with the icon above the label, like an app)
6
+ const props = defineProps<{
7
+ class?: HTMLAttributes['class'];
8
+ }>();
9
+ </script>
10
+
11
+ <template>
12
+ <div data-slot="action-bar" :class="cn('action-bar grid w-full auto-cols-fr grid-flow-col gap-2 md:flex md:w-auto md:flex-wrap md:items-center md:gap-3', props.class)">
13
+ <slot />
14
+ </div>
15
+ </template>
16
+
17
+ <style scoped>
18
+ /* Mobile tiles: icon above a short label, filling the bar */
19
+ @media (width < 48rem) {
20
+ .action-bar > :slotted([data-slot="button"]) {
21
+ height: auto;
22
+ width: auto;
23
+ flex-direction: column;
24
+ gap: 0.375rem;
25
+ padding: 0.75rem 0.25rem;
26
+ font-size: 0.6875rem;
27
+ line-height: 1.2;
28
+ white-space: normal;
29
+ text-align: center;
30
+ }
31
+
32
+ /* Same icon size on every tile */
33
+ .action-bar > :slotted([data-slot="button"]) svg {
34
+ width: 1.125rem;
35
+ height: 1.125rem;
36
+ }
37
+ }
38
+ </style>
@@ -0,0 +1,2 @@
1
+ export { default } from './ActionBar.vue';
2
+ export { default as ActionBar } from './ActionBar.vue';
@@ -153,7 +153,7 @@ const props = defineProps<{
153
153
  padding: 1rem 1.25rem 1rem 1.5rem;
154
154
  border: 1px solid var(--border);
155
155
  border-left: 3px solid var(--primary);
156
- border-radius: var(--radius-sm);
156
+ border-radius: var(--uv-radius);
157
157
  background-color: var(--surface);
158
158
  font-size: 15px;
159
159
  font-style: italic;
@@ -169,7 +169,7 @@ const props = defineProps<{
169
169
  color: var(--foreground);
170
170
  background-color: var(--surface);
171
171
  border: 1px solid var(--border);
172
- border-radius: 0.25rem;
172
+ border-radius: var(--uv-radius);
173
173
  display: inline-block;
174
174
  font-size: 0.75rem;
175
175
  font-weight: 600;
@@ -186,7 +186,7 @@ const props = defineProps<{
186
186
  /* Images */
187
187
  .blog-content :deep(img:not(.not-prose):not(.not-prose *)) {
188
188
  border: 1px solid var(--border);
189
- border-radius: var(--radius-sm);
189
+ border-radius: var(--uv-radius);
190
190
  box-shadow: var(--uv-shadow-sm);
191
191
  }
192
192
 
@@ -8,6 +8,7 @@ import BlogFAQSection from './BlogFAQSection.vue';
8
8
  import BlogInfoSection from './BlogInfoSection.vue';
9
9
  import BlogPostHeader from './BlogPostHeader.vue';
10
10
  import BlogSectionTitle from './BlogSectionTitle.vue';
11
+ import BlogSummary from './BlogSummary.vue';
11
12
  import BlogViewAllLink from './BlogViewAllLink.vue';
12
13
  import CategoryCard from './CategoryCard.vue';
13
14
  import PostCard from './PostCard.vue';
@@ -54,6 +55,9 @@ const postTags = computed(() => ((props.post.tags as string[] | undefined) || []
54
55
  :tags="postTags"
55
56
  :body="props.post.body" />
56
57
 
58
+ <!-- Short answer of the post (the summary field of the frontmatter) -->
59
+ <BlogSummary v-if="props.post.summary" :summary="props.post.summary" />
60
+
57
61
  <!-- Table of contents -->
58
62
  <TableOfContents :content="props.post" />
59
63
 
@@ -1,5 +1,7 @@
1
1
  <script setup lang="ts">
2
- // Props
2
+ import { SectionTitle } from '../section-title';
3
+
4
+ // Props (the blog sections use the shared section title, revealed on scroll)
3
5
  const props = withDefaults(defineProps<{
4
6
  title: string;
5
7
  description?: string;
@@ -9,25 +11,9 @@ const props = withDefaults(defineProps<{
9
11
  </script>
10
12
 
11
13
  <template>
12
- <div data-aos="blur-up" class="relative mb-6 flex flex-col gap-3 border-b border-border pb-4 sm:flex-row sm:items-end sm:justify-between md:mb-8">
13
- <!-- Primary segment at the start of the rule -->
14
- <span aria-hidden="true" class="absolute -bottom-px left-0 h-0.5 w-12 bg-primary" />
15
-
16
- <div class="min-w-0">
17
- <!-- Title -->
18
- <h2 class="text-xl font-semibold tracking-tight text-foreground md:text-2xl">
19
- {{ props.title }}
20
- </h2>
21
-
22
- <!-- Description -->
23
- <p v-if="props.description" class="mt-1 max-w-2xl text-xs leading-6 text-muted-foreground md:text-sm">
24
- {{ props.description }}
25
- </p>
26
- </div>
27
-
28
- <!-- Optional trailing action (for example a "View all" link) -->
29
- <div v-if="$slots.action" class="shrink-0">
14
+ <SectionTitle data-aos="blur-up" :title="props.title" :description="props.description">
15
+ <template v-if="$slots.action" #action>
30
16
  <slot name="action" />
31
- </div>
32
- </div>
17
+ </template>
18
+ </SectionTitle>
33
19
  </template>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import { useI18n } from 'vue-i18n';
3
+ import Idea01Icon from '@hugeicons/core-free-icons/Idea01Icon';
4
+ import { HugeiconsIcon } from '@hugeicons/vue';
5
+ import { Card } from '../ui/card';
6
+
7
+ // Props (the short answer of the post, from the summary field of its frontmatter)
8
+ const props = defineProps<{
9
+ summary: string;
10
+ }>();
11
+
12
+ const { t } = useI18n();
13
+ </script>
14
+
15
+ <template>
16
+ <Card data-slot="blog-summary" role="region" padding="none" class="mb-6" :aria-label="t('uiVintage.blog.inShort')">
17
+ <!-- Header (same layout as the table of contents accordion) -->
18
+ <div class="flex items-center gap-3 px-5 py-3.5">
19
+ <!-- Icon tile -->
20
+ <span class="flex size-7 shrink-0 items-center justify-center rounded border border-primary/30 text-primary">
21
+ <HugeiconsIcon :icon="Idea01Icon" class="size-3.5" />
22
+ </span>
23
+
24
+ <!-- Label -->
25
+ <p class="text-sm font-semibold text-foreground">{{ t('uiVintage.blog.inShort') }}</p>
26
+ </div>
27
+
28
+ <!-- Summary -->
29
+ <p class="border-t border-border px-5 py-4 text-sm leading-7 text-foreground md:text-base">{{ props.summary }}</p>
30
+ </Card>
31
+ </template>
@@ -3,6 +3,7 @@ export interface BlogPostData {
3
3
  path?: string;
4
4
  title?: string;
5
5
  description?: string;
6
+ summary?: string;
6
7
  image?: string;
7
8
  categoryText?: string;
8
9
  categorySlug?: string;
@@ -5,6 +5,7 @@ export { default as BlogInfoSection } from './BlogInfoSection.vue';
5
5
  export { default as BlogPostHeader } from './BlogPostHeader.vue';
6
6
  export { default as BlogHeaderSection } from './BlogHeaderSection.vue';
7
7
  export { default as BlogSectionTitle } from './BlogSectionTitle.vue';
8
+ export { default as BlogSummary } from './BlogSummary.vue';
8
9
  export { default as BlogTagsSection } from './BlogTagsSection.vue';
9
10
  export { default as CategoriesList } from './CategoriesList.vue';
10
11
  export { default as CategoriesSection } from './CategoriesSection.vue';
@@ -37,7 +37,7 @@ const handleRemove = () => {
37
37
  <span class="leading-none">{{ props.text }}</span>
38
38
 
39
39
  <!-- Remove button -->
40
- <button type="button" :aria-label="resolvedRemoveLabel" class="group/remove inline-flex size-4 cursor-pointer items-center justify-center rounded-sm opacity-70 [transition:opacity_150ms,background-color_150ms,scale_300ms_var(--ease-spring)] hover:bg-black/5 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/20 active:scale-75 dark:hover:bg-white/10" @click="handleRemove">
40
+ <button type="button" :aria-label="resolvedRemoveLabel" class="group/remove inline-flex size-4 cursor-pointer items-center justify-center rounded opacity-70 [transition:opacity_150ms,background-color_150ms,scale_300ms_var(--ease-spring)] hover:bg-black/5 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/20 active:scale-75 dark:hover:bg-white/10" @click="handleRemove">
41
41
  <HugeiconsIcon :icon="Cancel01Icon" class="size-3 transition-transform duration-300 ease-spring group-hover/remove:rotate-90" />
42
42
  </button>
43
43
  </div>
@@ -75,7 +75,7 @@ const logoAlt = computed(() => props.appName ? t('uiVintage.common.logo', { name
75
75
  <!-- Sidebar -->
76
76
  <Sidebar :collapsible="props.collapsible" :compact="props.compact">
77
77
  <!-- Sidebar header -->
78
- <SidebarHeader v-if="props.appName || lightLogo || $slots['sidebar-header']" class="h-16 justify-center border-b border-sidebar-border">
78
+ <SidebarHeader v-if="props.appName || lightLogo || $slots['sidebar-header']" class="h-16 justify-center shadow-[inset_0_-1px_0_var(--sidebar-border)]">
79
79
  <slot name="sidebar-header">
80
80
  <SidebarMenu>
81
81
  <SidebarMenuItem>
@@ -113,8 +113,8 @@ const logoAlt = computed(() => props.appName ? t('uiVintage.common.logo', { name
113
113
  <SidebarMenuItem v-for="item in section.items" :key="item.id">
114
114
  <SidebarMenuButton as-child :is-active="item.active" :class="cn('group/nav relative h-auto min-h-11 overflow-hidden rounded px-3 py-2.5 text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground data-[active=true]:bg-primary/10 data-[active=true]:text-foreground sm:px-3.5', 'before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:origin-center before:scale-y-0 before:bg-primary before:transition-transform before:duration-300 before:ease-spring data-[active=true]:before:scale-y-100', item.description ? 'items-start' : 'items-center')">
115
115
  <NuxtLinkLocale v-bind="getSidebarItemLinkProps(item)" :aria-current="item.active ? 'page' : undefined">
116
- <!-- Icon, centered on the capitals of the label (or of its first line, 1.25em tall): the line box alone centers it 0.08em too low, because of the space the font keeps for descenders (the size is important: the menu button shrinks its direct svg children to size-4) -->
117
- <HugeiconsIcon v-if="item.icon" :icon="item.icon" :stroke-width="1.8" :class="cn('size-5! shrink-0 opacity-90 transition-[opacity,color] duration-150 group-hover/nav:opacity-100 group-data-[active=true]/nav:text-primary', item.description ? 'mt-[calc((1.25em_-_20px)/2_-_0.08em)]' : 'self-center -translate-y-[0.08em]')" />
116
+ <!-- Icon, centered on the capitals of the label (or of its first line, 1.25em tall): the line box alone centers it 0.08em too low, because of the space the font keeps for descenders (the size is important: the menu button sizes its direct svg children itself, down to size-3.5 in compact mode) -->
117
+ <HugeiconsIcon v-if="item.icon" :icon="item.icon" :stroke-width="1.8" :class="cn('size-4! shrink-0 opacity-90 transition-[opacity,color] duration-150 group-hover/nav:opacity-100 group-data-[active=true]/nav:text-primary', item.description ? 'mt-[calc((1.25em_-_16px)/2_-_0.08em)]' : 'self-center -translate-y-[0.08em]')" />
118
118
 
119
119
  <!-- Label and optional description -->
120
120
  <div :class="cn('min-w-0 flex-1 text-left leading-tight', item.description ? 'grid' : 'flex items-center')">
@@ -149,8 +149,8 @@ const logoAlt = computed(() => props.appName ? t('uiVintage.common.logo', { name
149
149
  <!-- Background -->
150
150
  <BackgroundGrid v-if="props.showBackground" />
151
151
 
152
- <!-- Topbar -->
153
- <header :class="cn('sticky top-0 z-20 flex h-16 shrink-0 items-center gap-2 border-b border-sidebar-border bg-sidebar/80 px-4 backdrop-blur-md backdrop-saturate-150', props.topbarClass)">
152
+ <!-- Topbar (its bottom line is an inset shadow, so the whole 64px is free and the content centers on whole pixels) -->
153
+ <header :class="cn('sticky top-0 z-20 flex h-16 shrink-0 items-center gap-2 shadow-[inset_0_-1px_0_var(--sidebar-border)] bg-sidebar/80 px-4 backdrop-blur-md backdrop-saturate-150', props.topbarClass)">
154
154
  <!-- Sidebar trigger -->
155
155
  <SidebarTrigger class="-ml-1" />
156
156
 
@@ -2,14 +2,17 @@
2
2
  import type { HTMLAttributes } from 'vue';
3
3
  import { cn } from '../../lib/utils';
4
4
 
5
- // Props
6
- const props = defineProps<{
5
+ // Props (long text is clamped to 3 lines, wrap shows all of it, keeping its line breaks)
6
+ const props = withDefaults(defineProps<{
7
+ wrap?: boolean;
7
8
  class?: HTMLAttributes['class'];
8
- }>();
9
+ }>(), {
10
+ wrap: false
11
+ });
9
12
  </script>
10
13
 
11
14
  <template>
12
- <div data-slot="data-list-text" :class="cn('line-clamp-3', props.class)">
15
+ <div data-slot="data-list-text" :class="cn(props.wrap ? 'whitespace-pre-wrap break-words' : 'line-clamp-3', props.class)">
13
16
  <slot />
14
17
  </div>
15
18
  </template>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
2
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-[1.35rem] shrink-0 rounded-[2px]" viewBox="0 0 640 480" aria-hidden="true" focusable="false">
3
3
  <path fill="#ee1c25" d="M0 0h640v480H0z" />
4
4
  <path fill="#ff0" d="M76.8 177.6 120 48l43.2 129.6H48z" transform="matrix(.55555 0 0 .55555 53.333 53.333)" />
5
5
  <path fill="#ff0" d="M76.8 177.6 120 48l43.2 129.6H48z" transform="matrix(-.0955 -.15916 .15916 -.0955 240.3 48)" />