@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,68 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends ArrayOrNested<SelectItem> = ArrayOrNested<SelectItem>, VK extends GetItemKeys<T> = 'value', M extends boolean = false">
|
|
2
|
+
import type { ArrayOrNested, GetItemKeys, SelectEmits, SelectItem, SelectProps } from '@nuxt/ui'
|
|
3
|
+
import { USelect } from '#components'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* UiSelect — thin wrapper around Nuxt UI v4's USelect (the native-style
|
|
7
|
+
* single/multi select, not the overlay `USelectMenu` — that one stays raw
|
|
8
|
+
* per DESIGN.md's overlay-chrome rule).
|
|
9
|
+
*
|
|
10
|
+
* Tokens (radius, ring, full-width) are already centralized in
|
|
11
|
+
* `app.config.ts`'s `ui.select` — this component isn't a re-theme, it's the
|
|
12
|
+
* single legal entry point (raw `<USelect>` is ESLint-banned via
|
|
13
|
+
* `eslint/selectors.ts`) so a future behavioral change lands in one file
|
|
14
|
+
* instead of another repo-wide sweep. Every prop, emit, and slot passes
|
|
15
|
+
* straight through unchanged.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
defineOptions({ inheritAttrs: false })
|
|
19
|
+
|
|
20
|
+
const props = defineProps<Props>()
|
|
21
|
+
const emit = defineEmits<Omit<SelectEmits<T, VK, M>, 'update:modelValue'> & { 'update:modelValue': [value: ModelValue] }>()
|
|
22
|
+
/**
|
|
23
|
+
* `open`, `defaultOpen` and `portal` are omitted deliberately, and dropping any
|
|
24
|
+
* of them from this list breaks every select in the app.
|
|
25
|
+
*
|
|
26
|
+
* `defineProps<T>()` compiles a bare `boolean` prop into a runtime prop with no
|
|
27
|
+
* default, and Vue resolves an ABSENT boolean prop to `false` rather than
|
|
28
|
+
* `undefined`. `{ ...props }` below then hands `<USelect>` a real, enumerable
|
|
29
|
+
* `open: false` — indistinguishable from a caller writing `:open="false"`.
|
|
30
|
+
* Reka's `SelectRoot` reads that as controlled state and pins the listbox
|
|
31
|
+
* permanently closed: the trigger still emits `update:open`, nothing consumes
|
|
32
|
+
* it, and the menu never renders. Nuxt UI ships `useComponentProps()` to dodge
|
|
33
|
+
* exactly this footgun by inspecting the raw vnode props, and the spread
|
|
34
|
+
* defeats it. `portal` suffers the same forced `false`, which un-teleports the
|
|
35
|
+
* listbox and risks clipping inside scroll-constrained containers.
|
|
36
|
+
*
|
|
37
|
+
* No caller passes any of the three, so omitting them costs nothing.
|
|
38
|
+
*/
|
|
39
|
+
type Props = Omit<SelectProps<T, VK, M>, 'defaultValue' | 'modelModifiers' | 'open' | 'defaultOpen' | 'portal'>
|
|
40
|
+
/**
|
|
41
|
+
* The value USelect emits is always a picked item, so the model never emits
|
|
42
|
+
* `undefined` and a caller may v-model a ref that never holds it. The type is
|
|
43
|
+
* read from SelectProps, not rebuilt from Nuxt UI's private ExcludeItem
|
|
44
|
+
* generic. Inside this generic component TypeScript 7.1 cannot prove that
|
|
45
|
+
* USelect's own emitted type matches it, so `updateModel` takes the value
|
|
46
|
+
* untyped and the caller-facing emit carries the type.
|
|
47
|
+
*/
|
|
48
|
+
type ModelValue = NonNullable<SelectProps<T, VK, M>['modelValue']>
|
|
49
|
+
|
|
50
|
+
function updateModel(value: unknown) {
|
|
51
|
+
emit('update:modelValue', value as ModelValue)
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<!-- @vue-generic {T, VK, M} -->
|
|
57
|
+
<USelect
|
|
58
|
+
v-bind="{ ...props, ...$attrs }"
|
|
59
|
+
@update:model-value="updateModel"
|
|
60
|
+
@blur="emit('blur', $event)"
|
|
61
|
+
@change="emit('change', $event)"
|
|
62
|
+
@focus="emit('focus', $event)"
|
|
63
|
+
>
|
|
64
|
+
<template v-for="(_, name) in ($slots as Record<string, unknown>)" #[name]="slotProps" :key="name">
|
|
65
|
+
<slot :name="name" v-bind="slotProps || {}" />
|
|
66
|
+
</template>
|
|
67
|
+
</USelect>
|
|
68
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SemanticStatus } from '../../composables/semanticColors'
|
|
3
|
+
import { semanticColors } from '../../composables/semanticColors'
|
|
4
|
+
|
|
5
|
+
const { severity = 'neutral', label } = defineProps<{
|
|
6
|
+
severity?: SemanticStatus
|
|
7
|
+
label?: string
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
// When the caller supplies neither a label nor slot content, severity is left to
|
|
11
|
+
// the dot colour alone — give screen readers the severity word as a fallback.
|
|
12
|
+
const SEVERITY_WORD: Record<SemanticStatus, string> = {
|
|
13
|
+
error: 'Error',
|
|
14
|
+
warning: 'Warning',
|
|
15
|
+
info: 'Info',
|
|
16
|
+
success: 'Success',
|
|
17
|
+
neutral: 'Neutral',
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<span class="inline-flex items-center gap-1.5 text-xs text-default w-auto">
|
|
23
|
+
<span class="size-1.5 rounded-full shrink-0" :class="semanticColors[severity].dot" aria-hidden="true" />
|
|
24
|
+
<slot>{{ label }}</slot>
|
|
25
|
+
<span v-if="!label && !$slots.default" class="sr-only">{{ SEVERITY_WORD[severity] }}</span>
|
|
26
|
+
</span>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { UiFavicon } from '#components'
|
|
3
|
+
|
|
4
|
+
const { domain, tier, size = 16 } = defineProps<{
|
|
5
|
+
domain: string
|
|
6
|
+
/** Product-owned site tier. External identities should use `UiFavicon`. */
|
|
7
|
+
tier: 'growth' | 'monitor'
|
|
8
|
+
size?: number
|
|
9
|
+
}>()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<span
|
|
14
|
+
class="inline-grid shrink-0 place-items-center"
|
|
15
|
+
:style="{
|
|
16
|
+
width: `${size + 2}px`,
|
|
17
|
+
height: `${size + 2}px`,
|
|
18
|
+
}"
|
|
19
|
+
:data-monitoring-tier="tier"
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
>
|
|
22
|
+
<UiFavicon :domain="domain" :size="size" decorative />
|
|
23
|
+
</span>
|
|
24
|
+
</template>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useAttrs } from 'vue'
|
|
3
|
+
|
|
4
|
+
defineOptions({ inheritAttrs: false })
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Deterministic skeleton loader that avoids SSR hydration mismatches.
|
|
8
|
+
* Uses index-based pseudo-random sizing instead of Math.random().
|
|
9
|
+
*/
|
|
10
|
+
const {
|
|
11
|
+
type = 'text',
|
|
12
|
+
lines = 1,
|
|
13
|
+
index = 0,
|
|
14
|
+
base = 160,
|
|
15
|
+
range = 80,
|
|
16
|
+
} = defineProps<{
|
|
17
|
+
/**
|
|
18
|
+
* Skeleton shape. `text`/`bar`/`circle` size themselves from `base`/`index`;
|
|
19
|
+
* `block` sets no width or height of its own, so the caller's utility classes
|
|
20
|
+
* decide the box — use it whenever the placeholder must match a known layout
|
|
21
|
+
* (a `w-full` row, a fixed-width label). The self-sizing types write an inline
|
|
22
|
+
* `width`/`height`, which silently beats any `w-*`/`h-*` class passed in.
|
|
23
|
+
*/
|
|
24
|
+
type?: 'text' | 'bar' | 'circle' | 'block'
|
|
25
|
+
/** Number of skeleton lines (text type only) */
|
|
26
|
+
lines?: number
|
|
27
|
+
/** Seed index for deterministic width/height variation */
|
|
28
|
+
index?: number
|
|
29
|
+
/** Base size in px — text: width, bar: height percent, circle: diameter */
|
|
30
|
+
base?: number
|
|
31
|
+
/** Variation range in px or percent */
|
|
32
|
+
range?: number
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const attrs = useAttrs()
|
|
36
|
+
|
|
37
|
+
// Deterministic pseudo-random: produces consistent values for the same index on server + client
|
|
38
|
+
function seededValue(i: number): number {
|
|
39
|
+
const x = Math.sin(i * 127.1 + 311.7) * 43758.5453
|
|
40
|
+
return x - Math.floor(x) // 0..1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function textWidth(i: number): string {
|
|
44
|
+
return `${Math.round(base + seededValue(i + index) * range)}px`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function barHeight(i: number): string {
|
|
48
|
+
return `${Math.round(20 + Math.sin(i * 0.3) * 15 + seededValue(i + index) * 30)}%`
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<!-- Text rows (multiple lines = fragment, wrapped in div for class inheritance) -->
|
|
54
|
+
<div v-if="type === 'text' && lines > 1" aria-hidden="true" v-bind="attrs" class="flex flex-col gap-2">
|
|
55
|
+
<div
|
|
56
|
+
v-for="i in lines"
|
|
57
|
+
:key="i"
|
|
58
|
+
class="ui-skeleton rounded h-3 max-w-full"
|
|
59
|
+
:style="{ width: textWidth(i) }"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- Single text row -->
|
|
64
|
+
<div
|
|
65
|
+
v-else-if="type === 'text'"
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
v-bind="attrs"
|
|
68
|
+
class="ui-skeleton rounded h-3 max-w-full"
|
|
69
|
+
:style="{ width: textWidth(1) }"
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
<!-- Vertical chart bars -->
|
|
73
|
+
<div
|
|
74
|
+
v-else-if="type === 'bar'"
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
v-bind="attrs"
|
|
77
|
+
class="ui-skeleton rounded-t w-full mt-auto"
|
|
78
|
+
:style="{ height: barHeight(index) }"
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
<!-- Caller-sized box: no inline width/height, so `w-*`/`h-*` classes win -->
|
|
82
|
+
<div
|
|
83
|
+
v-else-if="type === 'block'"
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
v-bind="attrs"
|
|
86
|
+
class="ui-skeleton rounded"
|
|
87
|
+
/>
|
|
88
|
+
|
|
89
|
+
<!-- Circle -->
|
|
90
|
+
<div
|
|
91
|
+
v-else-if="type === 'circle'"
|
|
92
|
+
aria-hidden="true"
|
|
93
|
+
v-bind="attrs"
|
|
94
|
+
class="ui-skeleton rounded-full"
|
|
95
|
+
:style="{ width: `${base}px`, height: `${base}px` }"
|
|
96
|
+
/>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<!--
|
|
100
|
+
Cross-fade reveal recipe (consumer responsibility):
|
|
101
|
+
<Transition name="ui-skel-fade">
|
|
102
|
+
<UiSkeleton v-if="loading" />
|
|
103
|
+
<RealContent v-else />
|
|
104
|
+
</Transition>
|
|
105
|
+
with `.ui-skel-fade-enter-active{transition:opacity 160ms ease-out}`. Prevents pop-in.
|
|
106
|
+
-->
|
|
107
|
+
<style scoped>
|
|
108
|
+
.ui-skeleton {
|
|
109
|
+
position: relative;
|
|
110
|
+
background:
|
|
111
|
+
linear-gradient(
|
|
112
|
+
90deg,
|
|
113
|
+
transparent 0%,
|
|
114
|
+
color-mix(in srgb, var(--ui-bg-elevated) 70%, transparent) 50%,
|
|
115
|
+
transparent 100%
|
|
116
|
+
),
|
|
117
|
+
linear-gradient(
|
|
118
|
+
90deg,
|
|
119
|
+
var(--ui-bg-accented) 0%,
|
|
120
|
+
var(--ui-bg-elevated) 40%,
|
|
121
|
+
var(--ui-bg-accented) 80%
|
|
122
|
+
);
|
|
123
|
+
background-size: 200% 100%, 200% 100%;
|
|
124
|
+
background-repeat: no-repeat;
|
|
125
|
+
background-position: 200% 0, 200% 0;
|
|
126
|
+
/* Single shimmer pass. A second, slower animation on the same
|
|
127
|
+
`background-position` was fully overridden (last-declared animation wins)
|
|
128
|
+
so it only doubled paint work with zero visual effect. */
|
|
129
|
+
animation: ui-shimmer 1.6s ease-in-out infinite;
|
|
130
|
+
box-shadow: var(--elevation-inset);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@keyframes ui-shimmer {
|
|
134
|
+
0% { background-position: 200% 0, 0 0; }
|
|
135
|
+
100% { background-position: -200% 0, 0 0; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@media (prefers-reduced-motion: reduce) {
|
|
139
|
+
.ui-skeleton {
|
|
140
|
+
animation: ui-skel-pulse 1.6s ease-in-out infinite;
|
|
141
|
+
background: var(--ui-bg-accented);
|
|
142
|
+
}
|
|
143
|
+
@keyframes ui-skel-pulse {
|
|
144
|
+
0%, 100% { opacity: 0.6; }
|
|
145
|
+
50% { opacity: 1; }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
</style>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SlotTextOptions } from '../../utils/slot-text'
|
|
3
|
+
import { useReducedMotion } from 'motion-v'
|
|
4
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
5
|
+
|
|
6
|
+
const { text, options } = defineProps<{
|
|
7
|
+
text: string
|
|
8
|
+
options?: SlotTextOptions
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const reduced = useReducedMotion()
|
|
12
|
+
// SSR and the first hydrated frame stay plain text. Character-slot markup only
|
|
13
|
+
// appears for a real post-mount update, after the global animation CSS is ready.
|
|
14
|
+
// This prevents a slow stylesheet/hydration path from exposing split glyphs.
|
|
15
|
+
const mounted = ref(false)
|
|
16
|
+
const animateUpdates = ref(false)
|
|
17
|
+
onMounted(() => {
|
|
18
|
+
mounted.value = true
|
|
19
|
+
})
|
|
20
|
+
watch(() => text, (next, previous) => {
|
|
21
|
+
if (mounted.value && next !== previous)
|
|
22
|
+
animateUpdates.value = true
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const resolvedOptions = computed<SlotTextOptions>(() => ({
|
|
26
|
+
direction: 'up',
|
|
27
|
+
stagger: 32,
|
|
28
|
+
duration: 260,
|
|
29
|
+
exitOffset: 38,
|
|
30
|
+
...options,
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
const chars = computed(() => Array.from(text))
|
|
34
|
+
const directionClass = computed(() => resolvedOptions.value.direction === 'down' ? 'ui-slot-text--down' : 'ui-slot-text--up')
|
|
35
|
+
|
|
36
|
+
function glyph(char: string) {
|
|
37
|
+
return char === ' ' ? '\u00A0' : char
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function slotStyle(index: number) {
|
|
41
|
+
return {
|
|
42
|
+
'--ui-slot-delay': `${index * (resolvedOptions.value.stagger ?? 32)}ms`,
|
|
43
|
+
'--ui-slot-duration': `${resolvedOptions.value.duration ?? 260}ms`,
|
|
44
|
+
'--ui-slot-easing': resolvedOptions.value.easing ?? 'cubic-bezier(0.34, 1.56, 0.64, 1)',
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<span v-if="reduced || !animateUpdates" class="ui-slot-text-host">{{ text }}</span>
|
|
51
|
+
<span v-else class="ui-slot-text-host ui-slot-text" :class="directionClass" :aria-label="text">
|
|
52
|
+
<span
|
|
53
|
+
v-for="(char, index) in chars"
|
|
54
|
+
:key="index"
|
|
55
|
+
class="ui-slot-text__slot"
|
|
56
|
+
:style="slotStyle(index)"
|
|
57
|
+
aria-hidden="true"
|
|
58
|
+
>
|
|
59
|
+
<span class="ui-slot-text__sizer">{{ glyph(char) }}</span>
|
|
60
|
+
<Transition name="ui-slot-text-roll">
|
|
61
|
+
<span :key="`${index}:${char}`" class="ui-slot-text__face">{{ glyph(char) }}</span>
|
|
62
|
+
</Transition>
|
|
63
|
+
</span>
|
|
64
|
+
</span>
|
|
65
|
+
</template>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { UiBrandIcon, UiIcon } from '#components'
|
|
4
|
+
|
|
5
|
+
export interface SourceLogo {
|
|
6
|
+
id: string
|
|
7
|
+
label: string
|
|
8
|
+
icon: string
|
|
9
|
+
image?: string
|
|
10
|
+
brand?: boolean
|
|
11
|
+
invertDark?: boolean
|
|
12
|
+
tint?: string
|
|
13
|
+
mono?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { sources, size = 'sm', dither = false } = defineProps<{
|
|
17
|
+
sources: readonly SourceLogo[]
|
|
18
|
+
/**
|
|
19
|
+
* `inline` drops the frame so the mark sits inside a line of text at 12px,
|
|
20
|
+
* the way a byline credits its Source. `xs` and `sm` frame the mark as a
|
|
21
|
+
* badge for a row's edge.
|
|
22
|
+
*/
|
|
23
|
+
size?: 'inline' | 'xs' | 'sm'
|
|
24
|
+
/**
|
|
25
|
+
* Quiet provenance mark for a card corner that must not compete with the
|
|
26
|
+
* number beside it: desaturated, softened, stippled with a fine dot mask.
|
|
27
|
+
* Same treatment `UiFavicon` uses for its `dither` prop, so a Source logo
|
|
28
|
+
* and a site favicon recede the same way. Hovering the enclosing `.group`,
|
|
29
|
+
* link or button restores the full logo.
|
|
30
|
+
*/
|
|
31
|
+
dither?: boolean
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const accessibleLabel = computed(() => sources.map(source => source.label).join(' and '))
|
|
35
|
+
const framed = computed(() => size !== 'inline')
|
|
36
|
+
const frameClass = computed(() => size === 'inline' ? '' : `border border-default bg-default shadow-xs rounded-md ${size === 'xs' ? 'size-5' : 'size-6'}`)
|
|
37
|
+
const iconClass = computed(() => size === 'inline' ? 'size-3' : size === 'xs' ? 'size-3.5' : 'size-4')
|
|
38
|
+
const brandClass = computed(() => size === 'inline' ? '!size-3' : size === 'xs' ? '!size-3.5' : '!size-4')
|
|
39
|
+
const iconPixels = computed(() => size === 'inline' ? 12 : size === 'xs' ? 14 : 16)
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<span
|
|
44
|
+
v-if="sources.length"
|
|
45
|
+
class="inline-flex shrink-0 items-center"
|
|
46
|
+
role="img"
|
|
47
|
+
:aria-label="accessibleLabel"
|
|
48
|
+
:title="accessibleLabel"
|
|
49
|
+
>
|
|
50
|
+
<span
|
|
51
|
+
v-for="(source, index) in sources"
|
|
52
|
+
:key="source.id"
|
|
53
|
+
class="relative inline-flex shrink-0 items-center justify-center"
|
|
54
|
+
:class="[frameClass, index ? (framed ? '-ml-1.5' : 'ml-0.5') : '', dither ? 'ui-source-logo-dithered' : '']"
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
>
|
|
57
|
+
<img
|
|
58
|
+
v-if="source.image"
|
|
59
|
+
:src="source.image"
|
|
60
|
+
alt=""
|
|
61
|
+
:width="iconPixels"
|
|
62
|
+
:height="iconPixels"
|
|
63
|
+
:class="[iconClass, dither ? 'ui-source-logo-dither' : '']"
|
|
64
|
+
class="rounded-full"
|
|
65
|
+
>
|
|
66
|
+
<UiBrandIcon v-else-if="source.brand" size="md" :class="[brandClass, dither ? 'ui-source-logo-dither' : '']" />
|
|
67
|
+
<UiIcon
|
|
68
|
+
v-else
|
|
69
|
+
:name="source.icon"
|
|
70
|
+
:class="[iconClass, source.invertDark ? 'dark:invert' : '', source.tint ?? '', source.mono ? 'text-highlighted' : '', dither ? 'ui-source-logo-dither' : '']"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
/>
|
|
73
|
+
</span>
|
|
74
|
+
</span>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<style scoped>
|
|
78
|
+
/* Mirrors `UiFavicon`'s dither: the frame gets the dot mask, the mark gets the
|
|
79
|
+
desaturation, and the enclosing hover target restores both. */
|
|
80
|
+
.ui-source-logo-dithered::after {
|
|
81
|
+
content: '';
|
|
82
|
+
position: absolute;
|
|
83
|
+
inset: 0;
|
|
84
|
+
border-radius: inherit;
|
|
85
|
+
background-image: radial-gradient(circle, var(--ui-bg) 38%, transparent 44%);
|
|
86
|
+
background-size: 2px 2px;
|
|
87
|
+
opacity: .55;
|
|
88
|
+
pointer-events: none;
|
|
89
|
+
transition: opacity 180ms var(--ease-standard, ease);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.ui-source-logo-dither {
|
|
93
|
+
filter: saturate(.35) contrast(.9) opacity(.85);
|
|
94
|
+
transition: filter 180ms var(--ease-standard, ease);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Hover restores the full mark, but never on a disabled control: a Source that
|
|
98
|
+
cannot be read is dithered to SAY so, and lighting it up contradicts that. */
|
|
99
|
+
:is(a, button, .group):not(:disabled):hover .ui-source-logo-dithered::after {
|
|
100
|
+
opacity: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
:is(a, button, .group):not(:disabled):hover .ui-source-logo-dither {
|
|
104
|
+
filter: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@media (prefers-reduced-motion: reduce) {
|
|
108
|
+
.ui-source-logo-dithered::after,
|
|
109
|
+
.ui-source-logo-dither {
|
|
110
|
+
transition: none;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SourceLogo } from './UiSourceLogos.vue'
|
|
3
|
+
import { UiSourceLogos } from '#components'
|
|
4
|
+
|
|
5
|
+
// The Source rail: the left column of any "pick a Source, then the thing"
|
|
6
|
+
// surface (the Analytics add dialog, the Alert trigger picker). One Source per
|
|
7
|
+
// row, wearing the catalogue's mark. A Source that cannot be read here stays
|
|
8
|
+
// listed, greyed, with its reason: the rail is where a user learns what
|
|
9
|
+
// connecting Bing or an analytics property would buy them. It never decides
|
|
10
|
+
// availability itself; the caller passes `reason` from the Site's own state.
|
|
11
|
+
export interface SourceRailItem {
|
|
12
|
+
identity: SourceLogo
|
|
13
|
+
/**
|
|
14
|
+
* What to call the Source. Usually the identity's own name; Web Analytics
|
|
15
|
+
* passes its Source name while `identity` carries the linked Provider's
|
|
16
|
+
* logo, because a Source and a Provider are different things (GLOSSARY).
|
|
17
|
+
*/
|
|
18
|
+
label: string
|
|
19
|
+
/** Non-null when the Source is listed but cannot be read here. */
|
|
20
|
+
reason: string | null
|
|
21
|
+
/**
|
|
22
|
+
* False hides the reason line under the row (it stays in the title). The
|
|
23
|
+
* caller sets it when several rows share one reason and says it once
|
|
24
|
+
* instead, so the rail does not repeat a sentence four times.
|
|
25
|
+
*/
|
|
26
|
+
caption?: boolean
|
|
27
|
+
/** Trailing count, e.g. rows already added or rules on this Source. Empty hides it. */
|
|
28
|
+
count?: number | string | null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { sources, quiet = false } = defineProps<{
|
|
32
|
+
sources: readonly SourceRailItem[]
|
|
33
|
+
/**
|
|
34
|
+
* True while a search spans every Source: the selection is kept but not
|
|
35
|
+
* shown as current, so the rail does not claim a scope the rows ignore.
|
|
36
|
+
*/
|
|
37
|
+
quiet?: boolean
|
|
38
|
+
}>()
|
|
39
|
+
const emit = defineEmits<{ pick: [id: string] }>()
|
|
40
|
+
const selected = defineModel<string | null>({ default: null })
|
|
41
|
+
function pick(id: string) {
|
|
42
|
+
selected.value = id
|
|
43
|
+
emit('pick', id)
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<ul class="flex flex-row gap-1 overflow-x-auto sm:flex-col sm:overflow-visible" aria-label="Sources">
|
|
49
|
+
<li v-for="s in sources" :key="s.identity.id">
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
class="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm outline-none transition-colors focus-visible:ring-2 focus-visible:ring-primary"
|
|
53
|
+
:class="[
|
|
54
|
+
s.reason ? 'cursor-not-allowed text-dimmed' : 'text-default hover:bg-elevated',
|
|
55
|
+
!quiet && selected === s.identity.id ? 'bg-elevated text-highlighted' : '',
|
|
56
|
+
]"
|
|
57
|
+
:disabled="!!s.reason"
|
|
58
|
+
:aria-current="!quiet && selected === s.identity.id ? 'true' : undefined"
|
|
59
|
+
:title="s.reason ?? undefined"
|
|
60
|
+
@click="pick(s.identity.id)"
|
|
61
|
+
>
|
|
62
|
+
<UiSourceLogos :sources="[s.identity]" size="xs" :dither="!!s.reason" />
|
|
63
|
+
<span class="min-w-0 flex-1 truncate">{{ s.label }}</span>
|
|
64
|
+
<span v-if="!s.reason && s.count" class="font-mono text-mini text-dimmed">{{ s.count }}</span>
|
|
65
|
+
</button>
|
|
66
|
+
<p v-if="s.reason && s.caption !== false" class="hidden px-2 pb-1 text-mini text-dimmed sm:block">
|
|
67
|
+
{{ s.reason }}
|
|
68
|
+
</p>
|
|
69
|
+
</li>
|
|
70
|
+
</ul>
|
|
71
|
+
</template>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { UiStaticTerminalLine } from '../../shared/static-terminal'
|
|
3
|
+
import { useClipboard } from '@vueuse/core'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
import { UiButton } from '#components'
|
|
6
|
+
import { highlightCode } from '../../../shared/rangi'
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
lines,
|
|
10
|
+
copyLines,
|
|
11
|
+
title = 'zsh',
|
|
12
|
+
cwd = '~',
|
|
13
|
+
language = 'bash',
|
|
14
|
+
} = defineProps<{
|
|
15
|
+
lines: UiStaticTerminalLine[]
|
|
16
|
+
/** Plaintext commands to copy when the visible commands mask a secret. */
|
|
17
|
+
copyLines?: UiStaticTerminalLine[]
|
|
18
|
+
title?: string
|
|
19
|
+
cwd?: string
|
|
20
|
+
language?: string
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const { copy, copied } = useClipboard({ legacy: true })
|
|
24
|
+
const copyText = computed(() => (copyLines ?? lines).map(line => line.command).join('\n'))
|
|
25
|
+
const renderedLines = computed(() => lines.map(line => ({
|
|
26
|
+
...line,
|
|
27
|
+
noteHtml: line.note ? highlightCode(`# ${line.note}`, 'bash').html : null,
|
|
28
|
+
commandHtml: highlightCode(line.command, line.language ?? language).html,
|
|
29
|
+
outputHtml: line.output ? highlightCode(line.output, line.outputLanguage ?? 'plain').html : null,
|
|
30
|
+
})))
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="ui-static-terminal overflow-hidden rounded-xl border shadow-sm">
|
|
35
|
+
<div class="ui-static-terminal__chrome flex items-center gap-2 border-b px-3 py-1.5">
|
|
36
|
+
<span class="flex items-center gap-1.5" aria-hidden="true">
|
|
37
|
+
<span class="ui-static-terminal__dot size-2.5 rounded-full" />
|
|
38
|
+
<span class="ui-static-terminal__dot size-2.5 rounded-full" />
|
|
39
|
+
<span class="ui-static-terminal__dot size-2.5 rounded-full" />
|
|
40
|
+
</span>
|
|
41
|
+
<span class="ui-static-terminal__muted ml-1 min-w-0 flex-1 truncate font-mono text-mini">{{ cwd }} · {{ title }}</span>
|
|
42
|
+
<UiButton
|
|
43
|
+
:icon="copied ? 'check' : 'copy'"
|
|
44
|
+
purpose="quiet"
|
|
45
|
+
size="xs"
|
|
46
|
+
class="ui-static-terminal__copy min-h-11 min-w-11 sm:min-h-8 sm:min-w-8"
|
|
47
|
+
:aria-label="copied ? 'Copied' : 'Copy commands'"
|
|
48
|
+
@click="copy(copyText)"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div tabindex="0" class="focus-visible:ring-2 focus-visible:ring-primary overflow-x-auto px-3 py-2.5 font-mono text-xs leading-relaxed">
|
|
53
|
+
<div v-for="(line, index) in renderedLines" :key="index" :class="index > 0 ? 'mt-3' : ''">
|
|
54
|
+
<!-- Rangi escapes source text before creating this token markup. -->
|
|
55
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
56
|
+
<pre v-if="line.noteHtml" class="ui-static-terminal__muted m-0 whitespace-pre"><code class="rangi shj-lang-bash" v-html="line.noteHtml" /></pre>
|
|
57
|
+
<pre class="m-0 whitespace-pre"><span v-if="line.prompt !== false" class="select-none text-primary-500 dark:text-primary-400" aria-hidden="true">$ </span><!-- eslint-disable-next-line vue/no-v-html --><code class="rangi" :class="`shj-lang-${line.language ?? language}`" v-html="line.commandHtml" /></pre>
|
|
58
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
59
|
+
<pre v-if="line.outputHtml" class="ui-static-terminal__output m-0 whitespace-pre"><code class="rangi" :class="`shj-lang-${line.outputLanguage ?? 'plain'}`" v-html="line.outputHtml" /></pre>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<style scoped>
|
|
66
|
+
.ui-static-terminal {
|
|
67
|
+
--terminal-bg: light-dark(oklch(0.985 0.002 286), oklch(0.145 0.008 326));
|
|
68
|
+
--terminal-chrome: light-dark(oklch(0.965 0.004 286), oklch(0.18 0.01 326));
|
|
69
|
+
--terminal-fg: light-dark(oklch(0.21 0.008 286), oklch(0.985 0 0));
|
|
70
|
+
--terminal-muted: light-dark(oklch(0.48 0.012 286), oklch(0.69 0.018 326));
|
|
71
|
+
--terminal-output: light-dark(oklch(0.39 0.01 286), oklch(0.76 0.014 326));
|
|
72
|
+
--terminal-border: light-dark(oklch(0.89 0.008 286), oklch(0.28 0.014 326));
|
|
73
|
+
background: var(--terminal-bg);
|
|
74
|
+
border-color: var(--terminal-border);
|
|
75
|
+
color: var(--terminal-fg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ui-static-terminal__chrome {
|
|
79
|
+
background: var(--terminal-chrome);
|
|
80
|
+
border-color: var(--terminal-border);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ui-static-terminal__dot {
|
|
84
|
+
background: oklch(0.66 0.18 25);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ui-static-terminal__dot:nth-child(2) {
|
|
88
|
+
background: oklch(0.78 0.15 82);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ui-static-terminal__dot:nth-child(3) {
|
|
92
|
+
background: oklch(0.7 0.15 145);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.ui-static-terminal__muted {
|
|
96
|
+
color: var(--terminal-muted);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.ui-static-terminal__output,
|
|
100
|
+
.ui-static-terminal__copy {
|
|
101
|
+
color: var(--terminal-output) !important;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ui-static-terminal__copy:hover {
|
|
105
|
+
color: var(--terminal-fg) !important;
|
|
106
|
+
}
|
|
107
|
+
</style>
|