@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.
- package/README.md +181 -2
- package/app/app.config.ts +592 -0
- package/app/component-attributes.d.ts +8 -0
- package/app/components/README.md +38 -0
- package/app/components/brand/UiBrandMark.vue +63 -0
- package/app/components/brand/UiModuleIcon.vue +45 -0
- package/app/components/brand/UiProLettering.vue +26 -0
- package/app/components/brand/UiRangeTerrain.vue +311 -0
- package/app/components/brand/range-glyphs.ts +18 -0
- package/app/components/container/BrowserTab.vue +145 -0
- package/app/components/container/IntegrationRow.vue +120 -0
- package/app/components/container/KnowDoRails.vue +40 -0
- package/app/components/container/RailPanel.vue +93 -0
- package/app/components/container/UiAlert.vue +120 -0
- package/app/components/container/UiAppShell.vue +165 -0
- package/app/components/container/UiCard.vue +204 -0
- package/app/components/container/UiDisclosure.vue +43 -0
- package/app/components/container/UiEmptyState.vue +102 -0
- package/app/components/container/UiErrorState.vue +61 -0
- package/app/components/container/UiLoadingState.vue +20 -0
- package/app/components/container/UiPageHeader.vue +95 -0
- package/app/components/container/UiPopover.vue +102 -0
- package/app/components/container/UiSampleDataOverlay.vue +47 -0
- package/app/components/container/UiSectionHeader.vue +83 -0
- package/app/components/container/UiSummaryStrip.vue +40 -0
- package/app/components/container/UiToast.vue +230 -0
- package/app/components/container/UiToaster.vue +271 -0
- package/app/components/container/UiWidgetState.vue +143 -0
- package/app/components/container/ui-nav-drawer.ts +48 -0
- package/app/components/data/ActionRail.vue +152 -0
- package/app/components/data/FacetStrip.vue +93 -0
- package/app/components/data/UiBarCell.vue +37 -0
- package/app/components/data/UiBarChart.vue +281 -0
- package/app/components/data/UiChartAnnotationControls.vue +74 -0
- package/app/components/data/UiChartAnnotations.vue +163 -0
- package/app/components/data/UiChartBrushOverlay.vue +96 -0
- package/app/components/data/UiChartDataReveal.vue +81 -0
- package/app/components/data/UiChartFrame.vue +123 -0
- package/app/components/data/UiChartTooltip.vue +81 -0
- package/app/components/data/UiCoverageNote.vue +90 -0
- package/app/components/data/UiCwvSparkline.vue +188 -0
- package/app/components/data/UiDataList.vue +276 -0
- package/app/components/data/UiDataTableSection.vue +233 -0
- package/app/components/data/UiDistributionBars.vue +164 -0
- package/app/components/data/UiDonutChart.vue +132 -0
- package/app/components/data/UiDoorwayColumn.vue +108 -0
- package/app/components/data/UiDoorwayGrid.vue +58 -0
- package/app/components/data/UiDoorwayRow.vue +83 -0
- package/app/components/data/UiEntitySummary.vue +261 -0
- package/app/components/data/UiFactsGrid.vue +107 -0
- package/app/components/data/UiImpactMeter.vue +33 -0
- package/app/components/data/UiIssueRow.vue +149 -0
- package/app/components/data/UiLineChart.vue +431 -0
- package/app/components/data/UiMetricBarPopover.vue +121 -0
- package/app/components/data/UiMetricLabel.vue +30 -0
- package/app/components/data/UiMetricStat.vue +73 -0
- package/app/components/data/UiMetricToggle.vue +156 -0
- package/app/components/data/UiMetricsRow.vue +64 -0
- package/app/components/data/UiPositionMetric.vue +53 -0
- package/app/components/data/UiRelatedFixes.vue +113 -0
- package/app/components/data/UiRichText.vue +325 -0
- package/app/components/data/UiScatterPlot.vue +144 -0
- package/app/components/data/UiSeverityMarker.vue +30 -0
- package/app/components/data/UiSparkColumns.vue +160 -0
- package/app/components/data/UiSparkline.vue +681 -0
- package/app/components/data/UiStat.vue +445 -0
- package/app/components/data/UiStats.vue +76 -0
- package/app/components/data/UiTable.vue +517 -0
- package/app/components/data/UiTableExport.vue +40 -0
- package/app/components/data/UiTableFrame.vue +149 -0
- package/app/components/data/UiTablePagination.vue +37 -0
- package/app/components/data/UiTableShell.vue +48 -0
- package/app/components/data/UiTableTd.vue +39 -0
- package/app/components/data/UiTableTh.vue +61 -0
- package/app/components/data/UiTopEntityStackChart.vue +393 -0
- package/app/components/data/cells/UiTableDash.vue +3 -0
- package/app/components/data/cells/UiTableHeaderCell.vue +72 -0
- package/app/components/data/cells/UiTableMetricCell.vue +72 -0
- package/app/components/data/cells/UiTablePathCell.vue +76 -0
- package/app/components/data/cells/UiTableScoreTile.vue +49 -0
- package/app/components/data/cells/UiTableTrendCell.vue +41 -0
- package/app/components/element/UiAtmosphere.vue +64 -0
- package/app/components/element/UiAuthHeading.vue +24 -0
- package/app/components/element/UiAuthProviders.vue +46 -0
- package/app/components/element/UiBrandIcon.vue +39 -0
- package/app/components/element/UiButton.vue +549 -0
- package/app/components/element/UiCelebration.vue +65 -0
- package/app/components/element/UiChannelChips.vue +62 -0
- package/app/components/element/UiChatQuickLink.vue +319 -0
- package/app/components/element/UiChip.vue +101 -0
- package/app/components/element/UiConfirmDialog.vue +84 -0
- package/app/components/element/UiCopyButton.vue +90 -0
- package/app/components/element/UiDitherAvatar.vue +177 -0
- package/app/components/element/UiDotLabel.vue +19 -0
- package/app/components/element/UiEjectMenu.vue +181 -0
- package/app/components/element/UiEjectOverlay.vue +29 -0
- package/app/components/element/UiFavicon.vue +235 -0
- package/app/components/element/UiFaviconStack.vue +84 -0
- package/app/components/element/UiFilterMenu.vue +324 -0
- package/app/components/element/UiFocusedSurface.vue +129 -0
- package/app/components/element/UiHealthDot.vue +32 -0
- package/app/components/element/UiHelpLabel.vue +28 -0
- package/app/components/element/UiIcon.vue +22 -0
- package/app/components/element/UiInput.vue +35 -0
- package/app/components/element/UiLogoPlausible.vue +12 -0
- package/app/components/element/UiLogoPro.vue +25 -0
- package/app/components/element/UiLogoUmami.vue +12 -0
- package/app/components/element/UiNavIcon.vue +107 -0
- package/app/components/element/UiNavList.vue +156 -0
- package/app/components/element/UiOnboardingAnswers.vue +127 -0
- package/app/components/element/UiOverflowCount.vue +31 -0
- package/app/components/element/UiProBadge.vue +19 -0
- package/app/components/element/UiProgressCircle.vue +79 -0
- package/app/components/element/UiProgressPercent.vue +35 -0
- package/app/components/element/UiRelativeTime.vue +58 -0
- package/app/components/element/UiScrollFade.vue +16 -0
- package/app/components/element/UiSelect.vue +68 -0
- package/app/components/element/UiSeverityDot.vue +27 -0
- package/app/components/element/UiSiteFavicon.vue +24 -0
- package/app/components/element/UiSkeleton.vue +148 -0
- package/app/components/element/UiSlotText.vue +65 -0
- package/app/components/element/UiSourceLogos.vue +113 -0
- package/app/components/element/UiSourceRail.vue +71 -0
- package/app/components/element/UiStaticTerminal.vue +107 -0
- package/app/components/element/UiStatusBadge.vue +62 -0
- package/app/components/element/UiSyncDot.vue +46 -0
- package/app/components/element/UiTabs.vue +245 -0
- package/app/components/element/UiTogglePill.vue +86 -0
- package/app/components/element/UiTooltip.vue +390 -0
- package/app/components/element/UiTooltipGrid.vue +457 -0
- package/app/components/element/UiTrend.vue +184 -0
- package/app/components/element/UiTrialBenefits.vue +71 -0
- package/app/components/element/UiUrlLabel.vue +75 -0
- package/app/components/element/UiValenceDot.vue +37 -0
- package/app/components/element/UiWizardModal.vue +284 -0
- package/app/components/element/UiWizardNav.vue +59 -0
- package/app/components/element/UiWizardProgress.vue +78 -0
- package/app/components/element/UiWizardShell.vue +43 -0
- package/app/components/element/eject-menu.ts +359 -0
- package/app/components/element/eject-tracker.ts +34 -0
- package/app/components/element/onboarding-answers.ts +93 -0
- package/app/components/element/onboarding-intents.ts +19 -0
- package/app/composables/dataVizColors.ts +350 -0
- package/app/composables/formatting.ts +252 -0
- package/app/composables/semanticColors.ts +80 -0
- package/app/composables/useActivityOverlay.ts +55 -0
- package/app/composables/useAppShellNav.ts +21 -0
- package/app/composables/useChartBrush.ts +221 -0
- package/app/composables/useChartHover.ts +153 -0
- package/app/composables/useChartTickPlan.ts +186 -0
- package/app/composables/useCloseOnNavigation.ts +7 -0
- package/app/composables/useConfirm.ts +38 -0
- package/app/composables/useEdgeFade.ts +37 -0
- package/app/composables/useEject.ts +69 -0
- package/app/composables/useEntranceMotion.ts +22 -0
- package/app/composables/useFaviconBacking.ts +164 -0
- package/app/composables/useGridCols.ts +39 -0
- package/app/composables/useIssueOverlay.ts +43 -0
- package/app/composables/useOnboardingService.ts +106 -0
- package/app/composables/useOpenedOnce.ts +19 -0
- package/app/composables/useScrollSpy.ts +178 -0
- package/app/composables/useSquircleFallback.ts +21 -0
- package/app/composables/useWizardRoute.ts +123 -0
- package/app/plugins/favicon.ts +15 -0
- package/app/plugins/lazy-motion.ts +22 -0
- package/app/plugins/selection-highlight.client.ts +176 -0
- package/app/shared/cli.ts +10 -0
- package/app/shared/error-state.ts +150 -0
- package/app/shared/icons.ts +1 -0
- package/app/shared/motion.ts +1 -0
- package/app/shared/nav.ts +38 -0
- package/app/shared/navigation.ts +5 -0
- package/app/shared/static-terminal.ts +14 -0
- package/app/shared/table-export.ts +36 -0
- package/app/shared/table.ts +105 -0
- package/app/shared/ui-icons.ts +1 -0
- package/app/shared/view-state.ts +85 -0
- package/app/shared/widget-failure.ts +126 -0
- package/app/utils/app-favicon.ts +64 -0
- package/app/utils/chartAnnotations.ts +326 -0
- package/app/utils/countries.ts +337 -0
- package/app/utils/export-download.ts +100 -0
- package/app/utils/favicon-fallback.ts +12 -0
- package/app/utils/icon-color.ts +105 -0
- package/app/utils/number.ts +74 -0
- package/app/utils/render-link.ts +14 -0
- package/app/utils/slot-text.ts +18 -0
- package/app/utils/squircle.ts +154 -0
- package/app/utils/topEntityStack.ts +197 -0
- package/app/utils/vue.ts +57 -0
- package/build/design-system.ts +69 -0
- package/build/external-links.ts +38 -0
- package/build/fonts.ts +39 -0
- package/build/icon-server-bundle.ts +276 -0
- package/build/runtime/icon-server-bundle.ts +29 -0
- package/css/global.css +1896 -0
- package/nuxt.config.ts +143 -0
- package/package.json +93 -3
- package/public/fonts/README.md +33 -0
- package/public/fonts/fira-code-OFL.txt +93 -0
- package/public/fonts/fira-code-var-latin.woff2 +0 -0
- package/public/fonts/hubot-sans-OFL.txt +93 -0
- package/public/fonts/hubot-sans-var-latin.woff2 +0 -0
- package/server/api/favicon.get.ts +122 -0
- package/server/utils/favicon-source.ts +51 -0
- package/shared/contracts/feedback.ts +42 -0
- package/shared/contracts/index.ts +1 -0
- package/shared/delivery-channels.ts +53 -0
- package/shared/icons/index.ts +143 -0
- package/shared/icons/registry.ts +347 -0
- package/shared/icons/ui-slots.ts +58 -0
- package/shared/markdown.ts +44 -0
- package/shared/motion.ts +120 -0
- package/shared/rangi.ts +60 -0
- package/shared/rich-text.ts +34 -0
- package/shared/ui-icons.ts +6 -0
- package/shared/utils/urls.ts +64 -0
- package/types/index.ts +1 -0
- package/types/ui.ts +339 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { VNodeChild } from 'vue'
|
|
3
|
+
import { computed, nextTick, onMounted, onUpdated, ref, shallowRef, useSlots, watch } from 'vue'
|
|
4
|
+
import { TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger, UiIcon } from '#components'
|
|
5
|
+
/**
|
|
6
|
+
* UiTooltip
|
|
7
|
+
*
|
|
8
|
+
* Built directly on reka-ui's Tooltip primitives. Skips Nuxt UI's `UTooltip`
|
|
9
|
+
* wrapper because that layer has historically been buggy in dense scenarios
|
|
10
|
+
* (heatmap cells, packed grids).
|
|
11
|
+
*
|
|
12
|
+
* The global `TooltipProvider` is set up once in `app/app.vue`
|
|
13
|
+
* (`<UApp :tooltip="{ delayDuration: 0 }">`) so adjacent tooltips coordinate
|
|
14
|
+
* via `skipDelayDuration` automatically.
|
|
15
|
+
*
|
|
16
|
+
* Defaults match tooltip semantics:
|
|
17
|
+
* - `disableHoverableContent: true` — the content layer cannot be hovered,
|
|
18
|
+
* eliminating the "safe-polygon" cursor traversal flicker between
|
|
19
|
+
* tightly-packed triggers.
|
|
20
|
+
* - `pointer-events: none` on the content node — defence in depth so the
|
|
21
|
+
* tooltip never steals events from underlying interactive content.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
/** Plain text body. */
|
|
26
|
+
text?: string
|
|
27
|
+
/** Bold title rendered above description. */
|
|
28
|
+
title?: string
|
|
29
|
+
/** Secondary body text. */
|
|
30
|
+
description?: string
|
|
31
|
+
/** Raw HTML body — only rendered when title/description/text/#text are all absent. */
|
|
32
|
+
html?: string
|
|
33
|
+
/** Renders a label with a (?) trigger icon; tooltip mounts on the icon only. */
|
|
34
|
+
label?: string
|
|
35
|
+
size?: keyof typeof sizes
|
|
36
|
+
iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
37
|
+
side?: 'top' | 'bottom' | 'left' | 'right'
|
|
38
|
+
align?: 'start' | 'center' | 'end'
|
|
39
|
+
sideOffset?: number
|
|
40
|
+
/** Open delay (ms). Falls back to the provider default (0 in this app). */
|
|
41
|
+
delayDuration?: number
|
|
42
|
+
/** Whether the cursor may enter the tooltip body. Default `true` (tooltip semantics). */
|
|
43
|
+
disableHoverableContent?: boolean
|
|
44
|
+
disabled?: boolean
|
|
45
|
+
/**
|
|
46
|
+
* Default-variant trigger element. `'span'` (default) wraps the slot in a
|
|
47
|
+
* span. Keyboard focus on any focusable element inside opens the tooltip and
|
|
48
|
+
* describes that element; when the slot holds nothing focusable, the span
|
|
49
|
+
* itself becomes the Tab stop, so every tooltip stays keyboard-reachable.
|
|
50
|
+
* `'button'` wraps it in a focusable button so a non-interactive trigger
|
|
51
|
+
* (e.g. a bare info icon) stays keyboard- and screen-reader-reachable.
|
|
52
|
+
* `'child'` makes the slot's single root element the trigger directly — use
|
|
53
|
+
* it for full-width links/rows where an extra inline wrapper would alter
|
|
54
|
+
* layout or truncate the hit area.
|
|
55
|
+
*/
|
|
56
|
+
triggerAs?: 'span' | 'button' | 'child'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const {
|
|
60
|
+
text,
|
|
61
|
+
title,
|
|
62
|
+
description,
|
|
63
|
+
html,
|
|
64
|
+
side = 'top',
|
|
65
|
+
align = 'center',
|
|
66
|
+
sideOffset = 6,
|
|
67
|
+
disableHoverableContent = true,
|
|
68
|
+
disabled = false,
|
|
69
|
+
size = 'md',
|
|
70
|
+
triggerAs = 'span',
|
|
71
|
+
} = defineProps<Props>()
|
|
72
|
+
|
|
73
|
+
defineSlots<{
|
|
74
|
+
default?: () => VNodeChild
|
|
75
|
+
text?: () => VNodeChild
|
|
76
|
+
}>()
|
|
77
|
+
|
|
78
|
+
const slots = useSlots()
|
|
79
|
+
const childTriggerElement = shallowRef<HTMLElement>()
|
|
80
|
+
|
|
81
|
+
// Reka's as-child ref can stop at a Vue component instance. NuxtLink and other
|
|
82
|
+
// component triggers then leave Floating UI with no measurable anchor, so the
|
|
83
|
+
// tooltip falls back to viewport 0,0. Event listeners still reach the rendered
|
|
84
|
+
// DOM root, which gives us the concrete anchor without adding a layout wrapper.
|
|
85
|
+
function captureChildTrigger(event: Event): void {
|
|
86
|
+
if (event.currentTarget instanceof HTMLElement)
|
|
87
|
+
childTriggerElement.value = event.currentTarget
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Span trigger. Reka listens for `focus` on the trigger itself, and focus does
|
|
91
|
+
// not bubble, so a focusable child (a button, a link, a tabindex cell) never
|
|
92
|
+
// opened the tooltip and the description landed on the span. Listen for
|
|
93
|
+
// `focusin` instead, open on keyboard focus, and move the description onto the
|
|
94
|
+
// focused element. A span with nothing focusable inside takes the Tab stop.
|
|
95
|
+
// A trigger inside an `aria-hidden` or `inert` subtree (a position-only plot
|
|
96
|
+
// whose data a table carries) never takes a Tab stop: focus there is unnamed.
|
|
97
|
+
const open = ref(false)
|
|
98
|
+
const spanTrigger = shallowRef<HTMLElement>()
|
|
99
|
+
const spanTabStop = ref(false)
|
|
100
|
+
const focusedChild = shallowRef<HTMLElement>()
|
|
101
|
+
const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [contenteditable="true"], [tabindex]:not([tabindex="-1"])'
|
|
102
|
+
|
|
103
|
+
function syncSpanTabStop(): void {
|
|
104
|
+
const span = spanTrigger.value
|
|
105
|
+
if (!span)
|
|
106
|
+
return
|
|
107
|
+
spanTabStop.value = !disabled && hasContent.value
|
|
108
|
+
&& !span.querySelector(FOCUSABLE)
|
|
109
|
+
&& !span.closest('[aria-hidden="true"], [inert]')
|
|
110
|
+
}
|
|
111
|
+
onMounted(syncSpanTabStop)
|
|
112
|
+
onUpdated(syncSpanTabStop)
|
|
113
|
+
|
|
114
|
+
function onSpanFocusIn(event: FocusEvent): void {
|
|
115
|
+
const target = event.target
|
|
116
|
+
if (!(target instanceof HTMLElement) || target === spanTrigger.value)
|
|
117
|
+
return
|
|
118
|
+
if (disabled || !hasContent.value || !target.matches(':focus-visible'))
|
|
119
|
+
return
|
|
120
|
+
focusedChild.value = target
|
|
121
|
+
open.value = true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function onSpanFocusOut(event: FocusEvent): void {
|
|
125
|
+
if (event.target === spanTrigger.value)
|
|
126
|
+
return
|
|
127
|
+
if (event.relatedTarget instanceof Node && spanTrigger.value?.contains(event.relatedTarget))
|
|
128
|
+
return
|
|
129
|
+
open.value = false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Reka sets `aria-describedby` on the span while open; add it to the focused
|
|
133
|
+
// child, which is what a screen reader announces, and restore it on close.
|
|
134
|
+
let childDescribedBy: string | null = null
|
|
135
|
+
watch(open, async (isOpen) => {
|
|
136
|
+
const child = focusedChild.value
|
|
137
|
+
if (!child)
|
|
138
|
+
return
|
|
139
|
+
if (!isOpen) {
|
|
140
|
+
if (childDescribedBy)
|
|
141
|
+
child.setAttribute('aria-describedby', childDescribedBy)
|
|
142
|
+
else
|
|
143
|
+
child.removeAttribute('aria-describedby')
|
|
144
|
+
focusedChild.value = undefined
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
await nextTick()
|
|
148
|
+
const id = spanTrigger.value?.getAttribute('aria-describedby')
|
|
149
|
+
if (!id)
|
|
150
|
+
return
|
|
151
|
+
childDescribedBy = child.getAttribute('aria-describedby')
|
|
152
|
+
child.setAttribute('aria-describedby', childDescribedBy ? `${childDescribedBy} ${id}` : id)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const hasContent = computed(() =>
|
|
156
|
+
!!(text || title || description || html || slots.text),
|
|
157
|
+
)
|
|
158
|
+
const tooltipMaxWidth = computed(() =>
|
|
159
|
+
`min(${sizes[size]}px, calc(100vw - 2.5rem), var(--reka-tooltip-content-available-width, 100vw))`,
|
|
160
|
+
)
|
|
161
|
+
</script>
|
|
162
|
+
|
|
163
|
+
<script lang="ts">
|
|
164
|
+
export const sizes = {
|
|
165
|
+
xs: 80,
|
|
166
|
+
sm: 160,
|
|
167
|
+
md: 250,
|
|
168
|
+
lg: 440,
|
|
169
|
+
xl: 640,
|
|
170
|
+
}
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<template>
|
|
174
|
+
<!-- Label variant: tooltip mounts on a small ? icon next to plain text -->
|
|
175
|
+
<span v-if="label" class="inline-flex items-center gap-1">
|
|
176
|
+
<span>{{ label }}</span>
|
|
177
|
+
<TooltipProvider :delay-duration="delayDuration ?? 0" :skip-delay-duration="300" :disable-hoverable-content="disableHoverableContent">
|
|
178
|
+
<TooltipRoot
|
|
179
|
+
:delay-duration="delayDuration"
|
|
180
|
+
:disable-hoverable-content="disableHoverableContent"
|
|
181
|
+
:disabled="disabled || !hasContent"
|
|
182
|
+
>
|
|
183
|
+
<TooltipTrigger
|
|
184
|
+
:aria-label="`More information: ${label}`"
|
|
185
|
+
class="-m-1.5 inline-flex min-h-6 min-w-6 items-center justify-center rounded-full text-dimmed hover:text-muted transition-colors cursor-help focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
|
186
|
+
>
|
|
187
|
+
<UiIcon name="life-buoy" class="size-3" aria-hidden="true" />
|
|
188
|
+
</TooltipTrigger>
|
|
189
|
+
<TooltipPortal>
|
|
190
|
+
<TooltipContent
|
|
191
|
+
:side="side"
|
|
192
|
+
:align="align"
|
|
193
|
+
:side-offset="sideOffset"
|
|
194
|
+
:collision-padding="8"
|
|
195
|
+
class="ui-tooltip-content"
|
|
196
|
+
role="tooltip"
|
|
197
|
+
>
|
|
198
|
+
<div class="w-max space-y-2 text-left text-xs font-normal leading-normal" :style="{ maxWidth: tooltipMaxWidth }" data-ui="UiTooltip">
|
|
199
|
+
<template v-if="title">
|
|
200
|
+
<div class="font-semibold">
|
|
201
|
+
{{ title }}
|
|
202
|
+
</div>
|
|
203
|
+
<div v-if="description" class="text-muted text-xs">
|
|
204
|
+
{{ description }}
|
|
205
|
+
</div>
|
|
206
|
+
</template>
|
|
207
|
+
<div v-else>
|
|
208
|
+
{{ text }}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</TooltipContent>
|
|
212
|
+
</TooltipPortal>
|
|
213
|
+
</TooltipRoot>
|
|
214
|
+
</TooltipProvider>
|
|
215
|
+
</span>
|
|
216
|
+
|
|
217
|
+
<!-- Default variant: tooltip wraps the trigger (slot, or fallback (?) icon) -->
|
|
218
|
+
<TooltipProvider
|
|
219
|
+
v-else
|
|
220
|
+
:delay-duration="delayDuration ?? 0"
|
|
221
|
+
:skip-delay-duration="300"
|
|
222
|
+
:disable-hoverable-content="disableHoverableContent"
|
|
223
|
+
>
|
|
224
|
+
<TooltipRoot
|
|
225
|
+
v-model:open="open"
|
|
226
|
+
:delay-duration="delayDuration"
|
|
227
|
+
:disable-hoverable-content="disableHoverableContent"
|
|
228
|
+
:disabled="disabled || !hasContent"
|
|
229
|
+
>
|
|
230
|
+
<TooltipTrigger
|
|
231
|
+
v-if="$slots.default && triggerAs === 'child'"
|
|
232
|
+
:reference="childTriggerElement"
|
|
233
|
+
as-child
|
|
234
|
+
@pointermove="captureChildTrigger"
|
|
235
|
+
@focus="captureChildTrigger"
|
|
236
|
+
>
|
|
237
|
+
<slot />
|
|
238
|
+
</TooltipTrigger>
|
|
239
|
+
<TooltipTrigger v-else as-child>
|
|
240
|
+
<!-- `max-w-full`: a trigger never outgrows its container. Without it a
|
|
241
|
+
truncating child (a long query in a table cell) set the span's
|
|
242
|
+
width and spilled over the next column at phone width. -->
|
|
243
|
+
<span
|
|
244
|
+
v-if="$slots.default && triggerAs === 'span'"
|
|
245
|
+
ref="spanTrigger"
|
|
246
|
+
class="inline-block max-w-full"
|
|
247
|
+
:class="spanTabStop ? 'rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary' : undefined"
|
|
248
|
+
:tabindex="spanTabStop ? 0 : undefined"
|
|
249
|
+
@focusin="onSpanFocusIn"
|
|
250
|
+
@focusout="onSpanFocusOut"
|
|
251
|
+
>
|
|
252
|
+
<slot />
|
|
253
|
+
</span>
|
|
254
|
+
<button
|
|
255
|
+
v-else-if="$slots.default"
|
|
256
|
+
type="button"
|
|
257
|
+
:aria-label="text || title || description"
|
|
258
|
+
class="-m-0.5 inline-flex min-h-6 min-w-6 items-center justify-center cursor-help rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
|
259
|
+
>
|
|
260
|
+
<slot />
|
|
261
|
+
</button>
|
|
262
|
+
<button
|
|
263
|
+
v-else
|
|
264
|
+
type="button"
|
|
265
|
+
aria-label="More information"
|
|
266
|
+
class="-m-0.5 inline-flex min-h-6 min-w-6 items-center justify-center cursor-help rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
|
267
|
+
>
|
|
268
|
+
<UiIcon name="life-buoy" class="text-primary" :class="({ xs: 'size-3', sm: 'size-3.5', md: 'size-4', lg: 'size-5', xl: 'size-6' })[iconSize || 'md']" aria-hidden="true" />
|
|
269
|
+
</button>
|
|
270
|
+
</TooltipTrigger>
|
|
271
|
+
<TooltipPortal>
|
|
272
|
+
<TooltipContent
|
|
273
|
+
:side="side"
|
|
274
|
+
:align="align"
|
|
275
|
+
:side-offset="sideOffset"
|
|
276
|
+
:collision-padding="8"
|
|
277
|
+
class="ui-tooltip-content"
|
|
278
|
+
role="tooltip"
|
|
279
|
+
>
|
|
280
|
+
<div class="w-max space-y-2 text-left text-xs font-normal leading-normal" :style="{ maxWidth: tooltipMaxWidth }" data-ui="UiTooltip">
|
|
281
|
+
<slot v-if="$slots.text" name="text" />
|
|
282
|
+
<template v-else-if="title">
|
|
283
|
+
<div class="font-semibold">
|
|
284
|
+
{{ title }}
|
|
285
|
+
</div>
|
|
286
|
+
<div v-if="description" class="text-muted text-xs">
|
|
287
|
+
{{ description }}
|
|
288
|
+
</div>
|
|
289
|
+
</template>
|
|
290
|
+
<div v-else-if="text">
|
|
291
|
+
{{ text }}
|
|
292
|
+
</div>
|
|
293
|
+
<div v-else-if="html" v-html="html" />
|
|
294
|
+
</div>
|
|
295
|
+
</TooltipContent>
|
|
296
|
+
</TooltipPortal>
|
|
297
|
+
</TooltipRoot>
|
|
298
|
+
</TooltipProvider>
|
|
299
|
+
</template>
|
|
300
|
+
|
|
301
|
+
<style>
|
|
302
|
+
.ui-tooltip-content {
|
|
303
|
+
background-color: var(--ui-bg-elevated);
|
|
304
|
+
/* Popover-tier depth — see global.css. --elevation-popover carries the
|
|
305
|
+
atmospheric shadow + brand bevel; --surface-raised is the top-lit fill. */
|
|
306
|
+
background-image: var(--surface-raised);
|
|
307
|
+
box-shadow: var(--elevation-popover);
|
|
308
|
+
color: var(--ui-text);
|
|
309
|
+
border: 1px solid var(--ui-border);
|
|
310
|
+
border-radius: 0.5rem;
|
|
311
|
+
padding: 0.625rem 0.75rem;
|
|
312
|
+
pointer-events: none;
|
|
313
|
+
z-index: 50;
|
|
314
|
+
will-change: transform, opacity;
|
|
315
|
+
transform-origin: var(--reka-tooltip-content-transform-origin, center);
|
|
316
|
+
letter-spacing: -0.005em;
|
|
317
|
+
-webkit-font-smoothing: antialiased;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* Reka emits data-state + data-side; hook spring-out enter, soft exit. */
|
|
321
|
+
.ui-tooltip-content[data-state="delayed-open"],
|
|
322
|
+
.ui-tooltip-content[data-state="instant-open"] {
|
|
323
|
+
animation: ui-tooltip-in 140ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.ui-tooltip-content[data-state="closed"] {
|
|
327
|
+
animation: ui-tooltip-out 90ms ease-out;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
@keyframes ui-tooltip-in {
|
|
331
|
+
from {
|
|
332
|
+
opacity: 0;
|
|
333
|
+
transform: scale(0.96) translateY(var(--tt-slide-y, 0)) translateX(var(--tt-slide-x, 0));
|
|
334
|
+
}
|
|
335
|
+
to {
|
|
336
|
+
opacity: 1;
|
|
337
|
+
transform: scale(1) translateY(0) translateX(0);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@keyframes ui-tooltip-out {
|
|
342
|
+
from {
|
|
343
|
+
opacity: 1;
|
|
344
|
+
transform: scale(1);
|
|
345
|
+
}
|
|
346
|
+
to {
|
|
347
|
+
opacity: 0;
|
|
348
|
+
transform: scale(0.97);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* Slide-from-trigger directional accents */
|
|
353
|
+
.ui-tooltip-content[data-side="top"] { --tt-slide-y: 4px; }
|
|
354
|
+
.ui-tooltip-content[data-side="bottom"] { --tt-slide-y: -4px; }
|
|
355
|
+
.ui-tooltip-content[data-side="left"] { --tt-slide-x: 4px; }
|
|
356
|
+
.ui-tooltip-content[data-side="right"] { --tt-slide-x: -4px; }
|
|
357
|
+
|
|
358
|
+
@media (prefers-reduced-motion: reduce) {
|
|
359
|
+
.ui-tooltip-content[data-state="delayed-open"],
|
|
360
|
+
.ui-tooltip-content[data-state="instant-open"],
|
|
361
|
+
.ui-tooltip-content[data-state="closed"] {
|
|
362
|
+
animation: none;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
[data-ui="UiTooltip"] {
|
|
367
|
+
p, ul {
|
|
368
|
+
&:not(:last-child) {
|
|
369
|
+
margin-bottom: 0.75rem;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
ul {
|
|
374
|
+
padding-left: 1.5rem;
|
|
375
|
+
padding-right: 0.5rem;
|
|
376
|
+
list-style-type: disc;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
li {
|
|
380
|
+
margin-bottom: 0.25rem;
|
|
381
|
+
list-style-type: disc;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
code {
|
|
385
|
+
padding: 0.125rem 0.25rem;
|
|
386
|
+
border-radius: 0.375rem;
|
|
387
|
+
background-color: var(--ui-bg-accented);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
</style>
|