feat(ui): wire platform core primitives
This commit is contained in:
parent
5009a22675
commit
b73c969d14
@ -12,6 +12,8 @@
|
|||||||
"smoke:compose": "bash scripts/compose-smoke.sh",
|
"smoke:compose": "bash scripts/compose-smoke.sh",
|
||||||
"seed:bootstrap": "pnpm --filter @notelett/backend run bootstrap:seed",
|
"seed:bootstrap": "pnpm --filter @notelett/backend run bootstrap:seed",
|
||||||
"audit:release-guards": "bash scripts/release-guard-audit.sh",
|
"audit:release-guards": "bash scripts/release-guard-audit.sh",
|
||||||
|
"audit:ui": "bash scripts/ui-drift-audit.sh",
|
||||||
|
"audit:ui:strict": "bash scripts/ui-drift-audit.sh --strict",
|
||||||
"dependency:health": "bash scripts/dependency-health.sh",
|
"dependency:health": "bash scripts/dependency-health.sh",
|
||||||
"verify": "pnpm run typecheck && pnpm run test && pnpm run build",
|
"verify": "pnpm run typecheck && pnpm run test && pnpm run build",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
|
|||||||
42
scripts/ui-drift-audit.sh
Normal file
42
scripts/ui-drift-audit.sh
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STRICT=0
|
||||||
|
if [[ "${1:-}" == "--strict" ]]; then
|
||||||
|
STRICT=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROOT="$(git rev-parse --show-toplevel)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
report() {
|
||||||
|
local title="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
shift 2
|
||||||
|
local matches
|
||||||
|
matches="$(rg -n "$pattern" "$@" --glob '!**/*.test.*' || true)"
|
||||||
|
|
||||||
|
echo "=== $title ==="
|
||||||
|
if [[ -z "$matches" ]]; then
|
||||||
|
echo "ok: no matches"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$matches"
|
||||||
|
echo
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
failures=0
|
||||||
|
|
||||||
|
report "Raw interactive controls" '<button|<input|<textarea|<select' web/src/app web/src/components || failures=$((failures + 1))
|
||||||
|
report "Legacy global surface classes" 'className="[^"]*(badge|surface-card|surface-muted|input-shell)' web/src/app web/src/components || failures=$((failures + 1))
|
||||||
|
report "Hardcoded color literals" '#[0-9a-fA-F]{3,8}|rgba?\(' web/src/app web/src/components || failures=$((failures + 1))
|
||||||
|
report "Direct @bytelyst/ui imports outside adapter" 'from "@bytelyst/ui"|from '\''@bytelyst/ui'\''' web/src/app web/src/components --glob '!web/src/components/ui/Primitives.tsx' || failures=$((failures + 1))
|
||||||
|
|
||||||
|
if [[ "$STRICT" == "1" && "$failures" -gt 0 ]]; then
|
||||||
|
echo "UI drift audit failed in strict mode with $failures category/categories containing matches." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "UI drift audit completed with $failures non-empty category/categories."
|
||||||
@ -2,9 +2,37 @@ import {
|
|||||||
Badge as BytelystBadge,
|
Badge as BytelystBadge,
|
||||||
Button as BytelystButton,
|
Button as BytelystButton,
|
||||||
Card as BytelystCard,
|
Card as BytelystCard,
|
||||||
|
DiffCard as BytelystDiffCard,
|
||||||
|
IconButton as BytelystIconButton,
|
||||||
|
Input as BytelystInput,
|
||||||
|
Label as BytelystLabel,
|
||||||
|
ListItemButton as BytelystListItemButton,
|
||||||
|
Panel as BytelystPanel,
|
||||||
|
PanelBody as BytelystPanelBody,
|
||||||
|
PanelDescription as BytelystPanelDescription,
|
||||||
|
PanelHeader as BytelystPanelHeader,
|
||||||
|
PanelTitle as BytelystPanelTitle,
|
||||||
|
Select as BytelystSelect,
|
||||||
|
StatusBadge as BytelystStatusBadge,
|
||||||
|
Textarea as BytelystTextarea,
|
||||||
|
Timeline as BytelystTimeline,
|
||||||
type BadgeProps,
|
type BadgeProps,
|
||||||
type ButtonProps,
|
type ButtonProps,
|
||||||
type CardProps,
|
type CardProps,
|
||||||
|
type DiffCardProps,
|
||||||
|
type IconButtonProps,
|
||||||
|
type InputProps,
|
||||||
|
type LabelProps,
|
||||||
|
type ListItemButtonProps,
|
||||||
|
type PanelBodyProps,
|
||||||
|
type PanelDescriptionProps,
|
||||||
|
type PanelHeaderProps,
|
||||||
|
type PanelProps,
|
||||||
|
type PanelTitleProps,
|
||||||
|
type SelectProps,
|
||||||
|
type StatusBadgeProps,
|
||||||
|
type TextareaProps,
|
||||||
|
type TimelineProps,
|
||||||
} from "@bytelyst/ui";
|
} from "@bytelyst/ui";
|
||||||
|
|
||||||
function mergeClassNames(...classes: Array<string | undefined>) {
|
function mergeClassNames(...classes: Array<string | undefined>) {
|
||||||
@ -46,3 +74,112 @@ export function Card({ className, ...props }: CardProps) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Panel({ className, ...props }: PanelProps) {
|
||||||
|
return (
|
||||||
|
<BytelystPanel
|
||||||
|
className={mergeClassNames(
|
||||||
|
"rounded-[var(--nl-radius-md)] border-[var(--nl-border-default)] bg-[var(--nl-surface-card-translucent)] shadow-[var(--nl-elevation-md)]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PanelHeader({ className, ...props }: PanelHeaderProps) {
|
||||||
|
return <BytelystPanelHeader className={mergeClassNames("gap-[var(--nl-space-3)]", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PanelBody({ className, ...props }: PanelBodyProps) {
|
||||||
|
return <BytelystPanelBody className={mergeClassNames("gap-[var(--nl-space-3)]", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PanelTitle({ className, ...props }: PanelTitleProps) {
|
||||||
|
return <BytelystPanelTitle className={mergeClassNames("text-[var(--nl-text-primary)]", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PanelDescription({ className, ...props }: PanelDescriptionProps) {
|
||||||
|
return <BytelystPanelDescription className={mergeClassNames("text-[var(--nl-text-secondary)]", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IconButton({ className, ...props }: IconButtonProps) {
|
||||||
|
return (
|
||||||
|
<BytelystIconButton
|
||||||
|
className={mergeClassNames(
|
||||||
|
"rounded-[var(--nl-radius-sm)] focus-visible:ring-[var(--nl-accent-primary)]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListItemButton({ className, ...props }: ListItemButtonProps) {
|
||||||
|
return (
|
||||||
|
<BytelystListItemButton
|
||||||
|
className={mergeClassNames(
|
||||||
|
"rounded-[var(--nl-radius-sm)] border-[var(--nl-border-default)] bg-[var(--nl-surface-muted-translucent)] text-[var(--nl-text-primary)]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatusBadge({ className, ...props }: StatusBadgeProps) {
|
||||||
|
return (
|
||||||
|
<BytelystStatusBadge
|
||||||
|
className={mergeClassNames("border-[var(--nl-border-default)] text-[var(--nl-text-primary)]", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Input({ className, ...props }: InputProps) {
|
||||||
|
return (
|
||||||
|
<BytelystInput
|
||||||
|
className={mergeClassNames(
|
||||||
|
"rounded-[var(--nl-radius-sm)] border-[var(--nl-border-default)] bg-[var(--nl-input-bg)] text-[var(--nl-text-primary)] focus:ring-[var(--nl-accent-primary)]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Textarea({ className, ...props }: TextareaProps) {
|
||||||
|
return (
|
||||||
|
<BytelystTextarea
|
||||||
|
className={mergeClassNames(
|
||||||
|
"rounded-[var(--nl-radius-sm)] border-[var(--nl-border-default)] bg-[var(--nl-input-bg)] text-[var(--nl-text-primary)] focus:ring-[var(--nl-accent-primary)]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Select({ className, ...props }: SelectProps) {
|
||||||
|
return (
|
||||||
|
<BytelystSelect
|
||||||
|
className={mergeClassNames(
|
||||||
|
"rounded-[var(--nl-radius-sm)] border-[var(--nl-border-default)] bg-[var(--nl-input-bg)] text-[var(--nl-text-primary)] focus:ring-[var(--nl-accent-primary)]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Label({ className, ...props }: LabelProps) {
|
||||||
|
return <BytelystLabel className={mergeClassNames("text-[var(--nl-text-secondary)]", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Timeline({ className, ...props }: TimelineProps) {
|
||||||
|
return <BytelystTimeline className={mergeClassNames("gap-[var(--nl-space-3)]", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DiffCard({ className, ...props }: DiffCardProps) {
|
||||||
|
return <BytelystDiffCard className={mergeClassNames("gap-[var(--nl-space-3)]", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user