diff --git a/design/design.ts b/design/design.ts new file mode 100644 index 00000000..10021af8 --- /dev/null +++ b/design/design.ts @@ -0,0 +1,234 @@ +// @unocss-include +// Shared devframe -> @antfu/design class helpers: framework-neutral builders +// returning @antfu/design's semantic shortcut classes, so every surface (Solid, +// Svelte, React, Preact, vanilla) looks identical to the antfu Vue components. +// The `@unocss-include` marker makes UnoCSS emit the runtime-assembled class +// chains below; `design/uno.config.ts` adds this file to every app's content so +// the chains are extracted regardless of framework. +// Tag palette kept literal for extraction: badge-color-blue badge-color-amber +// badge-color-green badge-color-red badge-color-sky badge-color-violet +// badge-color-rose badge-color-teal badge-color-orange badge-color-emerald + +export function cx(...parts: Array): string { + return parts.filter(Boolean).join(' ') +} + +export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link' +export type ButtonSize = 'md' | 'sm' | 'lg' +export interface ButtonProps { variant?: ButtonVariant, size?: ButtonSize, class?: string } + +export function button({ variant = 'primary', size = 'md', class: extra }: ButtonProps = {}): string { + const variantClass: Record = { + primary: 'btn-primary', + secondary: 'btn-action', + outline: 'btn-action', + ghost: 'inline-flex items-center justify-center gap-1.5 rounded px2 py1 op75 hover:op100 hover:bg-active transition disabled:pointer-events-none disabled:op30!', + destructive: 'btn-action text-error border-error/30!', + link: 'inline-flex items-center gap-1.5 color-active hover:underline underline-offset-2', + } + const sizeClass = size === 'sm' + ? (variant === 'primary' ? 'text-sm px-2.5! py-1!' : 'text-sm') + : size === 'lg' ? 'text-base px-4! py-2!' : '' + return cx(variantClass[variant], sizeClass, extra) +} + +export type IconButtonVariant = 'outline' | 'ghost' +export type IconButtonSize = 'md' | 'sm' +export interface IconButtonProps { variant?: IconButtonVariant, size?: IconButtonSize, class?: string } + +export function iconButton({ variant = 'outline', size = 'md', class: extra }: IconButtonProps = {}): string { + const base = variant === 'ghost' ? 'btn-icon' : 'btn-icon-square' + const sizeClass = size === 'sm' ? 'w-7! h-7! text-sm' : '' + return cx(base, sizeClass, extra) +} + +export type BadgeVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline' +export interface BadgeProps { variant?: BadgeVariant, class?: string } + +export function badge({ variant = 'secondary', class: extra }: BadgeProps = {}): string { + const variantClass: Record = { + primary: 'badge-active', + secondary: 'badge-muted', + success: 'badge badge-color-green', + warning: 'badge badge-color-amber', + destructive: 'badge badge-color-red', + outline: 'badge border border-base', + } + return cx(variantClass[variant], extra) +} + +export function tag(color: string, extra?: string): string { + return cx('badge', `badge-color-${color}`, extra) +} + +export function tabsList(extra?: string): string { + return cx('inline-flex items-center gap-1 p-1 rounded-lg bg-secondary w-max', extra) +} + +export function tab(extra?: string): string { + return cx( + 'px-3 py-1 rounded-md text-sm color-muted inline-flex gap-1.5 items-center whitespace-nowrap select-none cursor-pointer transition outline-none hover:color-base focus-visible:ring-2 focus-visible:ring-primary-500/40 data-[state=active]:bg-base data-[state=active]:color-base data-[state=active]:shadow-sm', + extra, + ) +} + +export interface NavTabProps { active?: boolean, class?: string } +export function navTab({ active = false, class: extra }: NavTabProps = {}): string { + return cx( + 'relative inline-flex items-center gap-1.5 max-w-52 px-2 py-1 rounded-md border border-transparent text-sm op-fade select-none cursor-pointer transition hover:op100 hover:bg-active', + active && 'op100! bg-active border-base! color-base', + extra, + ) +} + +export function nav(extra?: string): string { + return cx('flex items-center gap-2 shrink-0 h-nav px-3 border-b border-base bg-base z-nav', extra) +} + +export function navBrand(extra?: string): string { + return cx('flex items-center gap-1.5 shrink-0 font-semibold text-sm select-none', extra) +} + +// Mirrors devframe's `DevframeConnectionStatus` (kept local so this class-helper +// module stays free of package imports); the two share the same string members. +export type ConnectionStatus = 'connecting' | 'connected' | 'unauthorized' | 'disconnected' | 'error' + +export interface ConnectionIndicator { + /** Short status label, e.g. `disconnected`. */ + label: string + /** Class chain for the status dot. */ + dot: string + /** Class chain for the pill wrapper. */ + class: string +} + +const CONNECTION_TONE: Record, { label: string, dot: string }> = { + connecting: { label: 'connecting…', dot: 'bg-neutral-400 animate-pulse' }, + disconnected: { label: 'disconnected', dot: 'bg-error' }, + unauthorized: { label: 'unauthorized', dot: 'bg-warning' }, + error: { label: 'error', dot: 'bg-error' }, +} + +// The shared top-nav connection indicator: a small status dot + label. Returns +// `null` when the client is `connected`, so every surface renders the indicator +// only while the connection is not live. +export function connectionIndicator(status: ConnectionStatus, extra?: string): ConnectionIndicator | null { + if (status === 'connected') + return null + const tone = CONNECTION_TONE[status] + return { + label: tone.label, + dot: cx('inline-block size-1.5 rounded-full shrink-0', tone.dot), + class: cx('flex items-center gap-1.5 shrink-0 text-xs color-muted select-none', extra), + } +} + +export interface ConnectionStateCopy { + /** Phosphor icon for the state glyph. */ + icon: string + /** Short heading, e.g. `Disconnected`. */ + title: string + /** One-line explanation of the state and how to recover. */ + body: string + /** Whether to offer the reload recovery button (every state but `connecting`). */ + reloadable: boolean + /** Whether the glyph should animate while the handshake is in flight. */ + spin: boolean +} + +const CONNECTION_STATE: Record, ConnectionStateCopy> = { + connecting: { + icon: 'i-ph-plugs-connected-duotone', + title: 'Connecting…', + body: 'Establishing a connection to the devframe server.', + reloadable: false, + spin: true, + }, + disconnected: { + icon: 'i-ph-plugs-duotone', + title: 'Disconnected', + body: 'Lost the connection to the devframe server. Reload once it is back up.', + reloadable: true, + spin: false, + }, + unauthorized: { + icon: 'i-ph-lock-key-duotone', + title: 'Not authorized', + body: 'This client isn’t authorized. Reopen the link printed by your dev server, then reload.', + reloadable: true, + spin: false, + }, + error: { + icon: 'i-ph-warning-octagon-duotone', + title: 'Connection failed', + body: 'Could not reach the devframe server.', + reloadable: true, + spin: false, + }, +} + +// The shared full-panel connection state copy: shown whenever the client isn't +// `connected`, so a surface never sits on an infinite spinner without saying +// why. Returns `null` when connected. Pair with the `connection*` class builders +// below so every surface renders the identical centered glyph + title + body. +export function connectionState(status: ConnectionStatus): ConnectionStateCopy | null { + if (status === 'connected') + return null + return CONNECTION_STATE[status] +} + +// Centered fill for the full-panel state; each surface adds its own fill +// strategy (`h-full`, `h-svh w-full`, `absolute inset-0`, …) via `extra`. +export function connectionPanel(extra?: string): string { + return cx('flex flex-col items-center justify-center gap-4 bg-base p-8 text-center', extra) +} + +export function connectionGlyph(spin = false, extra?: string): string { + return cx('text-4xl color-active', spin && 'animate-pulse', extra) +} + +export function connectionTitle(extra?: string): string { + return cx('text-lg font-medium color-base', extra) +} + +export function connectionBody(extra?: string): string { + return cx('max-w-sm text-sm color-muted', extra) +} + +export function connectionDetail(extra?: string): string { + return cx('mt-1 max-w-sm break-words font-mono text-xs color-faint', extra) +} + +export function toolbar(extra?: string): string { + return cx('flex items-center gap-2 shrink-0 h-8 px-2.5 border-b border-base bg-secondary text-sm', extra) +} + +export function card(extra?: string): string { + return cx('flex flex-col rounded-xl border border-base bg-base shadow-sm', extra) +} + +export function panel(extra?: string): string { + return cx('rounded-lg border border-base bg-base', extra) +} + +export function input(extra?: string): string { + return cx('w-full min-w-0 rounded border border-base bg-base px-2.5 py-1 text-sm outline-none transition placeholder:color-faint focus-visible:border-active focus-visible:ring-2 focus-visible:ring-primary-500/40', extra) +} + +export function link(extra?: string): string { + return cx('color-active hover:underline underline-offset-2', extra) +} + +export type DotState = 'running' | 'idle' | 'error' +export function dot(state: DotState, extra?: string): string { + const stateClass: Record = { + running: 'bg-success', + idle: 'bg-neutral-400', + error: 'bg-error', + } + return cx('inline-block size-1.5 rounded-full shrink-0', stateClass[state], extra) +} + +export function spinner(extra?: string): string { + return cx('inline-block size-4 rounded-full border-2 border-current border-t-transparent animate-spin', extra) +} diff --git a/design/uno.config.ts b/design/uno.config.ts new file mode 100644 index 00000000..e8ff4246 --- /dev/null +++ b/design/uno.config.ts @@ -0,0 +1,55 @@ +import { fileURLToPath } from 'node:url' +import { presetAnthonyDesign } from '@antfu/design/unocss' +import { + defineConfig, + presetIcons, + presetWebFonts, + presetWind4, + transformerDirectives, + transformerVariantGroup, +} from 'unocss' + +// Shared devframe UnoCSS base. Every plugin and example composes `@antfu/design` +// the same way — its preset (tuned to devframe's sage green) over a Wind4 base, +// Phosphor icons, DM Sans/Mono web fonts, and the directive/variant-group +// transformers — so the surfaces look and feel like one product across +// frameworks. Each app extends this via `mergeConfigs([designConfig, { … }])` +// and contributes only its own extraction globs (and any safelist). +// +// The shared web fonts (`sans`/`mono`), the named `z-*` layers and the `h-nav` +// navbar height live here so every surface shares one font stack, one z-index +// scale and one fixed navbar height. The `@antfu/design` preset blocks plain +// `z-`, so the layers are named on purpose. +export const designConfig = defineConfig({ + presets: [ + presetAnthonyDesign({ primary: '#3a6a45' }), + presetWind4(), + presetIcons({ scale: 1.1 }), + presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), + ], + transformers: [transformerDirectives(), transformerVariantGroup()], + // The shared class-helper builders (`design/design.ts`) assemble their class + // chains at runtime, so every app scans that one file (it carries + // `@unocss-include`) for extraction regardless of its own framework globs. + content: { + filesystem: [fileURLToPath(new URL('./design.ts', import.meta.url))], + }, + // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle + // shared border color (matching `border-base`) for unqualified borders. + preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], + shortcuts: { + // Fixed navbar height, shared by every surface's top nav. + 'h-nav': 'h-10', + // Named z-index layers, shared across every surface. + 'z-nav': 'z-[30]', + 'z-dropdown': 'z-[40]', + 'z-tooltip': 'z-[45]', + 'z-toast': 'z-[50]', + 'z-modal-backdrop': 'z-[60]', + 'z-modal-content': 'z-[70]', + 'z-drawer-backdrop': 'z-[80]', + 'z-drawer-content': 'z-[90]', + }, +}) + +export default designConfig diff --git a/examples/files-inspector/src/client/design.ts b/examples/files-inspector/src/client/design.ts index 9bedf72a..d8e49c73 100644 --- a/examples/files-inspector/src/client/design.ts +++ b/examples/files-inspector/src/client/design.ts @@ -1,122 +1,3 @@ -// @unocss-include -// Co-located devframe -> @antfu/design class helpers: framework-neutral builders -// returning @antfu/design's semantic shortcut classes, so this surface looks -// identical to the antfu Vue components. The `@unocss-include` marker makes -// UnoCSS emit the runtime-assembled class chains below. -// Tag palette kept literal for extraction: badge-color-blue badge-color-amber -// badge-color-green badge-color-red badge-color-sky badge-color-violet -// badge-color-rose badge-color-teal badge-color-orange badge-color-emerald - -export function cx(...parts: Array): string { - return parts.filter(Boolean).join(' ') -} - -export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link' -export type ButtonSize = 'md' | 'sm' | 'lg' -export interface ButtonProps { variant?: ButtonVariant, size?: ButtonSize, class?: string } - -export function button({ variant = 'primary', size = 'md', class: extra }: ButtonProps = {}): string { - const variantClass: Record = { - primary: 'btn-primary', - secondary: 'btn-action', - outline: 'btn-action', - ghost: 'inline-flex items-center justify-center gap-1.5 rounded px2 py1 op75 hover:op100 hover:bg-active transition disabled:pointer-events-none disabled:op30!', - destructive: 'btn-action text-error border-error/30!', - link: 'inline-flex items-center gap-1.5 color-active hover:underline underline-offset-2', - } - const sizeClass = size === 'sm' - ? (variant === 'primary' ? 'text-sm px-2.5! py-1!' : 'text-sm') - : size === 'lg' ? 'text-base px-4! py-2!' : '' - return cx(variantClass[variant], sizeClass, extra) -} - -export type IconButtonVariant = 'outline' | 'ghost' -export type IconButtonSize = 'md' | 'sm' -export interface IconButtonProps { variant?: IconButtonVariant, size?: IconButtonSize, class?: string } - -export function iconButton({ variant = 'outline', size = 'md', class: extra }: IconButtonProps = {}): string { - const base = variant === 'ghost' ? 'btn-icon' : 'btn-icon-square' - const sizeClass = size === 'sm' ? 'w-7! h-7! text-sm' : '' - return cx(base, sizeClass, extra) -} - -export type BadgeVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline' -export interface BadgeProps { variant?: BadgeVariant, class?: string } - -export function badge({ variant = 'secondary', class: extra }: BadgeProps = {}): string { - const variantClass: Record = { - primary: 'badge-active', - secondary: 'badge-muted', - success: 'badge badge-color-green', - warning: 'badge badge-color-amber', - destructive: 'badge badge-color-red', - outline: 'badge border border-base', - } - return cx(variantClass[variant], extra) -} - -export function tag(color: string, extra?: string): string { - return cx('badge', `badge-color-${color}`, extra) -} - -export function tabsList(extra?: string): string { - return cx('inline-flex items-center gap-1 p-1 rounded-lg bg-secondary w-max', extra) -} - -export function tab(extra?: string): string { - return cx( - 'px-3 py-1 rounded-md text-sm color-muted inline-flex gap-1.5 items-center whitespace-nowrap select-none cursor-pointer transition outline-none hover:color-base focus-visible:ring-2 focus-visible:ring-primary-500/40 data-[state=active]:bg-base data-[state=active]:color-base data-[state=active]:shadow-sm', - extra, - ) -} - -export interface NavTabProps { active?: boolean, class?: string } -export function navTab({ active = false, class: extra }: NavTabProps = {}): string { - return cx( - 'relative inline-flex items-center gap-1.5 max-w-52 px-2 py-1 rounded-md border border-transparent text-sm op-fade select-none cursor-pointer transition hover:op100 hover:bg-active', - active && 'op100! bg-active border-base! color-base', - extra, - ) -} - -export function nav(extra?: string): string { - return cx('flex items-center gap-2 shrink-0 h-10 px-3 border-b border-base bg-base z-nav', extra) -} - -export function navBrand(extra?: string): string { - return cx('flex items-center gap-1.5 shrink-0 font-semibold text-sm select-none', extra) -} - -export function toolbar(extra?: string): string { - return cx('flex items-center gap-2 shrink-0 h-8 px-2.5 border-b border-base bg-secondary text-sm', extra) -} - -export function card(extra?: string): string { - return cx('flex flex-col rounded-xl border border-base bg-base shadow-sm', extra) -} - -export function panel(extra?: string): string { - return cx('rounded-lg border border-base bg-base', extra) -} - -export function input(extra?: string): string { - return cx('w-full min-w-0 rounded border border-base bg-base px-2.5 py-1 text-sm outline-none transition placeholder:color-faint focus-visible:border-active focus-visible:ring-2 focus-visible:ring-primary-500/40', extra) -} - -export function link(extra?: string): string { - return cx('color-active hover:underline underline-offset-2', extra) -} - -export type DotState = 'running' | 'idle' | 'error' -export function dot(state: DotState, extra?: string): string { - const stateClass: Record = { - running: 'bg-success', - idle: 'bg-neutral-400', - error: 'bg-error', - } - return cx('inline-block size-1.5 rounded-full shrink-0', stateClass[state], extra) -} - -export function spinner(extra?: string): string { - return cx('inline-block size-4 rounded-full border-2 border-current border-t-transparent animate-spin', extra) -} +// Re-exports the shared devframe class-helper builders (see +// `design/design.ts`) so this surface stays in lockstep with every other. +export * from '../../../../design/design' diff --git a/examples/files-inspector/uno.config.ts b/examples/files-inspector/uno.config.ts index f6e6ff90..5eff85f7 100644 --- a/examples/files-inspector/uno.config.ts +++ b/examples/files-inspector/uno.config.ts @@ -1,36 +1,12 @@ -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' -// This example's Preact SPA uses `@antfu/design` directly for its semantic -// tokens, class vocabulary and Phosphor icons — matching the built-in plugins. -// The named `z-*` layers are the app's to own (the preset blocks plain `z-`). -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', +// This example's Preact SPA composes the shared devframe base (see +// `design/uno.config.ts`) — matching the built-in plugins — and adds only its +// own extraction globs. +export default mergeConfigs([ + designConfig, + { + content: { pipeline: { include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/] } }, }, - content: { pipeline: { include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/] } }, -}) +]) diff --git a/examples/minimal-next-devframe-hub/src/client/app/page.tsx b/examples/minimal-next-devframe-hub/src/client/app/page.tsx index 5f23c4af..9b9106b4 100644 --- a/examples/minimal-next-devframe-hub/src/client/app/page.tsx +++ b/examples/minimal-next-devframe-hub/src/client/app/page.tsx @@ -156,8 +156,11 @@ export default function Page() { return (
-
-

Minimal Next Devframe Hub

+
+

+ + Minimal Next Devframe Hub +

{status.text} diff --git a/examples/minimal-next-devframe-hub/uno.config.ts b/examples/minimal-next-devframe-hub/uno.config.ts index a1630af3..e5cb5469 100644 --- a/examples/minimal-next-devframe-hub/uno.config.ts +++ b/examples/minimal-next-devframe-hub/uno.config.ts @@ -1,41 +1,18 @@ import { fileURLToPath } from 'node:url' -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' -// `@unocss/postcss` (see src/client/postcss.config.mjs) loads this config. -// Absolute globs keep class extraction working regardless of the directory +// The hub UI composes the shared devframe base (see `design/uno.config.ts`). +// `@unocss/postcss` (see src/client/postcss.config.mjs) loads this config; +// absolute globs keep class extraction working regardless of the directory // PostCSS runs in (Next builds from `src/client`). const client = fileURLToPath(new URL('./src/client', import.meta.url)) -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', +export default mergeConfigs([ + designConfig, + { + content: { + filesystem: [`${client}/app/**/*.{ts,tsx}`], + }, }, - content: { - filesystem: [`${client}/app/**/*.{ts,tsx}`], - }, -}) +]) diff --git a/examples/minimal-vite-devframe-hub/index.html b/examples/minimal-vite-devframe-hub/index.html index d34a1bbd..f936f2a7 100644 --- a/examples/minimal-vite-devframe-hub/index.html +++ b/examples/minimal-vite-devframe-hub/index.html @@ -14,8 +14,11 @@

-
-

Minimal Vite Devframe Hub

+
+

+ + Minimal Vite Devframe Hub +

Connecting…

a ~120-line, vite-devtools-style hub you can copy

diff --git a/examples/minimal-vite-devframe-hub/package.json b/examples/minimal-vite-devframe-hub/package.json index 6c4f1118..afbdfa85 100644 --- a/examples/minimal-vite-devframe-hub/package.json +++ b/examples/minimal-vite-devframe-hub/package.json @@ -6,7 +6,7 @@ "description": "Protocol-witness example — a tiny Vite Devframe Hub built on @devframes/hub that exercises every hub subsystem end-to-end.", "homepage": "https://github.com/devframes/devframe/tree/main/examples/minimal-vite-devframe-hub", "scripts": { - "dev": "vite --host", + "dev": "vite", "build": "vite build", "typecheck": "tsc --noEmit" }, diff --git a/examples/minimal-vite-devframe-hub/uno.config.ts b/examples/minimal-vite-devframe-hub/uno.config.ts index 120a8807..be8319c2 100644 --- a/examples/minimal-vite-devframe-hub/uno.config.ts +++ b/examples/minimal-vite-devframe-hub/uno.config.ts @@ -1,39 +1,13 @@ -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' -// The hub UI uses `@antfu/design` directly — its preset (tuned to devframe's -// sage green) over a Wind4 base, Phosphor icons, DM Sans/Mono and the -// directive/variant-group transformers. Pair with `@antfu/design/styles.css` -// (imported in `src/client/main.ts`). The named `z-*` layers are the app's to -// own (the preset blocks plain `z-`). `.ts` is opted into extraction since -// the hub authors its class strings in vanilla `src/client/main.ts`. -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', +// The hub UI composes the shared devframe base (see `design/uno.config.ts`). +// Pair with `@antfu/design/styles.css` (imported in `src/client/main.ts`). `.ts` +// is opted into extraction since the hub authors its class strings in vanilla +// `src/client/main.ts`. +export default mergeConfigs([ + designConfig, + { + content: { pipeline: { include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/] } }, }, - content: { pipeline: { include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/] } }, -}) +]) diff --git a/examples/next-runtime-snapshot/src/client/app/design.ts b/examples/next-runtime-snapshot/src/client/app/design.ts index 9bedf72a..af3b99e6 100644 --- a/examples/next-runtime-snapshot/src/client/app/design.ts +++ b/examples/next-runtime-snapshot/src/client/app/design.ts @@ -1,122 +1,3 @@ -// @unocss-include -// Co-located devframe -> @antfu/design class helpers: framework-neutral builders -// returning @antfu/design's semantic shortcut classes, so this surface looks -// identical to the antfu Vue components. The `@unocss-include` marker makes -// UnoCSS emit the runtime-assembled class chains below. -// Tag palette kept literal for extraction: badge-color-blue badge-color-amber -// badge-color-green badge-color-red badge-color-sky badge-color-violet -// badge-color-rose badge-color-teal badge-color-orange badge-color-emerald - -export function cx(...parts: Array): string { - return parts.filter(Boolean).join(' ') -} - -export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link' -export type ButtonSize = 'md' | 'sm' | 'lg' -export interface ButtonProps { variant?: ButtonVariant, size?: ButtonSize, class?: string } - -export function button({ variant = 'primary', size = 'md', class: extra }: ButtonProps = {}): string { - const variantClass: Record = { - primary: 'btn-primary', - secondary: 'btn-action', - outline: 'btn-action', - ghost: 'inline-flex items-center justify-center gap-1.5 rounded px2 py1 op75 hover:op100 hover:bg-active transition disabled:pointer-events-none disabled:op30!', - destructive: 'btn-action text-error border-error/30!', - link: 'inline-flex items-center gap-1.5 color-active hover:underline underline-offset-2', - } - const sizeClass = size === 'sm' - ? (variant === 'primary' ? 'text-sm px-2.5! py-1!' : 'text-sm') - : size === 'lg' ? 'text-base px-4! py-2!' : '' - return cx(variantClass[variant], sizeClass, extra) -} - -export type IconButtonVariant = 'outline' | 'ghost' -export type IconButtonSize = 'md' | 'sm' -export interface IconButtonProps { variant?: IconButtonVariant, size?: IconButtonSize, class?: string } - -export function iconButton({ variant = 'outline', size = 'md', class: extra }: IconButtonProps = {}): string { - const base = variant === 'ghost' ? 'btn-icon' : 'btn-icon-square' - const sizeClass = size === 'sm' ? 'w-7! h-7! text-sm' : '' - return cx(base, sizeClass, extra) -} - -export type BadgeVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline' -export interface BadgeProps { variant?: BadgeVariant, class?: string } - -export function badge({ variant = 'secondary', class: extra }: BadgeProps = {}): string { - const variantClass: Record = { - primary: 'badge-active', - secondary: 'badge-muted', - success: 'badge badge-color-green', - warning: 'badge badge-color-amber', - destructive: 'badge badge-color-red', - outline: 'badge border border-base', - } - return cx(variantClass[variant], extra) -} - -export function tag(color: string, extra?: string): string { - return cx('badge', `badge-color-${color}`, extra) -} - -export function tabsList(extra?: string): string { - return cx('inline-flex items-center gap-1 p-1 rounded-lg bg-secondary w-max', extra) -} - -export function tab(extra?: string): string { - return cx( - 'px-3 py-1 rounded-md text-sm color-muted inline-flex gap-1.5 items-center whitespace-nowrap select-none cursor-pointer transition outline-none hover:color-base focus-visible:ring-2 focus-visible:ring-primary-500/40 data-[state=active]:bg-base data-[state=active]:color-base data-[state=active]:shadow-sm', - extra, - ) -} - -export interface NavTabProps { active?: boolean, class?: string } -export function navTab({ active = false, class: extra }: NavTabProps = {}): string { - return cx( - 'relative inline-flex items-center gap-1.5 max-w-52 px-2 py-1 rounded-md border border-transparent text-sm op-fade select-none cursor-pointer transition hover:op100 hover:bg-active', - active && 'op100! bg-active border-base! color-base', - extra, - ) -} - -export function nav(extra?: string): string { - return cx('flex items-center gap-2 shrink-0 h-10 px-3 border-b border-base bg-base z-nav', extra) -} - -export function navBrand(extra?: string): string { - return cx('flex items-center gap-1.5 shrink-0 font-semibold text-sm select-none', extra) -} - -export function toolbar(extra?: string): string { - return cx('flex items-center gap-2 shrink-0 h-8 px-2.5 border-b border-base bg-secondary text-sm', extra) -} - -export function card(extra?: string): string { - return cx('flex flex-col rounded-xl border border-base bg-base shadow-sm', extra) -} - -export function panel(extra?: string): string { - return cx('rounded-lg border border-base bg-base', extra) -} - -export function input(extra?: string): string { - return cx('w-full min-w-0 rounded border border-base bg-base px-2.5 py-1 text-sm outline-none transition placeholder:color-faint focus-visible:border-active focus-visible:ring-2 focus-visible:ring-primary-500/40', extra) -} - -export function link(extra?: string): string { - return cx('color-active hover:underline underline-offset-2', extra) -} - -export type DotState = 'running' | 'idle' | 'error' -export function dot(state: DotState, extra?: string): string { - const stateClass: Record = { - running: 'bg-success', - idle: 'bg-neutral-400', - error: 'bg-error', - } - return cx('inline-block size-1.5 rounded-full shrink-0', stateClass[state], extra) -} - -export function spinner(extra?: string): string { - return cx('inline-block size-4 rounded-full border-2 border-current border-t-transparent animate-spin', extra) -} +// Re-exports the shared devframe class-helper builders (see +// `design/design.ts`) so this surface stays in lockstep with every other. +export * from '../../../../../design/design' diff --git a/examples/next-runtime-snapshot/uno.config.ts b/examples/next-runtime-snapshot/uno.config.ts index 2499c33c..4f6e6bf8 100644 --- a/examples/next-runtime-snapshot/uno.config.ts +++ b/examples/next-runtime-snapshot/uno.config.ts @@ -1,42 +1,19 @@ import { fileURLToPath } from 'node:url' -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' +// This example composes the shared devframe base (see `design/uno.config.ts`). // `@unocss/postcss` (see src/client/postcss.config.mjs) loads this config; the // absolute glob keeps class extraction working regardless of the directory Next // builds from. The co-located `app/design.ts` (carrying `@unocss-include`) is // covered by the same glob. const client = fileURLToPath(new URL('./src/client', import.meta.url)) -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', +export default mergeConfigs([ + designConfig, + { + content: { + filesystem: [`${client}/app/**/*.{ts,tsx}`], + }, }, - content: { - filesystem: [`${client}/app/**/*.{ts,tsx}`], - }, -}) +]) diff --git a/examples/storybook-hub/package.json b/examples/storybook-hub/package.json index 86ccede2..68c78d9e 100644 --- a/examples/storybook-hub/package.json +++ b/examples/storybook-hub/package.json @@ -6,9 +6,9 @@ "description": "Example — a devframe hub that docks every built-in plugin's Storybook (lazily spawned in dev, served static in build) alongside the live terminals plugin.", "homepage": "https://github.com/devframes/devframe/tree/main/examples/storybook-hub", "scripts": { - "dev": "vite --host", + "dev": "vite", "build": "vite build", - "preview": "vite preview --host", + "preview": "vite preview", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/examples/storybook-hub/uno.config.ts b/examples/storybook-hub/uno.config.ts index 120a8807..be8319c2 100644 --- a/examples/storybook-hub/uno.config.ts +++ b/examples/storybook-hub/uno.config.ts @@ -1,39 +1,13 @@ -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' -// The hub UI uses `@antfu/design` directly — its preset (tuned to devframe's -// sage green) over a Wind4 base, Phosphor icons, DM Sans/Mono and the -// directive/variant-group transformers. Pair with `@antfu/design/styles.css` -// (imported in `src/client/main.ts`). The named `z-*` layers are the app's to -// own (the preset blocks plain `z-`). `.ts` is opted into extraction since -// the hub authors its class strings in vanilla `src/client/main.ts`. -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', +// The hub UI composes the shared devframe base (see `design/uno.config.ts`). +// Pair with `@antfu/design/styles.css` (imported in `src/client/main.ts`). `.ts` +// is opted into extraction since the hub authors its class strings in vanilla +// `src/client/main.ts`. +export default mergeConfigs([ + designConfig, + { + content: { pipeline: { include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/] } }, }, - content: { pipeline: { include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/] } }, -}) +]) diff --git a/examples/streaming-chat/src/client/design.ts b/examples/streaming-chat/src/client/design.ts index 9bedf72a..d8e49c73 100644 --- a/examples/streaming-chat/src/client/design.ts +++ b/examples/streaming-chat/src/client/design.ts @@ -1,122 +1,3 @@ -// @unocss-include -// Co-located devframe -> @antfu/design class helpers: framework-neutral builders -// returning @antfu/design's semantic shortcut classes, so this surface looks -// identical to the antfu Vue components. The `@unocss-include` marker makes -// UnoCSS emit the runtime-assembled class chains below. -// Tag palette kept literal for extraction: badge-color-blue badge-color-amber -// badge-color-green badge-color-red badge-color-sky badge-color-violet -// badge-color-rose badge-color-teal badge-color-orange badge-color-emerald - -export function cx(...parts: Array): string { - return parts.filter(Boolean).join(' ') -} - -export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link' -export type ButtonSize = 'md' | 'sm' | 'lg' -export interface ButtonProps { variant?: ButtonVariant, size?: ButtonSize, class?: string } - -export function button({ variant = 'primary', size = 'md', class: extra }: ButtonProps = {}): string { - const variantClass: Record = { - primary: 'btn-primary', - secondary: 'btn-action', - outline: 'btn-action', - ghost: 'inline-flex items-center justify-center gap-1.5 rounded px2 py1 op75 hover:op100 hover:bg-active transition disabled:pointer-events-none disabled:op30!', - destructive: 'btn-action text-error border-error/30!', - link: 'inline-flex items-center gap-1.5 color-active hover:underline underline-offset-2', - } - const sizeClass = size === 'sm' - ? (variant === 'primary' ? 'text-sm px-2.5! py-1!' : 'text-sm') - : size === 'lg' ? 'text-base px-4! py-2!' : '' - return cx(variantClass[variant], sizeClass, extra) -} - -export type IconButtonVariant = 'outline' | 'ghost' -export type IconButtonSize = 'md' | 'sm' -export interface IconButtonProps { variant?: IconButtonVariant, size?: IconButtonSize, class?: string } - -export function iconButton({ variant = 'outline', size = 'md', class: extra }: IconButtonProps = {}): string { - const base = variant === 'ghost' ? 'btn-icon' : 'btn-icon-square' - const sizeClass = size === 'sm' ? 'w-7! h-7! text-sm' : '' - return cx(base, sizeClass, extra) -} - -export type BadgeVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline' -export interface BadgeProps { variant?: BadgeVariant, class?: string } - -export function badge({ variant = 'secondary', class: extra }: BadgeProps = {}): string { - const variantClass: Record = { - primary: 'badge-active', - secondary: 'badge-muted', - success: 'badge badge-color-green', - warning: 'badge badge-color-amber', - destructive: 'badge badge-color-red', - outline: 'badge border border-base', - } - return cx(variantClass[variant], extra) -} - -export function tag(color: string, extra?: string): string { - return cx('badge', `badge-color-${color}`, extra) -} - -export function tabsList(extra?: string): string { - return cx('inline-flex items-center gap-1 p-1 rounded-lg bg-secondary w-max', extra) -} - -export function tab(extra?: string): string { - return cx( - 'px-3 py-1 rounded-md text-sm color-muted inline-flex gap-1.5 items-center whitespace-nowrap select-none cursor-pointer transition outline-none hover:color-base focus-visible:ring-2 focus-visible:ring-primary-500/40 data-[state=active]:bg-base data-[state=active]:color-base data-[state=active]:shadow-sm', - extra, - ) -} - -export interface NavTabProps { active?: boolean, class?: string } -export function navTab({ active = false, class: extra }: NavTabProps = {}): string { - return cx( - 'relative inline-flex items-center gap-1.5 max-w-52 px-2 py-1 rounded-md border border-transparent text-sm op-fade select-none cursor-pointer transition hover:op100 hover:bg-active', - active && 'op100! bg-active border-base! color-base', - extra, - ) -} - -export function nav(extra?: string): string { - return cx('flex items-center gap-2 shrink-0 h-10 px-3 border-b border-base bg-base z-nav', extra) -} - -export function navBrand(extra?: string): string { - return cx('flex items-center gap-1.5 shrink-0 font-semibold text-sm select-none', extra) -} - -export function toolbar(extra?: string): string { - return cx('flex items-center gap-2 shrink-0 h-8 px-2.5 border-b border-base bg-secondary text-sm', extra) -} - -export function card(extra?: string): string { - return cx('flex flex-col rounded-xl border border-base bg-base shadow-sm', extra) -} - -export function panel(extra?: string): string { - return cx('rounded-lg border border-base bg-base', extra) -} - -export function input(extra?: string): string { - return cx('w-full min-w-0 rounded border border-base bg-base px-2.5 py-1 text-sm outline-none transition placeholder:color-faint focus-visible:border-active focus-visible:ring-2 focus-visible:ring-primary-500/40', extra) -} - -export function link(extra?: string): string { - return cx('color-active hover:underline underline-offset-2', extra) -} - -export type DotState = 'running' | 'idle' | 'error' -export function dot(state: DotState, extra?: string): string { - const stateClass: Record = { - running: 'bg-success', - idle: 'bg-neutral-400', - error: 'bg-error', - } - return cx('inline-block size-1.5 rounded-full shrink-0', stateClass[state], extra) -} - -export function spinner(extra?: string): string { - return cx('inline-block size-4 rounded-full border-2 border-current border-t-transparent animate-spin', extra) -} +// Re-exports the shared devframe class-helper builders (see +// `design/design.ts`) so this surface stays in lockstep with every other. +export * from '../../../../design/design' diff --git a/examples/streaming-chat/uno.config.ts b/examples/streaming-chat/uno.config.ts index 3eb3a226..c644da22 100644 --- a/examples/streaming-chat/uno.config.ts +++ b/examples/streaming-chat/uno.config.ts @@ -1,33 +1,11 @@ -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', +// This example's Preact SPA composes the shared devframe base (see +// `design/uno.config.ts`) and adds only its own extraction globs. +export default mergeConfigs([ + designConfig, + { + content: { pipeline: { include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/] } }, }, - content: { pipeline: { include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/] } }, -}) +]) diff --git a/plugins/a11y/src/spa/design.ts b/plugins/a11y/src/spa/design.ts index 7667ef8c..d8e49c73 100644 --- a/plugins/a11y/src/spa/design.ts +++ b/plugins/a11y/src/spa/design.ts @@ -1,122 +1,3 @@ -// @unocss-include -// Co-located devframe -> @antfu/design class helpers: framework-neutral builders -// returning @antfu/design's semantic shortcut classes, so this Solid surface -// looks identical to the antfu Vue components. The `@unocss-include` marker makes -// UnoCSS emit the runtime-assembled class chains below. -// Tag palette kept literal for extraction: badge-color-blue badge-color-amber -// badge-color-green badge-color-red badge-color-sky badge-color-violet -// badge-color-rose badge-color-teal badge-color-orange badge-color-emerald - -export function cx(...parts: Array): string { - return parts.filter(Boolean).join(' ') -} - -export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link' -export type ButtonSize = 'md' | 'sm' | 'lg' -export interface ButtonProps { variant?: ButtonVariant, size?: ButtonSize, class?: string } - -export function button({ variant = 'primary', size = 'md', class: extra }: ButtonProps = {}): string { - const variantClass: Record = { - primary: 'btn-primary', - secondary: 'btn-action', - outline: 'btn-action', - ghost: 'inline-flex items-center justify-center gap-1.5 rounded px2 py1 op75 hover:op100 hover:bg-active transition disabled:pointer-events-none disabled:op30!', - destructive: 'btn-action text-error border-error/30!', - link: 'inline-flex items-center gap-1.5 color-active hover:underline underline-offset-2', - } - const sizeClass = size === 'sm' - ? (variant === 'primary' ? 'text-sm px-2.5! py-1!' : 'text-sm') - : size === 'lg' ? 'text-base px-4! py-2!' : '' - return cx(variantClass[variant], sizeClass, extra) -} - -export type IconButtonVariant = 'outline' | 'ghost' -export type IconButtonSize = 'md' | 'sm' -export interface IconButtonProps { variant?: IconButtonVariant, size?: IconButtonSize, class?: string } - -export function iconButton({ variant = 'outline', size = 'md', class: extra }: IconButtonProps = {}): string { - const base = variant === 'ghost' ? 'btn-icon' : 'btn-icon-square' - const sizeClass = size === 'sm' ? 'w-7! h-7! text-sm' : '' - return cx(base, sizeClass, extra) -} - -export type BadgeVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline' -export interface BadgeProps { variant?: BadgeVariant, class?: string } - -export function badge({ variant = 'secondary', class: extra }: BadgeProps = {}): string { - const variantClass: Record = { - primary: 'badge-active', - secondary: 'badge-muted', - success: 'badge badge-color-green', - warning: 'badge badge-color-amber', - destructive: 'badge badge-color-red', - outline: 'badge border border-base', - } - return cx(variantClass[variant], extra) -} - -export function tag(color: string, extra?: string): string { - return cx('badge', `badge-color-${color}`, extra) -} - -export function tabsList(extra?: string): string { - return cx('inline-flex items-center gap-1 p-1 rounded-lg bg-secondary w-max', extra) -} - -export function tab(extra?: string): string { - return cx( - 'px-3 py-1 rounded-md text-sm color-muted inline-flex gap-1.5 items-center whitespace-nowrap select-none cursor-pointer transition outline-none hover:color-base focus-visible:ring-2 focus-visible:ring-primary-500/40 data-[state=active]:bg-base data-[state=active]:color-base data-[state=active]:shadow-sm', - extra, - ) -} - -export interface NavTabProps { active?: boolean, class?: string } -export function navTab({ active = false, class: extra }: NavTabProps = {}): string { - return cx( - 'relative inline-flex items-center gap-1.5 max-w-52 px-2 py-1 rounded-md border border-transparent text-sm op-fade select-none cursor-pointer transition hover:op100 hover:bg-active', - active && 'op100! bg-active border-base! color-base', - extra, - ) -} - -export function nav(extra?: string): string { - return cx('flex items-center gap-2 shrink-0 h-10 px-3 border-b border-base bg-base z-nav', extra) -} - -export function navBrand(extra?: string): string { - return cx('flex items-center gap-1.5 shrink-0 font-semibold text-sm select-none', extra) -} - -export function toolbar(extra?: string): string { - return cx('flex items-center gap-2 shrink-0 h-8 px-2.5 border-b border-base bg-secondary text-sm', extra) -} - -export function card(extra?: string): string { - return cx('flex flex-col rounded-xl border border-base bg-base shadow-sm', extra) -} - -export function panel(extra?: string): string { - return cx('rounded-lg border border-base bg-base', extra) -} - -export function input(extra?: string): string { - return cx('w-full min-w-0 rounded border border-base bg-base px-2.5 py-1 text-sm outline-none transition placeholder:color-faint focus-visible:border-active focus-visible:ring-2 focus-visible:ring-primary-500/40', extra) -} - -export function link(extra?: string): string { - return cx('color-active hover:underline underline-offset-2', extra) -} - -export type DotState = 'running' | 'idle' | 'error' -export function dot(state: DotState, extra?: string): string { - const stateClass: Record = { - running: 'bg-success', - idle: 'bg-neutral-400', - error: 'bg-error', - } - return cx('inline-block size-1.5 rounded-full shrink-0', stateClass[state], extra) -} - -export function spinner(extra?: string): string { - return cx('inline-block size-4 rounded-full border-2 border-current border-t-transparent animate-spin', extra) -} +// Re-exports the shared devframe class-helper builders (see +// `design/design.ts`) so this surface stays in lockstep with every other. +export * from '../../../../design/design' diff --git a/plugins/a11y/uno.config.ts b/plugins/a11y/uno.config.ts index 37997dc5..cb712b84 100644 --- a/plugins/a11y/uno.config.ts +++ b/plugins/a11y/uno.config.ts @@ -1,43 +1,18 @@ -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' -// The a11y inspector's Solid SPA uses `@antfu/design` directly: its preset (tuned -// to devframe's sage green) over a Wind4 base, with Phosphor icons, DM Sans/Mono -// and the directive/variant-group transformers. The in-page agent bundle -// (src/inject) is deliberately excluded — it inlines its own styles into the host -// document. `.tsx` is scanned by default; `.ts` (the co-located `design.ts` -// helpers) is opted in. -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', - }, - content: { - pipeline: { - include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/], +// The a11y inspector's Solid SPA composes the shared devframe base (see +// `design/uno.config.ts`) and adds only its own extraction globs. The in-page +// agent bundle (src/inject) is deliberately excluded — it inlines its own styles +// into the host document. `.tsx` is scanned by default; `.ts` (the co-located +// `design.ts` helpers) is opted in. +export default mergeConfigs([ + designConfig, + { + content: { + pipeline: { + include: [/\.(?:[cm]?[jt]sx?|html)($|\?)/], + }, }, }, -}) +]) diff --git a/plugins/code-server/uno.config.ts b/plugins/code-server/uno.config.ts index d7985bc9..2929b5ad 100644 --- a/plugins/code-server/uno.config.ts +++ b/plugins/code-server/uno.config.ts @@ -1,44 +1,17 @@ -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' -// The code-server launcher is a Vue SPA using `@antfu/design` directly: its -// preset (tuned to devframe's sage green) over a Wind4 base, with Phosphor -// icons, DM Sans/Mono and the directive/variant-group transformers. The SPA and -// Storybook generate CSS from this config. -// -// Vue templates are scanned by default; `.ts` is opted into the extraction -// pipeline for class strings authored in composables/helpers. -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', - }, - content: { - pipeline: { - include: [/\.(?:vue|[cm]?[jt]sx?|html)($|\?)/], +// The code-server launcher composes the shared devframe base (see +// `design/uno.config.ts`) and adds only its own extraction globs. Vue templates +// are scanned by default; `.ts` is opted in for class strings authored in +// composables/helpers. The SPA and Storybook generate CSS from this config. +export default mergeConfigs([ + designConfig, + { + content: { + pipeline: { + include: [/\.(?:vue|[cm]?[jt]sx?|html)($|\?)/], + }, }, }, -}) +]) diff --git a/plugins/data-inspector/src/spa/components/AppHeader.vue b/plugins/data-inspector/src/spa/components/AppHeader.vue index a2922c0a..6f1992b2 100644 --- a/plugins/data-inspector/src/spa/components/AppHeader.vue +++ b/plugins/data-inspector/src/spa/components/AppHeader.vue @@ -1,40 +1,54 @@ diff --git a/plugins/data-inspector/uno.config.ts b/plugins/data-inspector/uno.config.ts index 713eb4b8..52a69844 100644 --- a/plugins/data-inspector/uno.config.ts +++ b/plugins/data-inspector/uno.config.ts @@ -1,42 +1,17 @@ -import { presetAnthonyDesign } from '@antfu/design/unocss' -import { - defineConfig, - presetIcons, - presetWebFonts, - presetWind4, - transformerDirectives, - transformerVariantGroup, -} from 'unocss' +import { mergeConfigs } from 'unocss' +import { designConfig } from '../../design/uno.config' -// The inspector uses `@antfu/design` directly: its preset (tuned to devframe's -// sage green) over a Wind4 base, with Phosphor icons, DM Sans/Mono and the -// directive/variant-group transformers. Vue templates are scanned by default; -// `.ts` is opted in for class strings authored in composables/helpers. The -// named `z-*` layers are the app's to own (the preset blocks plain `z-`). -export default defineConfig({ - presets: [ - presetAnthonyDesign({ primary: '#3a6a45' }), - presetWind4(), - presetIcons({ scale: 1.1 }), - presetWebFonts({ provider: 'none', fonts: { sans: 'DM Sans', mono: 'DM Mono' } }), - ], - transformers: [transformerDirectives(), transformerVariantGroup()], - // Wind4 leaves bare `border`/`border-b` at currentColor; restore the subtle - // shared border color (matching `border-base`) for unqualified borders. - preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }], - shortcuts: { - 'z-nav': 'z-[30]', - 'z-dropdown': 'z-[40]', - 'z-tooltip': 'z-[45]', - 'z-toast': 'z-[50]', - 'z-modal-backdrop': 'z-[60]', - 'z-modal-content': 'z-[70]', - 'z-drawer-backdrop': 'z-[80]', - 'z-drawer-content': 'z-[90]', - }, - content: { - pipeline: { - include: [/\.(?:vue|[cm]?[jt]sx?|html)($|\?)/], +// The data inspector composes the shared devframe base (see +// `design/uno.config.ts`) and adds only its own extraction globs. Vue templates +// are scanned by default; `.ts` is opted in for class strings authored in +// composables/helpers. +export default mergeConfigs([ + designConfig, + { + content: { + pipeline: { + include: [/\.(?:vue|[cm]?[jt]sx?|html)($|\?)/], + }, }, }, -}) +]) diff --git a/plugins/git/src/client/components/connection-state.tsx b/plugins/git/src/client/components/connection-state.tsx index aae59ca2..bf77d54b 100644 --- a/plugins/git/src/client/components/connection-state.tsx +++ b/plugins/git/src/client/components/connection-state.tsx @@ -1,63 +1,30 @@ 'use client' import type { DevframeConnectionStatus } from 'devframe/client' -import { button } from '../lib/design' +import { button, connectionBody, connectionDetail, connectionGlyph, connectionPanel, connectionState, connectionTitle } from '../lib/design' import { Icon } from './ui/icon' -interface StateCopy { - icon: string - title: string - body: string - spin?: boolean -} - -const COPY: Record, StateCopy> = { - connecting: { - icon: 'i-ph-plugs-connected-duotone', - title: 'Connecting…', - body: 'Establishing a connection to the devframe server.', - spin: true, - }, - disconnected: { - icon: 'i-ph-plugs-duotone', - title: 'Disconnected', - body: 'Lost the connection to the devframe server. Reload once it is back up.', - }, - unauthorized: { - icon: 'i-ph-lock-key-duotone', - title: 'Not authorized', - body: 'This client isn’t authorized. Reopen the link printed by your dev server, then reload.', - }, - error: { - icon: 'i-ph-warning-octagon-duotone', - title: 'Connection failed', - body: 'Could not reach the devframe server.', - }, -} - /** - * Full-panel connection state — shown whenever the client isn't `connected`, - * so the UI never sits on an infinite spinner without saying why. Reload is the - * recovery path (the client doesn't auto-reconnect). + * Full-panel connection state — shown whenever the client isn't `connected`, so + * the UI never sits on an infinite spinner without saying why. Copy and layout + * come from the shared `design/design.ts` so every surface looks identical; + * reload is the recovery path (the client doesn't auto-reconnect). */ export function ConnectionState({ status, error }: { status: DevframeConnectionStatus, error?: string | null }) { - if (status === 'connected') + const copy = connectionState(status) + if (!copy) return null - const copy = COPY[status] return ( -
- +
+
-

{copy.title}

-

{copy.body}

+

{copy.title}

+

{copy.body}

{error && status === 'error' && ( -

{error}

+

{error}

)}
- {status !== 'connecting' && ( + {copy.reloadable && (