feat(ui): @wing/ui 기본 컴포넌트 8개 구현
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
부모
a4859f54bc
커밋
6167a0ebd8
27
packages/ui/src/components/Badge.tsx
Normal file
27
packages/ui/src/components/Badge.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { cn } from '../utils/cn.ts';
|
||||||
|
|
||||||
|
interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
||||||
|
variant?: 'default' | 'accent' | 'danger' | 'warning' | 'success' | 'muted';
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Badge({ variant = 'default', className, children, ...props }: BadgeProps) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center rounded-[5px] border px-1.5 py-px text-[8px] font-extrabold leading-none tracking-wide',
|
||||||
|
variant === 'default' && 'border-white/8 bg-wing-muted/22 text-white',
|
||||||
|
variant === 'accent' && 'border-wing-accent/70 bg-wing-accent/22 text-white',
|
||||||
|
variant === 'danger' && 'border-wing-danger/70 bg-wing-danger/22 text-white',
|
||||||
|
variant === 'warning' && 'border-wing-warning/70 bg-wing-warning/22 text-white',
|
||||||
|
variant === 'success' && 'border-wing-success/70 bg-wing-success/22 text-white',
|
||||||
|
variant === 'muted' && 'border-wing-border/90 bg-wing-card/55 text-wing-muted font-bold',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
packages/ui/src/components/Button.tsx
Normal file
30
packages/ui/src/components/Button.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { cn } from '../utils/cn.ts';
|
||||||
|
|
||||||
|
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
variant?: 'ghost' | 'primary' | 'danger';
|
||||||
|
size?: 'sm' | 'md';
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Button({ variant = 'ghost', size = 'sm', className, children, ...props }: ButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer rounded border transition-all duration-150 select-none whitespace-nowrap',
|
||||||
|
size === 'sm' && 'px-1.5 py-0.5 text-[9px]',
|
||||||
|
size === 'md' && 'px-2.5 py-1.5 text-xs rounded-lg',
|
||||||
|
variant === 'ghost' &&
|
||||||
|
'border-wing-border bg-transparent text-wing-muted hover:text-wing-text hover:border-wing-accent',
|
||||||
|
variant === 'primary' &&
|
||||||
|
'border-wing-accent bg-wing-accent text-white hover:brightness-110',
|
||||||
|
variant === 'danger' &&
|
||||||
|
'border-wing-danger bg-transparent text-wing-danger hover:bg-wing-danger/10',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
packages/ui/src/components/IconButton.tsx
Normal file
27
packages/ui/src/components/IconButton.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { cn } from '../utils/cn.ts';
|
||||||
|
|
||||||
|
interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
active?: boolean;
|
||||||
|
size?: 'sm' | 'md';
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IconButton({ active, size = 'md', className, children, ...props }: IconButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'flex shrink-0 cursor-pointer items-center justify-center rounded border p-0 transition-all duration-150 select-none',
|
||||||
|
'border-wing-border bg-wing-glass text-wing-muted backdrop-blur-lg',
|
||||||
|
'hover:text-wing-text hover:border-wing-accent',
|
||||||
|
size === 'sm' && 'h-[22px] w-[22px] rounded text-sm',
|
||||||
|
size === 'md' && 'h-[29px] w-[29px] rounded text-base',
|
||||||
|
active && 'text-wing-accent border-wing-accent',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
26
packages/ui/src/components/ListItem.tsx
Normal file
26
packages/ui/src/components/ListItem.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { cn } from '../utils/cn.ts';
|
||||||
|
|
||||||
|
interface ListItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
selected?: boolean;
|
||||||
|
highlighted?: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListItem({ selected, highlighted, className, children, ...props }: ListItemProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex cursor-pointer items-center gap-1.5 rounded px-1.5 py-1 text-[10px] transition-colors duration-100 select-none',
|
||||||
|
'hover:bg-wing-card',
|
||||||
|
selected && 'bg-[rgba(14,234,255,0.16)] border-[rgba(14,234,255,0.55)]',
|
||||||
|
highlighted && !selected && 'bg-[rgba(245,158,11,0.16)] border border-[rgba(245,158,11,0.4)]',
|
||||||
|
selected && highlighted && 'bg-gradient-to-r from-[rgba(14,234,255,0.16)] to-[rgba(245,158,11,0.16)] border-[rgba(14,234,255,0.7)]',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
packages/ui/src/components/Panel.tsx
Normal file
24
packages/ui/src/components/Panel.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { cn } from '../utils/cn.ts';
|
||||||
|
|
||||||
|
interface PanelProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
glass?: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Panel({ glass = true, className, children, ...props }: PanelProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'rounded-lg border border-wing-border p-3',
|
||||||
|
glass
|
||||||
|
? 'bg-wing-glass backdrop-blur-lg'
|
||||||
|
: 'bg-wing-surface',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
packages/ui/src/components/Section.tsx
Normal file
29
packages/ui/src/components/Section.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { cn } from '../utils/cn.ts';
|
||||||
|
|
||||||
|
interface SectionProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
|
||||||
|
title: ReactNode;
|
||||||
|
actions?: ReactNode;
|
||||||
|
defaultOpen?: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Section({ title, actions, defaultOpen = true, className, children, ...props }: SectionProps) {
|
||||||
|
return (
|
||||||
|
<details open={defaultOpen || undefined} className={cn('border-b border-wing-border', className)} {...props}>
|
||||||
|
<summary className="flex cursor-pointer list-none items-center justify-between gap-2 px-3 py-2.5 select-none [&::-webkit-details-marker]:hidden">
|
||||||
|
<span className="text-[9px] font-bold uppercase tracking-[1.5px] text-wing-muted">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
{actions && (
|
||||||
|
<span onClick={(e) => e.preventDefault()} className="flex items-center gap-1.5">
|
||||||
|
{actions}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</summary>
|
||||||
|
<div className="px-3 pb-2.5">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
packages/ui/src/components/TextInput.tsx
Normal file
18
packages/ui/src/components/TextInput.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { InputHTMLAttributes } from 'react';
|
||||||
|
import { cn } from '../utils/cn.ts';
|
||||||
|
|
||||||
|
type TextInputProps = InputHTMLAttributes<HTMLInputElement>;
|
||||||
|
|
||||||
|
export function TextInput({ className, ...props }: TextInputProps) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className={cn(
|
||||||
|
'flex-1 rounded-md border border-wing-border bg-wing-card/75 px-2 py-1.5 text-[10px] text-wing-text outline-none',
|
||||||
|
'placeholder:text-wing-muted/90',
|
||||||
|
'focus:border-wing-accent',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
packages/ui/src/components/ToggleButton.tsx
Normal file
24
packages/ui/src/components/ToggleButton.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
||||||
|
import { cn } from '../utils/cn.ts';
|
||||||
|
|
||||||
|
interface ToggleButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
on?: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ToggleButton({ on, className, children, ...props }: ToggleButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer rounded border px-1.5 py-0.5 text-[8px] transition-all duration-150 select-none',
|
||||||
|
on
|
||||||
|
? 'border-wing-accent bg-wing-accent text-white'
|
||||||
|
: 'border-wing-border bg-wing-card text-wing-muted',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1 +1,8 @@
|
|||||||
// Components will be added in Step 2
|
export { Button } from './Button.tsx';
|
||||||
|
export { IconButton } from './IconButton.tsx';
|
||||||
|
export { ToggleButton } from './ToggleButton.tsx';
|
||||||
|
export { Badge } from './Badge.tsx';
|
||||||
|
export { Panel } from './Panel.tsx';
|
||||||
|
export { Section } from './Section.tsx';
|
||||||
|
export { ListItem } from './ListItem.tsx';
|
||||||
|
export { TextInput } from './TextInput.tsx';
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user