@nuxtseo/design-system 0.0.0-stage → 0.1.1

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 (219) hide show
  1. package/README.md +181 -2
  2. package/app/app.config.ts +592 -0
  3. package/app/component-attributes.d.ts +8 -0
  4. package/app/components/README.md +38 -0
  5. package/app/components/brand/UiBrandMark.vue +63 -0
  6. package/app/components/brand/UiModuleIcon.vue +45 -0
  7. package/app/components/brand/UiProLettering.vue +26 -0
  8. package/app/components/brand/UiRangeTerrain.vue +311 -0
  9. package/app/components/brand/range-glyphs.ts +18 -0
  10. package/app/components/container/BrowserTab.vue +145 -0
  11. package/app/components/container/IntegrationRow.vue +120 -0
  12. package/app/components/container/KnowDoRails.vue +40 -0
  13. package/app/components/container/RailPanel.vue +93 -0
  14. package/app/components/container/UiAlert.vue +120 -0
  15. package/app/components/container/UiAppShell.vue +165 -0
  16. package/app/components/container/UiCard.vue +204 -0
  17. package/app/components/container/UiDisclosure.vue +43 -0
  18. package/app/components/container/UiEmptyState.vue +102 -0
  19. package/app/components/container/UiErrorState.vue +61 -0
  20. package/app/components/container/UiLoadingState.vue +20 -0
  21. package/app/components/container/UiPageHeader.vue +95 -0
  22. package/app/components/container/UiPopover.vue +102 -0
  23. package/app/components/container/UiSampleDataOverlay.vue +47 -0
  24. package/app/components/container/UiSectionHeader.vue +83 -0
  25. package/app/components/container/UiSummaryStrip.vue +40 -0
  26. package/app/components/container/UiToast.vue +230 -0
  27. package/app/components/container/UiToaster.vue +271 -0
  28. package/app/components/container/UiWidgetState.vue +143 -0
  29. package/app/components/container/ui-nav-drawer.ts +48 -0
  30. package/app/components/data/ActionRail.vue +152 -0
  31. package/app/components/data/FacetStrip.vue +93 -0
  32. package/app/components/data/UiBarCell.vue +37 -0
  33. package/app/components/data/UiBarChart.vue +281 -0
  34. package/app/components/data/UiChartAnnotationControls.vue +74 -0
  35. package/app/components/data/UiChartAnnotations.vue +163 -0
  36. package/app/components/data/UiChartBrushOverlay.vue +96 -0
  37. package/app/components/data/UiChartDataReveal.vue +81 -0
  38. package/app/components/data/UiChartFrame.vue +123 -0
  39. package/app/components/data/UiChartTooltip.vue +81 -0
  40. package/app/components/data/UiCoverageNote.vue +90 -0
  41. package/app/components/data/UiCwvSparkline.vue +188 -0
  42. package/app/components/data/UiDataList.vue +276 -0
  43. package/app/components/data/UiDataTableSection.vue +233 -0
  44. package/app/components/data/UiDistributionBars.vue +164 -0
  45. package/app/components/data/UiDonutChart.vue +132 -0
  46. package/app/components/data/UiDoorwayColumn.vue +108 -0
  47. package/app/components/data/UiDoorwayGrid.vue +58 -0
  48. package/app/components/data/UiDoorwayRow.vue +83 -0
  49. package/app/components/data/UiEntitySummary.vue +261 -0
  50. package/app/components/data/UiFactsGrid.vue +107 -0
  51. package/app/components/data/UiImpactMeter.vue +33 -0
  52. package/app/components/data/UiIssueRow.vue +149 -0
  53. package/app/components/data/UiLineChart.vue +431 -0
  54. package/app/components/data/UiMetricBarPopover.vue +121 -0
  55. package/app/components/data/UiMetricLabel.vue +30 -0
  56. package/app/components/data/UiMetricStat.vue +73 -0
  57. package/app/components/data/UiMetricToggle.vue +156 -0
  58. package/app/components/data/UiMetricsRow.vue +64 -0
  59. package/app/components/data/UiPositionMetric.vue +53 -0
  60. package/app/components/data/UiRelatedFixes.vue +113 -0
  61. package/app/components/data/UiRichText.vue +325 -0
  62. package/app/components/data/UiScatterPlot.vue +144 -0
  63. package/app/components/data/UiSeverityMarker.vue +30 -0
  64. package/app/components/data/UiSparkColumns.vue +160 -0
  65. package/app/components/data/UiSparkline.vue +681 -0
  66. package/app/components/data/UiStat.vue +445 -0
  67. package/app/components/data/UiStats.vue +76 -0
  68. package/app/components/data/UiTable.vue +517 -0
  69. package/app/components/data/UiTableExport.vue +40 -0
  70. package/app/components/data/UiTableFrame.vue +149 -0
  71. package/app/components/data/UiTablePagination.vue +37 -0
  72. package/app/components/data/UiTableShell.vue +48 -0
  73. package/app/components/data/UiTableTd.vue +39 -0
  74. package/app/components/data/UiTableTh.vue +61 -0
  75. package/app/components/data/UiTopEntityStackChart.vue +393 -0
  76. package/app/components/data/cells/UiTableDash.vue +3 -0
  77. package/app/components/data/cells/UiTableHeaderCell.vue +72 -0
  78. package/app/components/data/cells/UiTableMetricCell.vue +72 -0
  79. package/app/components/data/cells/UiTablePathCell.vue +76 -0
  80. package/app/components/data/cells/UiTableScoreTile.vue +49 -0
  81. package/app/components/data/cells/UiTableTrendCell.vue +41 -0
  82. package/app/components/element/UiAtmosphere.vue +64 -0
  83. package/app/components/element/UiAuthHeading.vue +24 -0
  84. package/app/components/element/UiAuthProviders.vue +46 -0
  85. package/app/components/element/UiBrandIcon.vue +39 -0
  86. package/app/components/element/UiButton.vue +549 -0
  87. package/app/components/element/UiCelebration.vue +65 -0
  88. package/app/components/element/UiChannelChips.vue +62 -0
  89. package/app/components/element/UiChatQuickLink.vue +319 -0
  90. package/app/components/element/UiChip.vue +101 -0
  91. package/app/components/element/UiConfirmDialog.vue +84 -0
  92. package/app/components/element/UiCopyButton.vue +90 -0
  93. package/app/components/element/UiDitherAvatar.vue +177 -0
  94. package/app/components/element/UiDotLabel.vue +19 -0
  95. package/app/components/element/UiEjectMenu.vue +181 -0
  96. package/app/components/element/UiEjectOverlay.vue +29 -0
  97. package/app/components/element/UiFavicon.vue +235 -0
  98. package/app/components/element/UiFaviconStack.vue +84 -0
  99. package/app/components/element/UiFilterMenu.vue +324 -0
  100. package/app/components/element/UiFocusedSurface.vue +129 -0
  101. package/app/components/element/UiHealthDot.vue +32 -0
  102. package/app/components/element/UiHelpLabel.vue +28 -0
  103. package/app/components/element/UiIcon.vue +22 -0
  104. package/app/components/element/UiInput.vue +35 -0
  105. package/app/components/element/UiLogoPlausible.vue +12 -0
  106. package/app/components/element/UiLogoPro.vue +25 -0
  107. package/app/components/element/UiLogoUmami.vue +12 -0
  108. package/app/components/element/UiNavIcon.vue +107 -0
  109. package/app/components/element/UiNavList.vue +156 -0
  110. package/app/components/element/UiOnboardingAnswers.vue +127 -0
  111. package/app/components/element/UiOverflowCount.vue +31 -0
  112. package/app/components/element/UiProBadge.vue +19 -0
  113. package/app/components/element/UiProgressCircle.vue +79 -0
  114. package/app/components/element/UiProgressPercent.vue +35 -0
  115. package/app/components/element/UiRelativeTime.vue +58 -0
  116. package/app/components/element/UiScrollFade.vue +16 -0
  117. package/app/components/element/UiSelect.vue +68 -0
  118. package/app/components/element/UiSeverityDot.vue +27 -0
  119. package/app/components/element/UiSiteFavicon.vue +24 -0
  120. package/app/components/element/UiSkeleton.vue +148 -0
  121. package/app/components/element/UiSlotText.vue +65 -0
  122. package/app/components/element/UiSourceLogos.vue +113 -0
  123. package/app/components/element/UiSourceRail.vue +71 -0
  124. package/app/components/element/UiStaticTerminal.vue +107 -0
  125. package/app/components/element/UiStatusBadge.vue +62 -0
  126. package/app/components/element/UiSyncDot.vue +46 -0
  127. package/app/components/element/UiTabs.vue +245 -0
  128. package/app/components/element/UiTogglePill.vue +86 -0
  129. package/app/components/element/UiTooltip.vue +390 -0
  130. package/app/components/element/UiTooltipGrid.vue +457 -0
  131. package/app/components/element/UiTrend.vue +184 -0
  132. package/app/components/element/UiTrialBenefits.vue +71 -0
  133. package/app/components/element/UiUrlLabel.vue +75 -0
  134. package/app/components/element/UiValenceDot.vue +37 -0
  135. package/app/components/element/UiWizardModal.vue +284 -0
  136. package/app/components/element/UiWizardNav.vue +59 -0
  137. package/app/components/element/UiWizardProgress.vue +78 -0
  138. package/app/components/element/UiWizardShell.vue +43 -0
  139. package/app/components/element/eject-menu.ts +359 -0
  140. package/app/components/element/eject-tracker.ts +34 -0
  141. package/app/components/element/onboarding-answers.ts +93 -0
  142. package/app/components/element/onboarding-intents.ts +19 -0
  143. package/app/composables/dataVizColors.ts +350 -0
  144. package/app/composables/formatting.ts +252 -0
  145. package/app/composables/semanticColors.ts +80 -0
  146. package/app/composables/useActivityOverlay.ts +55 -0
  147. package/app/composables/useAppShellNav.ts +21 -0
  148. package/app/composables/useChartBrush.ts +221 -0
  149. package/app/composables/useChartHover.ts +153 -0
  150. package/app/composables/useChartTickPlan.ts +186 -0
  151. package/app/composables/useCloseOnNavigation.ts +7 -0
  152. package/app/composables/useConfirm.ts +38 -0
  153. package/app/composables/useEdgeFade.ts +37 -0
  154. package/app/composables/useEject.ts +69 -0
  155. package/app/composables/useEntranceMotion.ts +22 -0
  156. package/app/composables/useFaviconBacking.ts +164 -0
  157. package/app/composables/useGridCols.ts +39 -0
  158. package/app/composables/useIssueOverlay.ts +43 -0
  159. package/app/composables/useOnboardingService.ts +106 -0
  160. package/app/composables/useOpenedOnce.ts +19 -0
  161. package/app/composables/useScrollSpy.ts +178 -0
  162. package/app/composables/useSquircleFallback.ts +21 -0
  163. package/app/composables/useWizardRoute.ts +123 -0
  164. package/app/plugins/favicon.ts +15 -0
  165. package/app/plugins/lazy-motion.ts +22 -0
  166. package/app/plugins/selection-highlight.client.ts +176 -0
  167. package/app/shared/cli.ts +10 -0
  168. package/app/shared/error-state.ts +150 -0
  169. package/app/shared/icons.ts +1 -0
  170. package/app/shared/motion.ts +1 -0
  171. package/app/shared/nav.ts +38 -0
  172. package/app/shared/navigation.ts +5 -0
  173. package/app/shared/static-terminal.ts +14 -0
  174. package/app/shared/table-export.ts +36 -0
  175. package/app/shared/table.ts +105 -0
  176. package/app/shared/ui-icons.ts +1 -0
  177. package/app/shared/view-state.ts +85 -0
  178. package/app/shared/widget-failure.ts +126 -0
  179. package/app/utils/app-favicon.ts +64 -0
  180. package/app/utils/chartAnnotations.ts +326 -0
  181. package/app/utils/countries.ts +337 -0
  182. package/app/utils/export-download.ts +100 -0
  183. package/app/utils/favicon-fallback.ts +12 -0
  184. package/app/utils/icon-color.ts +105 -0
  185. package/app/utils/number.ts +74 -0
  186. package/app/utils/render-link.ts +14 -0
  187. package/app/utils/slot-text.ts +18 -0
  188. package/app/utils/squircle.ts +154 -0
  189. package/app/utils/topEntityStack.ts +197 -0
  190. package/app/utils/vue.ts +57 -0
  191. package/build/design-system.ts +69 -0
  192. package/build/external-links.ts +38 -0
  193. package/build/fonts.ts +39 -0
  194. package/build/icon-server-bundle.ts +276 -0
  195. package/build/runtime/icon-server-bundle.ts +29 -0
  196. package/css/global.css +1896 -0
  197. package/nuxt.config.ts +143 -0
  198. package/package.json +93 -3
  199. package/public/fonts/README.md +33 -0
  200. package/public/fonts/fira-code-OFL.txt +93 -0
  201. package/public/fonts/fira-code-var-latin.woff2 +0 -0
  202. package/public/fonts/hubot-sans-OFL.txt +93 -0
  203. package/public/fonts/hubot-sans-var-latin.woff2 +0 -0
  204. package/server/api/favicon.get.ts +122 -0
  205. package/server/utils/favicon-source.ts +51 -0
  206. package/shared/contracts/feedback.ts +42 -0
  207. package/shared/contracts/index.ts +1 -0
  208. package/shared/delivery-channels.ts +53 -0
  209. package/shared/icons/index.ts +143 -0
  210. package/shared/icons/registry.ts +347 -0
  211. package/shared/icons/ui-slots.ts +58 -0
  212. package/shared/markdown.ts +44 -0
  213. package/shared/motion.ts +120 -0
  214. package/shared/rangi.ts +60 -0
  215. package/shared/rich-text.ts +34 -0
  216. package/shared/ui-icons.ts +6 -0
  217. package/shared/utils/urls.ts +64 -0
  218. package/types/index.ts +1 -0
  219. package/types/ui.ts +339 -0
@@ -0,0 +1,149 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ name = 'UiTableFrame',
4
+ bordered = false,
5
+ rowHover = false,
6
+ scrolled = false,
7
+ // No default on purpose. `role="region"` is a LANDMARK: several unlabelled
8
+ // tables on one page would each announce the same generic name, which is the
9
+ // exact failure landmark navigation exists to avoid. Unnamed frames fall back
10
+ // to `role="group"`, which stays keyboard-scrollable without polluting the
11
+ // landmark rotor.
12
+ scrollLabel,
13
+ } = defineProps<{
14
+ name?: string
15
+ bordered?: boolean
16
+ rowHover?: boolean
17
+ scrolled?: boolean
18
+ scrollLabel?: string
19
+ }>()
20
+ </script>
21
+
22
+ <template>
23
+ <div
24
+ :data-ui="name"
25
+ :data-bordered="bordered || undefined"
26
+ :data-row-hover="rowHover || undefined"
27
+ :data-scrolled="scrolled || undefined"
28
+ :aria-label="scrollLabel"
29
+ :role="scrollLabel ? 'region' : 'group'"
30
+ tabindex="0"
31
+ class="w-full overflow-x-auto overscroll-x-contain"
32
+ :class="bordered ? 'rounded-xl border border-default bg-default' : ''"
33
+ >
34
+ <slot />
35
+ </div>
36
+ </template>
37
+
38
+ <style scoped>
39
+ :deep(table) {
40
+ width: 100%;
41
+ border-collapse: collapse;
42
+ }
43
+
44
+ :deep(thead) {
45
+ position: sticky;
46
+ inset-block-start: 0;
47
+ z-index: 10;
48
+ background: var(--ui-bg);
49
+ }
50
+
51
+ :deep(thead th) {
52
+ border-block-end: 1px solid var(--ui-border);
53
+ background: var(--ui-bg);
54
+ }
55
+
56
+ :deep(tbody > tr.spacer) {
57
+ block-size: 0.25rem;
58
+ }
59
+
60
+ :deep(tbody > tr.spacer + tr > :is(th, td)) {
61
+ border-block-start: 4px solid transparent;
62
+ background-clip: padding-box;
63
+ }
64
+
65
+ :deep(tbody > tr:not(.spacer):not(:last-child) > :is(th, td)) {
66
+ border-block-end: 1px solid var(--ui-border);
67
+ }
68
+
69
+ [data-row-hover] :deep(tbody > tr:not(.spacer):not(.expanded-row) > :is(th, td)) {
70
+ transition: background-color 140ms ease-out;
71
+ }
72
+
73
+ [data-row-hover] :deep(tbody > tr:not(.spacer):not(.expanded-row):not([data-state='selected']):hover > :is(th, td)),
74
+ [data-row-hover] :deep(tbody > tr:not(.spacer):not(.expanded-row):not([data-state='selected']):focus-visible > :is(th, td)) {
75
+ background: var(--ui-bg-muted);
76
+ }
77
+
78
+ [data-row-hover] :deep(tbody > tr:not(.spacer):not(.expanded-row):not([data-state='selected']):hover > :is(th, td):first-child),
79
+ [data-row-hover] :deep(tbody > tr:not(.spacer):not(.expanded-row):not([data-state='selected']):focus-visible > :is(th, td):first-child) {
80
+ border-start-start-radius: 0.5rem;
81
+ border-end-start-radius: 0.5rem;
82
+ }
83
+
84
+ [data-row-hover] :deep(tbody > tr:not(.spacer):not(.expanded-row):not([data-state='selected']):hover > :is(th, td):last-child),
85
+ [data-row-hover] :deep(tbody > tr:not(.spacer):not(.expanded-row):not([data-state='selected']):focus-visible > :is(th, td):last-child) {
86
+ border-start-end-radius: 0.5rem;
87
+ border-end-end-radius: 0.5rem;
88
+ }
89
+
90
+ [data-row-hover] :deep(tbody > tr.spacer + tr:hover > :is(th, td)),
91
+ [data-row-hover] :deep(tbody > tr.spacer + tr:focus-visible > :is(th, td)) {
92
+ background-clip: border-box;
93
+ }
94
+
95
+ /*
96
+ * One continuous selection band: the fill runs the whole row and only its two
97
+ * end cells round, so the row reads as a single shape rather than a strip of
98
+ * separately rounded cells. Same geometry as the hover treatment above, which
99
+ * is what makes hovering a selected row a no-op instead of a shape change.
100
+ */
101
+ :deep(tbody > tr[data-state='selected'] > :is(th, td)) {
102
+ background: color-mix(in srgb, var(--ui-primary) 12%, var(--ui-bg));
103
+ border-radius: 0;
104
+ }
105
+
106
+ :deep(tbody > tr[data-state='selected'] > :is(th, td):first-child) {
107
+ border-start-start-radius: 0.5rem;
108
+ border-end-start-radius: 0.5rem;
109
+ }
110
+
111
+ :deep(tbody > tr[data-state='selected'] > :is(th, td):last-child) {
112
+ border-start-end-radius: 0.5rem;
113
+ border-end-end-radius: 0.5rem;
114
+ }
115
+
116
+ :deep(table[data-size='xs'] tbody > tr:not(.spacer):not(.expanded-row) > :is(th, td)) {
117
+ block-size: 2.75rem;
118
+ padding-block: 0.25rem;
119
+ }
120
+
121
+ :deep(table[data-size='sm'] tbody > tr:not(.spacer):not(.expanded-row) > :is(th, td)),
122
+ :deep(table[data-size='md'] tbody > tr:not(.spacer):not(.expanded-row) > :is(th, td)) {
123
+ block-size: 2.75rem;
124
+ }
125
+
126
+ [data-scrolled] :deep(thead) {
127
+ box-shadow: var(--elevation-overlay);
128
+ }
129
+
130
+ :deep(thead) {
131
+ transition: box-shadow 200ms ease-out;
132
+ }
133
+
134
+ /*
135
+ * Rows shrink on a PRECISE pointer, not on a wide screen. A tablet is both wide
136
+ * and touched, and in a click-through table the row is the primary target — a
137
+ * 40px row on a touch device is under the 44px floor.
138
+ */
139
+ @media (min-width: 40rem) and (pointer: fine) {
140
+ :deep(table[data-size='xs'] tbody > tr:not(.spacer):not(.expanded-row) > :is(th, td)) {
141
+ block-size: 2rem;
142
+ }
143
+
144
+ :deep(table[data-size='sm'] tbody > tr:not(.spacer):not(.expanded-row) > :is(th, td)),
145
+ :deep(table[data-size='md'] tbody > tr:not(.spacer):not(.expanded-row) > :is(th, td)) {
146
+ block-size: 2.5rem;
147
+ }
148
+ }
149
+ </style>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import { UPagination } from '#components'
3
+
4
+ const { total, pageSize } = defineProps<{
5
+ total: number
6
+ pageSize: number
7
+ }>()
8
+
9
+ const page = defineModel<number>('page', { required: true })
10
+
11
+ // `justify-center` keeps the icon or number centred once the target outgrows its content.
12
+ const TARGET = 'min-h-11 min-w-11 justify-center sm:min-h-8 sm:min-w-8'
13
+ const paginationUi = {
14
+ first: TARGET,
15
+ prev: TARGET,
16
+ item: TARGET,
17
+ next: TARGET,
18
+ last: TARGET,
19
+ } as const
20
+ </script>
21
+
22
+ <template>
23
+ <div class="flex flex-col items-stretch gap-3 pt-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
24
+ <p class="text-center text-sm text-muted sm:text-left">
25
+ <slot />
26
+ </p>
27
+ <UPagination
28
+ v-model:page="page"
29
+ class="self-center sm:self-auto"
30
+ size="sm"
31
+ :items-per-page="pageSize"
32
+ :total="total"
33
+ :sibling-count="1"
34
+ :ui="paginationUi"
35
+ />
36
+ </div>
37
+ </template>
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ import type { UiTableSize } from '../../shared/table'
3
+ import { UiTableFrame } from '#components'
4
+
5
+ const {
6
+ size = 'md',
7
+ rowHover = false,
8
+ bordered = false,
9
+ label,
10
+ loading = false,
11
+ tableClass,
12
+ } = defineProps<{
13
+ size?: UiTableSize
14
+ /** Enable hover gradient on body rows. */
15
+ rowHover?: boolean
16
+ /** Wrap the table in a bordered/rounded container (use false when caller owns the chrome). */
17
+ bordered?: boolean
18
+ /** Accessible name for the table. Rendered as a visually-hidden <caption>. */
19
+ label: string
20
+ loading?: boolean
21
+ tableClass?: string
22
+ }>()
23
+ </script>
24
+
25
+ <template>
26
+ <UiTableFrame
27
+ name="UiTableShell"
28
+ :bordered="bordered"
29
+ :row-hover="rowHover"
30
+ :scroll-label="`${label} table scroll area`"
31
+ >
32
+ <table class="w-full" :class="tableClass" :data-size="size" :aria-busy="loading || undefined">
33
+ <caption v-if="label" class="sr-only">
34
+ {{ label }}
35
+ </caption>
36
+ <thead v-if="$slots.head">
37
+ <tr class="h-10">
38
+ <slot name="head" />
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ <tr class="spacer" aria-hidden="true" />
43
+ <slot />
44
+ </tbody>
45
+ <slot name="tfoot" />
46
+ </table>
47
+ </UiTableFrame>
48
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ import type { UiTableCellProps, UiTableSize } from '../../shared/table'
3
+ import { computed } from 'vue'
4
+ import { uiTableCellSizeClass, uiTableVisibleFromClass } from '../../shared/table'
5
+
6
+ const {
7
+ align = 'left',
8
+ size = 'md',
9
+ noPadding = false,
10
+ numeric = false,
11
+ rowHeader = false,
12
+ visibleFrom,
13
+ } = defineProps<UiTableCellProps & {
14
+ size?: UiTableSize
15
+ noPadding?: boolean
16
+ rowHeader?: boolean
17
+ }>()
18
+
19
+ const resolvedAlign = computed(() => numeric ? 'right' : align)
20
+ const textAlign = computed(() => resolvedAlign.value === 'right' ? 'text-right' : resolvedAlign.value === 'center' ? 'text-center' : 'text-left')
21
+ const padClass = computed(() => noPadding ? '' : 'px-3')
22
+ </script>
23
+
24
+ <template>
25
+ <component
26
+ :is="rowHeader ? 'th' : 'td'"
27
+ :scope="rowHeader ? 'row' : undefined"
28
+ class="text-xs font-normal text-default relative"
29
+ :class="[
30
+ uiTableCellSizeClass[size],
31
+ padClass,
32
+ textAlign,
33
+ numeric ? 'tabular-nums' : '',
34
+ visibleFrom ? uiTableVisibleFromClass[visibleFrom] : '',
35
+ ]"
36
+ >
37
+ <slot />
38
+ </component>
39
+ </template>
@@ -0,0 +1,61 @@
1
+ <script setup lang="ts">
2
+ import type { UiTableCellProps } from '../../shared/table'
3
+ import { computed } from 'vue'
4
+ import { UiIcon } from '#components'
5
+ import { uiTableVisibleFromClass } from '../../shared/table'
6
+
7
+ const {
8
+ align = 'left',
9
+ sortable = false,
10
+ noPadding = false,
11
+ numeric = false,
12
+ sortDirection = false,
13
+ ariaLabel,
14
+ visibleFrom,
15
+ } = defineProps<UiTableCellProps & {
16
+ sortable?: boolean
17
+ noPadding?: boolean
18
+ sortDirection?: 'asc' | 'desc' | false
19
+ ariaLabel?: string
20
+ }>()
21
+
22
+ const emit = defineEmits<{ sort: [event: MouseEvent] }>()
23
+
24
+ const resolvedAlign = computed(() => numeric ? 'right' : align)
25
+ const justify = computed(() => resolvedAlign.value === 'right' ? 'justify-end' : resolvedAlign.value === 'center' ? 'justify-center' : 'justify-start')
26
+ // Vertical padding lives here, not on the consumer's <tr>. UiTable sets `h-10`
27
+ // on its own header row, but this primitive exists for hand-rolled tables where
28
+ // there is no such row — without `py-*` a 10px label's line box is the entire
29
+ // header height and the text sits jammed against the bottom border.
30
+ //
31
+ // A sortable header moves its padding onto the inner button so the whole cell
32
+ // is the click target. The button carries the same `px-3` as UiTableTd, or a
33
+ // sortable numeric header sits 4px inboard of its own column's digits.
34
+ const padClass = computed(() => noPadding || sortable ? '' : 'px-3 py-2.5')
35
+ const sortIcon = computed(() => sortDirection ? 'collapse' : 'sort')
36
+ const sortIconClass = computed(() => sortDirection === 'desc' ? 'rotate-180' : '')
37
+ </script>
38
+
39
+ <template>
40
+ <th
41
+ scope="col"
42
+ class="text-label text-left whitespace-nowrap border-b border-default bg-default"
43
+ :class="[padClass, numeric ? 'text-right tabular-nums' : '', visibleFrom ? uiTableVisibleFromClass[visibleFrom] : '']"
44
+ :aria-sort="sortable ? sortDirection === 'asc' ? 'ascending' : sortDirection === 'desc' ? 'descending' : 'none' : undefined"
45
+ >
46
+ <button
47
+ v-if="sortable"
48
+ type="button"
49
+ class="flex min-h-11 w-full items-center gap-1 rounded px-3 py-2.5 text-label text-muted transition-colors hover:text-default focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary sm:min-h-0"
50
+ :class="justify"
51
+ :aria-label="ariaLabel"
52
+ @click="emit('sort', $event)"
53
+ >
54
+ <slot />
55
+ <UiIcon :name="sortIcon" class="size-3 text-dimmed transition-transform duration-150" :class="sortIconClass" aria-hidden="true" />
56
+ </button>
57
+ <div v-else class="flex items-center gap-1" :class="justify">
58
+ <slot />
59
+ </div>
60
+ </th>
61
+ </template>