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 {
|
||||
AppShell,
|
||||
AppShellMain,
|
||||
AppShellMobileToggle,
|
||||
AppShellNav,
|
||||
AppShellNavItem,
|
||||
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,
|
||||
type StatusTone,
|
||||
Badge as CommonBadge,
|
||||
Button as CommonButton,
|
||||
Input as CommonInput,
|
||||
Select as CommonSelect,
|
||||
Textarea as CommonTextarea,
|
||||
type BadgeProps as CommonBadgeProps,
|
||||
type ButtonProps as CommonButtonProps,
|
||||
type InputProps as CommonInputProps,
|
||||
type SelectProps as CommonSelectProps,
|
||||
type TextareaProps as CommonTextareaProps,
|
||||
} from '@bytelyst/ui';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export {
|
||||
AppShell,
|
||||
AppShellMain,
|
||||
AppShellMobileToggle,
|
||||
AppShellNav,
|
||||
AppShellNavItem,
|
||||
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,
|
||||
};
|
||||
type ProductButtonVariant = NonNullable<CommonButtonProps['variant']> | 'link';
|
||||
type ProductButtonSize = NonNullable<CommonButtonProps['size']> | 'icon';
|
||||
type ProductFieldVariant = 'surface' | 'muted';
|
||||
type ProductFieldSize = 'sm' | 'md';
|
||||
type ProductBadgeVariant = NonNullable<CommonBadgeProps['variant']> | 'danger';
|
||||
type ProductStatusTone = 'success' | 'warning' | 'error' | 'info' | 'neutral';
|
||||
|
||||
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 =
|
||||
| 'active'
|
||||
@ -190,18 +75,35 @@ export type ProductStatus =
|
||||
| 'synced'
|
||||
| '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',
|
||||
approved: 'success',
|
||||
blocked: 'danger',
|
||||
blocked: 'error',
|
||||
buy: 'success',
|
||||
cancelled: 'neutral',
|
||||
connected: 'success',
|
||||
danger: 'danger',
|
||||
danger: 'error',
|
||||
degraded: 'warning',
|
||||
disabled: 'neutral',
|
||||
error: 'danger',
|
||||
failed: 'danger',
|
||||
error: 'error',
|
||||
failed: 'error',
|
||||
idle: 'neutral',
|
||||
info: 'info',
|
||||
live: 'warning',
|
||||
@ -210,30 +112,116 @@ const productStatusTone: Record<ProductStatus, StatusTone> = {
|
||||
ok: 'success',
|
||||
paper: 'info',
|
||||
pending: 'warning',
|
||||
rejected: 'danger',
|
||||
sell: 'danger',
|
||||
rejected: 'error',
|
||||
sell: 'error',
|
||||
success: 'success',
|
||||
synced: 'success',
|
||||
warning: 'warning',
|
||||
};
|
||||
|
||||
export function statusToneFor(status: ProductStatus | string | null | undefined): StatusTone {
|
||||
if (!status) return 'neutral';
|
||||
function badgeVariantFor(variant?: ProductBadgeVariant): CommonBadgeProps['variant'] | undefined {
|
||||
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;
|
||||
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({
|
||||
status,
|
||||
children,
|
||||
}: {
|
||||
status: ProductStatus | string | null | undefined;
|
||||
children?: ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<StatusBadge tone={statusToneFor(status)} dot>
|
||||
<Badge variant={statusToneFor(status)} dot>
|
||||
{children ?? status ?? 'Unknown'}
|
||||
</StatusBadge>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ const monacoEditorPath = path.resolve(
|
||||
__dirname,
|
||||
'../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/
|
||||
function bytelystAlias(pkg: string): string {
|
||||
@ -34,6 +35,12 @@ export default defineConfig({
|
||||
// Vendor packages that live only in vendor/ (not in web/node_modules/)
|
||||
{ find: '@bytelyst/api-client', replacement: bytelystAlias('api-client') },
|
||||
{ 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
|
||||
// against pnpm's root store without a web/node_modules symlink when the
|
||||
// private mobile registry is unavailable. Keep local worker imports
|
||||
|
||||
Loading…
Reference in New Issue
Block a user