@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,58 @@
|
|
|
1
|
+
import type { ICON_ALIASES, IconRole } from './registry'
|
|
2
|
+
|
|
3
|
+
type SemanticUiIcon = IconRole | keyof typeof ICON_ALIASES
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Nuxt UI's complete icon-slot vocabulary, expressed only in design-system
|
|
7
|
+
* roles. Keeping this in build-time app config prevents Nuxt UI from adding
|
|
8
|
+
* its default Lucide set beside the curated client bundle.
|
|
9
|
+
*/
|
|
10
|
+
export const UI_ICON_SLOTS = {
|
|
11
|
+
arrowDown: 'down',
|
|
12
|
+
arrowLeft: 'back',
|
|
13
|
+
arrowRight: 'next',
|
|
14
|
+
arrowUp: 'up',
|
|
15
|
+
caution: 'caution',
|
|
16
|
+
check: 'check',
|
|
17
|
+
chevronDoubleLeft: 'page-first',
|
|
18
|
+
chevronDoubleRight: 'page-last',
|
|
19
|
+
chevronDown: 'expand',
|
|
20
|
+
chevronLeft: 'chevron-left',
|
|
21
|
+
chevronRight: 'chevron-right',
|
|
22
|
+
chevronUp: 'collapse',
|
|
23
|
+
close: 'close',
|
|
24
|
+
copy: 'copy',
|
|
25
|
+
copyCheck: 'success',
|
|
26
|
+
dark: 'dark',
|
|
27
|
+
document: 'file',
|
|
28
|
+
drag: 'grip',
|
|
29
|
+
ellipsis: 'more-horizontal',
|
|
30
|
+
error: 'error',
|
|
31
|
+
external: 'external',
|
|
32
|
+
eye: 'view',
|
|
33
|
+
eyeOff: 'hide',
|
|
34
|
+
file: 'file',
|
|
35
|
+
folder: 'folder',
|
|
36
|
+
folderOpen: 'folder',
|
|
37
|
+
hash: 'hash',
|
|
38
|
+
info: 'info',
|
|
39
|
+
light: 'light',
|
|
40
|
+
loading: 'loading',
|
|
41
|
+
menu: 'menu',
|
|
42
|
+
minus: 'minus',
|
|
43
|
+
next: 'next',
|
|
44
|
+
note: 'note',
|
|
45
|
+
panelClose: 'back',
|
|
46
|
+
panelOpen: 'next',
|
|
47
|
+
plus: 'add',
|
|
48
|
+
prev: 'back',
|
|
49
|
+
reload: 'refresh',
|
|
50
|
+
search: 'search',
|
|
51
|
+
stop: 'stop',
|
|
52
|
+
star: 'star',
|
|
53
|
+
success: 'success',
|
|
54
|
+
system: 'system',
|
|
55
|
+
tip: 'tip',
|
|
56
|
+
upload: 'upload',
|
|
57
|
+
warning: 'warning',
|
|
58
|
+
} as const satisfies Record<string, SemanticUiIcon>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import rangi from '@comark/nuxt/plugins/rangi'
|
|
2
|
+
import security from '@comark/nuxt/plugins/security'
|
|
3
|
+
import { contentRangiTheme } from './rangi'
|
|
4
|
+
|
|
5
|
+
export const markdownOptions = { registerDefaultPlugins: false }
|
|
6
|
+
|
|
7
|
+
export const markdownPlugins = [rangi({
|
|
8
|
+
classPrefix: 'rangi',
|
|
9
|
+
lineNumbers: true,
|
|
10
|
+
preStyles: false,
|
|
11
|
+
theme: contentRangiTheme,
|
|
12
|
+
}), security({
|
|
13
|
+
allowedTags: [
|
|
14
|
+
'p',
|
|
15
|
+
'a',
|
|
16
|
+
'strong',
|
|
17
|
+
'em',
|
|
18
|
+
's',
|
|
19
|
+
'del',
|
|
20
|
+
'code',
|
|
21
|
+
'pre',
|
|
22
|
+
'span',
|
|
23
|
+
'br',
|
|
24
|
+
'hr',
|
|
25
|
+
'blockquote',
|
|
26
|
+
'ul',
|
|
27
|
+
'ol',
|
|
28
|
+
'li',
|
|
29
|
+
'h1',
|
|
30
|
+
'h2',
|
|
31
|
+
'h3',
|
|
32
|
+
'h4',
|
|
33
|
+
'h5',
|
|
34
|
+
'h6',
|
|
35
|
+
'table',
|
|
36
|
+
'thead',
|
|
37
|
+
'tbody',
|
|
38
|
+
'tr',
|
|
39
|
+
'th',
|
|
40
|
+
'td',
|
|
41
|
+
],
|
|
42
|
+
allowedProtocols: ['http', 'https', 'mailto'],
|
|
43
|
+
allowDataImages: false,
|
|
44
|
+
})]
|
package/shared/motion.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion presets — the JS half of the design system's motion vocabulary,
|
|
3
|
+
* driven by motion-v (`<m.*>` elements). The CSS half is the `--ease-*`
|
|
4
|
+
* easings + route keyframes in global.css.
|
|
5
|
+
*
|
|
6
|
+
* - springs named spring transitions, reused everywhere
|
|
7
|
+
* - liftPresets hover / press transforms (counterpart of --elevation-hover)
|
|
8
|
+
* - entrancePresets mount reveals — fade / fadeUp / pop
|
|
9
|
+
* - stagger* list-reveal orchestration
|
|
10
|
+
*
|
|
11
|
+
* Every consumer gates motion behind `useReducedMotion()` — see `entranceProps()`
|
|
12
|
+
* and the `UiButton` `lift` computed for the pattern.
|
|
13
|
+
*
|
|
14
|
+
* SSR note: a motion-v entrance bound on the server ships its hidden `initial`
|
|
15
|
+
* frame, and Vue keeps that style through hydration. Under reduced motion
|
|
16
|
+
* nothing moves it out, so the content stays invisible. Components bind
|
|
17
|
+
* entrances through `useEntranceMotion()`, which binds nothing on the server
|
|
18
|
+
* or during hydration.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/* ─── Springs ─────────────────────────────────────────────────────────────
|
|
22
|
+
Named motion-v `transition` configs. Reuse rather than hand-tuning. */
|
|
23
|
+
export const springs = {
|
|
24
|
+
/** Quick, minimal overshoot — toggles, small reveals. */
|
|
25
|
+
snappy: { type: 'spring', stiffness: 380, damping: 26, mass: 0.6 },
|
|
26
|
+
/** Balanced, unhurried — the default for entrances and content shifts. */
|
|
27
|
+
smooth: { type: 'spring', stiffness: 210, damping: 26, mass: 0.9 },
|
|
28
|
+
/** Soft, slow settle — large surfaces, drawers. */
|
|
29
|
+
gentle: { type: 'spring', stiffness: 180, damping: 24, mass: 1 },
|
|
30
|
+
} as const
|
|
31
|
+
|
|
32
|
+
/* ─── Lift ────────────────────────────────────────────────────────────────
|
|
33
|
+
Hover / press transforms. The CSS counterpart is `--elevation-hover`; this
|
|
34
|
+
is the JS half. Shared so buttons, cards, and links lift with one feel. */
|
|
35
|
+
export type LiftIntensity = 'subtle' | 'default' | 'cta'
|
|
36
|
+
|
|
37
|
+
export interface LiftPreset {
|
|
38
|
+
hover: { scale: number, y: number }
|
|
39
|
+
tap: { scale: number, y: number }
|
|
40
|
+
transition: { type: 'spring', stiffness: number, damping: number, mass: number }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const liftPresets: Record<LiftIntensity, LiftPreset> = {
|
|
44
|
+
/** Barely-there nudge — crisp, quick, no overshoot (high damping). */
|
|
45
|
+
subtle: {
|
|
46
|
+
hover: { scale: 1.012, y: -1 },
|
|
47
|
+
tap: { scale: 0.99, y: 0 },
|
|
48
|
+
transition: { type: 'spring', stiffness: 420, damping: 30, mass: 0.5 },
|
|
49
|
+
},
|
|
50
|
+
/** Restrained lift with a touch of spring: the standard. */
|
|
51
|
+
default: {
|
|
52
|
+
hover: { scale: 1.025, y: -2 },
|
|
53
|
+
tap: { scale: 0.98, y: 0 },
|
|
54
|
+
transition: { type: 'spring', stiffness: 320, damping: 21, mass: 0.8 },
|
|
55
|
+
},
|
|
56
|
+
/** Emphatic lift: visible overshoot with a controlled settle. */
|
|
57
|
+
cta: {
|
|
58
|
+
hover: { scale: 1.045, y: -4 },
|
|
59
|
+
tap: { scale: 0.96, y: 0 },
|
|
60
|
+
transition: { type: 'spring', stiffness: 240, damping: 15, mass: 1.1 },
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ─── Entrance ────────────────────────────────────────────────────────────
|
|
65
|
+
Mount reveals for `<m.*>` elements. Components bind them with `useEntranceMotion()`,
|
|
66
|
+
which handles reduced motion and server rendering. */
|
|
67
|
+
export type EntranceName = 'fade' | 'fadeUp' | 'pop'
|
|
68
|
+
|
|
69
|
+
export interface EntrancePreset {
|
|
70
|
+
initial: Record<string, number>
|
|
71
|
+
enter: Record<string, number>
|
|
72
|
+
transition: Record<string, unknown>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const entrancePresets: Record<EntranceName, EntrancePreset> = {
|
|
76
|
+
fade: {
|
|
77
|
+
initial: { opacity: 0 },
|
|
78
|
+
enter: { opacity: 1 },
|
|
79
|
+
transition: { duration: 0.45, ease: 'easeOut' },
|
|
80
|
+
},
|
|
81
|
+
fadeUp: {
|
|
82
|
+
initial: { opacity: 0, y: 12 },
|
|
83
|
+
enter: { opacity: 1, y: 0 },
|
|
84
|
+
// A long, mildly-eased tween — not a spring and not an aggressive easeOut;
|
|
85
|
+
// both front-load the change and read as rushed. A gentle easeOut over
|
|
86
|
+
// 600ms spreads the motion so the entrance is graceful and watchable.
|
|
87
|
+
transition: { duration: 0.6, ease: 'easeOut' },
|
|
88
|
+
},
|
|
89
|
+
pop: {
|
|
90
|
+
initial: { opacity: 0, scale: 0.94 },
|
|
91
|
+
enter: { opacity: 1, scale: 1 },
|
|
92
|
+
transition: springs.smooth,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* v-bind helper — returns motion-v `{ initial, animate, transition }`, or `{}`
|
|
98
|
+
* when reduced motion is on (the element then mounts in its resting state).
|
|
99
|
+
*/
|
|
100
|
+
export function entranceProps(preset: EntrancePreset, reduced?: boolean) {
|
|
101
|
+
if (reduced)
|
|
102
|
+
return {}
|
|
103
|
+
return { initial: preset.initial, animate: preset.enter, transition: preset.transition }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ─── Stagger ─────────────────────────────────────────────────────────────
|
|
107
|
+
List-reveal orchestration. The parent `<m.*>` binds `staggerContainer`, each
|
|
108
|
+
child `<m.*>` binds `staggerChild`; children animate in sequence. */
|
|
109
|
+
export const staggerContainer = {
|
|
110
|
+
initial: 'initial',
|
|
111
|
+
animate: 'animate',
|
|
112
|
+
variants: { animate: { transition: { staggerChildren: 0.05 } } },
|
|
113
|
+
} as const
|
|
114
|
+
|
|
115
|
+
export const staggerChild = {
|
|
116
|
+
variants: {
|
|
117
|
+
initial: { opacity: 0, y: 6 },
|
|
118
|
+
animate: { opacity: 1, y: 0, transition: springs.smooth },
|
|
119
|
+
},
|
|
120
|
+
} as const
|
package/shared/rangi.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ShjThemePair, ShjToken } from 'rangi'
|
|
2
|
+
import { tokenize } from 'rangi'
|
|
3
|
+
import { githubDark, githubLight } from 'rangi/themes'
|
|
4
|
+
|
|
5
|
+
// @harlan-zw/comark-content also exports `contentRangiTheme`, but only from its
|
|
6
|
+
// build-time module entry, which pulls `@nuxt/kit` and `node:` builtins. This
|
|
7
|
+
// file is imported by Vue components (`ModuleInstall`, `SiteConfigQuickSetup`,
|
|
8
|
+
// the docs nav) and by `shared/markdown.ts`, so it must stay client-safe. The
|
|
9
|
+
// two definitions are identical apart from the theme name; keep them in step.
|
|
10
|
+
export const contentRangiTheme: ShjThemePair = {
|
|
11
|
+
light: {
|
|
12
|
+
...githubLight,
|
|
13
|
+
name: 'nuxtseo-github-light-aa',
|
|
14
|
+
tokens: { ...githubLight.tokens, cmnt: '#57606a' },
|
|
15
|
+
},
|
|
16
|
+
dark: githubDark,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface HighlightedCode {
|
|
20
|
+
className: string
|
|
21
|
+
html: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function escapeHtml(value: string): string {
|
|
25
|
+
return value
|
|
26
|
+
.replaceAll('&', '&')
|
|
27
|
+
.replaceAll('<', '<')
|
|
28
|
+
.replaceAll('>', '>')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function tokenStyle(token: ShjToken, appearance: 'adaptive' | 'dark'): string {
|
|
32
|
+
const light = contentRangiTheme.light.tokens[token]
|
|
33
|
+
const dark = contentRangiTheme.dark.tokens[token]
|
|
34
|
+
const color = appearance === 'dark'
|
|
35
|
+
? dark
|
|
36
|
+
: light && dark && light !== dark ? `light-dark(${light},${dark})` : light ?? dark
|
|
37
|
+
return [color ? `color:${color}` : '', token === 'cmnt' ? 'font-style:italic' : ''].filter(Boolean).join(';')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function highlightCode(code: string, lang: string, appearance: 'adaptive' | 'dark' = 'adaptive'): HighlightedCode {
|
|
41
|
+
const requestedLanguage = lang.toLowerCase().replace(/[^a-z0-9-]/g, '') || 'plain'
|
|
42
|
+
const language = requestedLanguage === 'dotenv' || requestedLanguage === 'env' ? 'ini' : requestedLanguage
|
|
43
|
+
const html = tokenize(code, { lang: language }).map((token) => {
|
|
44
|
+
const value = escapeHtml(token.text)
|
|
45
|
+
if (!token.type)
|
|
46
|
+
return value
|
|
47
|
+
const style = tokenStyle(token.type, appearance)
|
|
48
|
+
return style ? `<span style="${style}">${value}</span>` : value
|
|
49
|
+
}).join('')
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
html,
|
|
53
|
+
className: `rangi shj-lang-${requestedLanguage}`,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function inlineHighlightedCode(code: string, lang: string): string {
|
|
58
|
+
const highlighted = highlightCode(code, lang)
|
|
59
|
+
return `<code class="${highlighted.className}">${highlighted.html}</code>`
|
|
60
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RichSegment` — the typed inline-token vocabulary for action/entity prose.
|
|
3
|
+
*
|
|
4
|
+
* Pinned contract (both the design-system `UiRichText` renderer and pro-actions's
|
|
5
|
+
* `action-display.ts` build against exactly this shape). pro-actions mirrors this
|
|
6
|
+
* type structurally with a "keep in sync" comment (ADR-0042: design-system MAY
|
|
7
|
+
* know the shape, never the runtime that produces it). Supersedes the retired
|
|
8
|
+
* `UiActionTitle` / `ActionTitleSegment` regex-recovered vocabulary (deleted
|
|
9
|
+
* Phase 4, sprint-action-typed-rendering, 2026-07-29).
|
|
10
|
+
*
|
|
11
|
+
* A segment is never parsed from prose at render time — it is produced upstream
|
|
12
|
+
* by a typed why-template, so every token here already carries its own display
|
|
13
|
+
* data (no regex, no string surgery in the renderer).
|
|
14
|
+
*/
|
|
15
|
+
export type RichSegment
|
|
16
|
+
= | { kind: 'text', text: string }
|
|
17
|
+
/** Renders ' · ' — a calm, aria-hidden divider between segments. */
|
|
18
|
+
| { kind: 'sep' }
|
|
19
|
+
/** Quoted mono run (keyword treatment). */
|
|
20
|
+
| { kind: 'keyword', text: string, to?: string, tooltip?: string }
|
|
21
|
+
/** Social @handle. */
|
|
22
|
+
| { kind: 'handle', text: string, to?: string }
|
|
23
|
+
/** Compacted path; the full URL belongs in the tooltip/title, not the label. */
|
|
24
|
+
| { kind: 'page', text: string, url: string, to?: string, tooltip?: string }
|
|
25
|
+
/** Favicon + host. `external` opens the domain itself in a new tab. */
|
|
26
|
+
| { kind: 'domain', text: string, domain: string, external?: boolean, to?: string }
|
|
27
|
+
/** Numerals-display, tabular figure. */
|
|
28
|
+
| { kind: 'metric', text: string, tooltip?: string }
|
|
29
|
+
/** '#8' with `UiPositionMetric`-style quality bands. */
|
|
30
|
+
| { kind: 'position', text: string, position: number }
|
|
31
|
+
/** '19 → 26' linked pair. Colour rides ONLY on an explicit `good`; otherwise neutral. */
|
|
32
|
+
| { kind: 'delta', from: string, to: string, direction: 'up' | 'down' | 'flat', good?: boolean, tooltip?: string }
|
|
33
|
+
/** Selectors, rule ids — mono, unquoted (distinct from `keyword`). */
|
|
34
|
+
| { kind: 'code', text: string }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function getLastPathSegment(path: string) {
|
|
2
|
+
const segments = path.split('/')
|
|
3
|
+
return segments.slice(-1).join('/')
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function getPathSegments(path: string, size: number): string {
|
|
7
|
+
const segments = path.split('/')
|
|
8
|
+
|
|
9
|
+
if (segments.length <= size)
|
|
10
|
+
return path
|
|
11
|
+
|
|
12
|
+
return segments.slice(0, size + 1).join('/')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Strip a URL down to a human-readable label: drop the protocol, a leading
|
|
17
|
+
* `www.`, the `sc-domain:` prefix, and any trailing slash. Bare domains and
|
|
18
|
+
* paths pass through cleaned.
|
|
19
|
+
*/
|
|
20
|
+
export function prettifyUrl(input: string | null | undefined): string {
|
|
21
|
+
if (!input)
|
|
22
|
+
return ''
|
|
23
|
+
return input
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/^sc-domain:/i, '')
|
|
26
|
+
.replace(/^[a-z][\w+.-]*:\/\//i, '')
|
|
27
|
+
.replace(/^www\./i, '')
|
|
28
|
+
.replace(/\/+$/, '')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Best-effort hostname extraction for favicon lookups. Falls back to the
|
|
33
|
+
* cleaned input when it isn't a parseable absolute URL.
|
|
34
|
+
*/
|
|
35
|
+
export function urlHostname(input: string | null | undefined): string {
|
|
36
|
+
if (!input)
|
|
37
|
+
return ''
|
|
38
|
+
const cleaned = input.trim().replace(/^sc-domain:/i, '')
|
|
39
|
+
const withProto = /^[a-z][\w+.-]*:\/\//i.test(cleaned) ? cleaned : `https://${cleaned}`
|
|
40
|
+
try {
|
|
41
|
+
return new URL(withProto).hostname.replace(/^www\./i, '')
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return prettifyUrl(cleaned).split('/')[0] ?? ''
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The URL itself when it is an absolute `http:` or `https:` URL, else
|
|
50
|
+
* `undefined`. Every `href` built from stored or caller data goes through
|
|
51
|
+
* this: a `javascript:` value in an anchor runs script on click, and the
|
|
52
|
+
* `scheme://` shape test it replaces let `javascript://%0A…` through.
|
|
53
|
+
*/
|
|
54
|
+
export function safeHttpHref(input: string | null | undefined): string | undefined {
|
|
55
|
+
if (!input)
|
|
56
|
+
return undefined
|
|
57
|
+
try {
|
|
58
|
+
return /^https?:$/.test(new URL(input).protocol) ? input : undefined
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Not an absolute URL, so there is nothing safe to link to.
|
|
62
|
+
return undefined
|
|
63
|
+
}
|
|
64
|
+
}
|
package/types/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ui'
|