refactor(ui): reconcile common ui adapter
This commit is contained in:
parent
799bbf36af
commit
35fbe873e4
@ -1,168 +1,53 @@
|
|||||||
import type { ReactNode } from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
AppShell,
|
Badge as CommonBadge,
|
||||||
AppShellMain,
|
Button as CommonButton,
|
||||||
AppShellMobileToggle,
|
Input as CommonInput,
|
||||||
AppShellNav,
|
Select as CommonSelect,
|
||||||
AppShellNavItem,
|
Textarea as CommonTextarea,
|
||||||
AppShellOverlay,
|
type BadgeProps as CommonBadgeProps,
|
||||||
AppShellPageHeader,
|
type ButtonProps as CommonButtonProps,
|
||||||
AppShellSidebar,
|
type InputProps as CommonInputProps,
|
||||||
AppShellSkipLink,
|
type SelectProps as CommonSelectProps,
|
||||||
Badge,
|
type TextareaProps as CommonTextareaProps,
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
Checkbox,
|
|
||||||
ConfirmDialog,
|
|
||||||
DataList,
|
|
||||||
DataListItem,
|
|
||||||
DataListMeta,
|
|
||||||
DataTable,
|
|
||||||
DataTableBody,
|
|
||||||
DataTableCell,
|
|
||||||
DataTableHead,
|
|
||||||
DataTableHeader,
|
|
||||||
DataTableRow,
|
|
||||||
DiffCard,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuSub,
|
|
||||||
DropdownMenuSubTrigger,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
EmptyState,
|
|
||||||
IconButton,
|
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
ListItemButton,
|
|
||||||
LoadingSpinner,
|
|
||||||
Modal,
|
|
||||||
Panel,
|
|
||||||
PanelBody,
|
|
||||||
PanelDescription,
|
|
||||||
PanelHeader,
|
|
||||||
PanelTitle,
|
|
||||||
RadioGroup,
|
|
||||||
RadioGroupItem,
|
|
||||||
SegmentedControl,
|
|
||||||
Select,
|
|
||||||
Separator,
|
|
||||||
Sidebar,
|
|
||||||
SidebarItem,
|
|
||||||
StatCard,
|
|
||||||
StatusBadge,
|
|
||||||
StatusDot,
|
|
||||||
Surface,
|
|
||||||
SurfaceList,
|
|
||||||
SurfaceListItem,
|
|
||||||
Switch,
|
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
Textarea,
|
|
||||||
Timeline,
|
|
||||||
Toast,
|
|
||||||
ToastProvider,
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
dismissToast,
|
|
||||||
toast,
|
|
||||||
useToast,
|
|
||||||
type StatusTone,
|
|
||||||
} from '@bytelyst/ui';
|
} from '@bytelyst/ui';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
export {
|
type ProductButtonVariant = NonNullable<CommonButtonProps['variant']> | 'link';
|
||||||
AppShell,
|
type ProductButtonSize = NonNullable<CommonButtonProps['size']> | 'icon';
|
||||||
AppShellMain,
|
type ProductFieldVariant = 'surface' | 'muted';
|
||||||
AppShellMobileToggle,
|
type ProductFieldSize = 'sm' | 'md';
|
||||||
AppShellNav,
|
type ProductBadgeVariant = NonNullable<CommonBadgeProps['variant']> | 'danger';
|
||||||
AppShellNavItem,
|
type ProductStatusTone = 'success' | 'warning' | 'error' | 'info' | 'neutral';
|
||||||
AppShellOverlay,
|
|
||||||
AppShellPageHeader,
|
|
||||||
AppShellSidebar,
|
|
||||||
AppShellSkipLink,
|
|
||||||
Badge,
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
Checkbox,
|
|
||||||
ConfirmDialog,
|
|
||||||
DataList,
|
|
||||||
DataListItem,
|
|
||||||
DataListMeta,
|
|
||||||
DataTable,
|
|
||||||
DataTableBody,
|
|
||||||
DataTableCell,
|
|
||||||
DataTableHead,
|
|
||||||
DataTableHeader,
|
|
||||||
DataTableRow,
|
|
||||||
DiffCard,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuSub,
|
|
||||||
DropdownMenuSubTrigger,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
EmptyState,
|
|
||||||
IconButton,
|
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
ListItemButton,
|
|
||||||
LoadingSpinner,
|
|
||||||
Modal,
|
|
||||||
Panel,
|
|
||||||
PanelBody,
|
|
||||||
PanelDescription,
|
|
||||||
PanelHeader,
|
|
||||||
PanelTitle,
|
|
||||||
RadioGroup,
|
|
||||||
RadioGroupItem,
|
|
||||||
SegmentedControl,
|
|
||||||
Select,
|
|
||||||
Separator,
|
|
||||||
Sidebar,
|
|
||||||
SidebarItem,
|
|
||||||
StatCard,
|
|
||||||
StatusBadge,
|
|
||||||
StatusDot,
|
|
||||||
Surface,
|
|
||||||
SurfaceList,
|
|
||||||
SurfaceListItem,
|
|
||||||
Switch,
|
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
Textarea,
|
|
||||||
Timeline,
|
|
||||||
Toast,
|
|
||||||
ToastProvider,
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
dismissToast,
|
|
||||||
toast,
|
|
||||||
useToast,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type { StatusTone };
|
export interface ButtonProps extends Omit<CommonButtonProps, 'variant' | 'size'> {
|
||||||
|
variant?: ProductButtonVariant;
|
||||||
|
size?: ProductButtonSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IconButtonProps extends Omit<ButtonProps, 'children'> {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InputProps extends CommonInputProps {
|
||||||
|
controlSize?: ProductFieldSize;
|
||||||
|
variant?: ProductFieldVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectProps extends CommonSelectProps {
|
||||||
|
controlSize?: ProductFieldSize;
|
||||||
|
variant?: ProductFieldVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextareaProps extends CommonTextareaProps {
|
||||||
|
controlSize?: ProductFieldSize;
|
||||||
|
variant?: ProductFieldVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BadgeProps extends Omit<CommonBadgeProps, 'variant'> {
|
||||||
|
variant?: ProductBadgeVariant;
|
||||||
|
}
|
||||||
|
|
||||||
export type ProductStatus =
|
export type ProductStatus =
|
||||||
| 'active'
|
| 'active'
|
||||||
@ -190,18 +75,35 @@ export type ProductStatus =
|
|||||||
| 'synced'
|
| 'synced'
|
||||||
| 'warning';
|
| 'warning';
|
||||||
|
|
||||||
const productStatusTone: Record<ProductStatus, StatusTone> = {
|
const buttonSizeClass: Record<ProductButtonSize, string> = {
|
||||||
|
sm: '',
|
||||||
|
md: '',
|
||||||
|
lg: '',
|
||||||
|
icon: 'h-10 w-10 px-0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldSizeClass: Record<ProductFieldSize, string> = {
|
||||||
|
sm: 'min-h-8 px-2 py-1 text-xs',
|
||||||
|
md: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldVariantClass: Record<ProductFieldVariant, string> = {
|
||||||
|
surface: 'bg-[var(--card)] border-[var(--border)] text-[var(--foreground)]',
|
||||||
|
muted: 'bg-[var(--muted)] border-[var(--border)] text-[var(--foreground)]',
|
||||||
|
};
|
||||||
|
|
||||||
|
const productStatusTone: Record<ProductStatus, ProductStatusTone> = {
|
||||||
active: 'success',
|
active: 'success',
|
||||||
approved: 'success',
|
approved: 'success',
|
||||||
blocked: 'danger',
|
blocked: 'error',
|
||||||
buy: 'success',
|
buy: 'success',
|
||||||
cancelled: 'neutral',
|
cancelled: 'neutral',
|
||||||
connected: 'success',
|
connected: 'success',
|
||||||
danger: 'danger',
|
danger: 'error',
|
||||||
degraded: 'warning',
|
degraded: 'warning',
|
||||||
disabled: 'neutral',
|
disabled: 'neutral',
|
||||||
error: 'danger',
|
error: 'error',
|
||||||
failed: 'danger',
|
failed: 'error',
|
||||||
idle: 'neutral',
|
idle: 'neutral',
|
||||||
info: 'info',
|
info: 'info',
|
||||||
live: 'warning',
|
live: 'warning',
|
||||||
@ -210,30 +112,116 @@ const productStatusTone: Record<ProductStatus, StatusTone> = {
|
|||||||
ok: 'success',
|
ok: 'success',
|
||||||
paper: 'info',
|
paper: 'info',
|
||||||
pending: 'warning',
|
pending: 'warning',
|
||||||
rejected: 'danger',
|
rejected: 'error',
|
||||||
sell: 'danger',
|
sell: 'error',
|
||||||
success: 'success',
|
success: 'success',
|
||||||
synced: 'success',
|
synced: 'success',
|
||||||
warning: 'warning',
|
warning: 'warning',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function statusToneFor(status: ProductStatus | string | null | undefined): StatusTone {
|
function badgeVariantFor(variant?: ProductBadgeVariant): CommonBadgeProps['variant'] | undefined {
|
||||||
if (!status) return 'neutral';
|
if (variant === 'danger') return 'error';
|
||||||
|
return variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonVariantFor(variant?: ProductButtonVariant): CommonButtonProps['variant'] | undefined {
|
||||||
|
if (variant === 'link') return 'ghost';
|
||||||
|
return variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function statusToneFor(status: ProductStatus | string | null | undefined): ProductStatusTone {
|
||||||
|
if (!status) return 'neutral';
|
||||||
const normalized = status.trim().toLowerCase().replace(/[\s_]+/g, '-') as ProductStatus;
|
const normalized = status.trim().toLowerCase().replace(/[\s_]+/g, '-') as ProductStatus;
|
||||||
return productStatusTone[normalized] ?? 'neutral';
|
return productStatusTone[normalized] ?? 'neutral';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ variant = 'primary', size = 'md', className, ...props }, ref) => (
|
||||||
|
<CommonButton
|
||||||
|
ref={ref}
|
||||||
|
variant={buttonVariantFor(variant)}
|
||||||
|
size={size === 'icon' ? 'sm' : size}
|
||||||
|
className={cn(
|
||||||
|
buttonSizeClass[size],
|
||||||
|
variant === 'link' && 'h-auto px-0 py-0 underline underline-offset-4 hover:bg-transparent',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
|
export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||||
|
({ icon, label, variant = 'ghost', size = 'icon', className, ...props }, ref) => (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
type="button"
|
||||||
|
aria-label={label}
|
||||||
|
title={props.title ?? label}
|
||||||
|
variant={variant}
|
||||||
|
size={size}
|
||||||
|
className={cn('shrink-0', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
IconButton.displayName = 'IconButton';
|
||||||
|
|
||||||
|
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
|
({ controlSize = 'md', variant = 'surface', className, ...props }, ref) => (
|
||||||
|
<CommonInput
|
||||||
|
ref={ref}
|
||||||
|
className={cn(fieldSizeClass[controlSize], fieldVariantClass[variant], className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Input.displayName = 'Input';
|
||||||
|
|
||||||
|
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
||||||
|
({ controlSize = 'md', variant = 'surface', className, ...props }, ref) => (
|
||||||
|
<CommonSelect
|
||||||
|
ref={ref}
|
||||||
|
className={cn(fieldSizeClass[controlSize], fieldVariantClass[variant], className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Select.displayName = 'Select';
|
||||||
|
|
||||||
|
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
|
({ controlSize = 'md', variant = 'surface', className, ...props }, ref) => (
|
||||||
|
<CommonTextarea
|
||||||
|
ref={ref}
|
||||||
|
className={cn(fieldSizeClass[controlSize], fieldVariantClass[variant], className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Textarea.displayName = 'Textarea';
|
||||||
|
|
||||||
|
export function Badge({ variant = 'neutral', ...props }: BadgeProps) {
|
||||||
|
return <CommonBadge variant={badgeVariantFor(variant)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
export function ProductStatusBadge({
|
export function ProductStatusBadge({
|
||||||
status,
|
status,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
status: ProductStatus | string | null | undefined;
|
status: ProductStatus | string | null | undefined;
|
||||||
children?: ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<StatusBadge tone={statusToneFor(status)} dot>
|
<Badge variant={statusToneFor(status)} dot>
|
||||||
{children ?? status ?? 'Unknown'}
|
{children ?? status ?? 'Unknown'}
|
||||||
</StatusBadge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ const monacoEditorPath = path.resolve(
|
|||||||
__dirname,
|
__dirname,
|
||||||
'../node_modules/.pnpm/monaco-editor@0.55.1/node_modules/monaco-editor',
|
'../node_modules/.pnpm/monaco-editor@0.55.1/node_modules/monaco-editor',
|
||||||
);
|
);
|
||||||
|
const commonUiSourcePath = '/opt/bytelyst/learning_ai_common_plat/packages/ui/src/index.ts';
|
||||||
|
|
||||||
// Resolve a @bytelyst/* package: prefer web/node_modules, fall back to vendor/
|
// Resolve a @bytelyst/* package: prefer web/node_modules, fall back to vendor/
|
||||||
function bytelystAlias(pkg: string): string {
|
function bytelystAlias(pkg: string): string {
|
||||||
@ -34,6 +35,12 @@ export default defineConfig({
|
|||||||
// Vendor packages that live only in vendor/ (not in web/node_modules/)
|
// Vendor packages that live only in vendor/ (not in web/node_modules/)
|
||||||
{ find: '@bytelyst/api-client', replacement: bytelystAlias('api-client') },
|
{ find: '@bytelyst/api-client', replacement: bytelystAlias('api-client') },
|
||||||
{ find: '@bytelyst/errors', replacement: bytelystAlias('errors') },
|
{ find: '@bytelyst/errors', replacement: bytelystAlias('errors') },
|
||||||
|
{
|
||||||
|
find: '@bytelyst/ui',
|
||||||
|
replacement: fs.existsSync(commonUiSourcePath)
|
||||||
|
? commonUiSourcePath
|
||||||
|
: path.resolve(__dirname, 'node_modules/@bytelyst/ui'),
|
||||||
|
},
|
||||||
// Monaco is an explicit web dependency, but this workspace often runs
|
// Monaco is an explicit web dependency, but this workspace often runs
|
||||||
// against pnpm's root store without a web/node_modules symlink when the
|
// against pnpm's root store without a web/node_modules symlink when the
|
||||||
// private mobile registry is unavailable. Keep local worker imports
|
// private mobile registry is unavailable. Keep local worker imports
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user