diff --git a/.storybook/main.ts b/.storybook/main.ts index f5bf784..98a0d2a 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -4,6 +4,7 @@ import type { StorybookConfig } from 'storybook-react-rsbuild'; const config: StorybookConfig = { stories: ['../src/**/*.mdx', '../src/**/*.stories.@(ts|tsx)'], addons: ['@storybook/addon-docs'], + staticDirs: ['../src/assets'], framework: { name: 'storybook-react-rsbuild', options: {}, @@ -13,9 +14,12 @@ const config: StorybookConfig = { reactDocgen: 'react-docgen-typescript', reactDocgenTypescriptOptions: { shouldExtractLiteralValuesFromEnum: true, - // Keep our own props; drop the noise inherited from node_modules. + // Keep our own props + Radix primitives; drop other node_modules noise. propFilter: (prop) => - prop.parent ? !/node_modules/.test(prop.parent.fileName) : true, + prop.parent + ? !/node_modules/.test(prop.parent.fileName) || + /node_modules\/radix-ui/.test(prop.parent.fileName) + : true, }, }, }; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000..6d62926 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,12 @@ + + + + diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index cc693e6..7c144f8 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -2,8 +2,9 @@ import { useEffect, type ReactNode } from 'react'; import type { Preview, Decorator } from 'storybook-react-rsbuild'; import { Tooltip } from 'radix-ui'; -/* The shipped library surface, exactly as a consumer would load it. */ -import '../src/styles/fonts.css'; +/* The shipped library surface, exactly as a consumer would load it. + Fonts are loaded via preview-head.html (Google Fonts link + Anta @font-face) + to avoid bundler inlining the @import url() mid-stylesheet. */ import '../src/styles/index.css'; /* Storybook-only canvas styling (background, docs blocks). */ import './preview.css'; diff --git a/src/components/alert-dialog/index.tsx b/src/components/alert-dialog/index.tsx new file mode 100644 index 0000000..02b06be --- /dev/null +++ b/src/components/alert-dialog/index.tsx @@ -0,0 +1,57 @@ +import { type ReactNode } from 'react'; +import { AlertDialog as RAlertDialog } from 'radix-ui'; +import { Button } from '../button'; + +export const AlertDialog = ({ + trigger, + title, + description, + cancelLabel = 'Cancel', + actionLabel = 'Confirm', + destructive, + onAction, + open, + defaultOpen, + onOpenChange, +}: { + trigger?: ReactNode; + title: string; + description?: ReactNode; + cancelLabel?: string; + actionLabel?: string; + destructive?: boolean; + onAction?: () => void; + open?: boolean; + defaultOpen?: boolean; + onOpenChange?: (o: boolean) => void; +}) => ( + + {trigger && {trigger}} + + + + + {title} + + {description && ( + + {description} + + )} +
+ + + + + + +
+
+
+
+); diff --git a/src/components/badge/index.tsx b/src/components/badge/index.tsx new file mode 100644 index 0000000..b6c9526 --- /dev/null +++ b/src/components/badge/index.tsx @@ -0,0 +1,44 @@ +import { type ComponentPropsWithoutRef, type ReactNode } from 'react'; +import { cx } from '../utils'; + +type BadgeVariant = 'lime' | 'ember' | 'neutral' | 'outline'; + +export const Badge = ({ + variant = 'neutral', + dot, + className, + children, + ...props +}: ComponentPropsWithoutRef<'span'> & { + variant?: BadgeVariant; + dot?: boolean; +}) => ( + + {children} + +); + +export const Chip = ({ + children, + onRemove, +}: { + children: ReactNode; + onRemove?: () => void; +}) => ( + + {children} + {onRemove && ( + + )} + +); diff --git a/src/components/button/index.tsx b/src/components/button/index.tsx new file mode 100644 index 0000000..9e8390c --- /dev/null +++ b/src/components/button/index.tsx @@ -0,0 +1,27 @@ +import { forwardRef, type ComponentPropsWithoutRef } from 'react'; +import { cx } from '../utils'; + +export type BtnVariant = 'key' | 'primary' | 'ember' | 'ghost'; + +type ButtonProps = ComponentPropsWithoutRef<'button'> & { + variant?: BtnVariant; + size?: 'sm'; + iconOnly?: boolean; +}; + +export const Button = forwardRef( + ({ variant = 'key', size, iconOnly, className, ...props }, ref) => ( + + + +); diff --git a/src/components/spinner/index.tsx b/src/components/spinner/index.tsx new file mode 100644 index 0000000..684fb6f --- /dev/null +++ b/src/components/spinner/index.tsx @@ -0,0 +1,45 @@ +import { useId, type ComponentPropsWithoutRef } from 'react'; +import { cx } from '../utils'; + +export const Spinner = ({ + size, + className, + ...props +}: ComponentPropsWithoutRef<'span'> & { size?: 'sm' | 'lg' }) => { + const gid = `modern-sk-groove-${useId()}`; + return ( + + + + + + + + + + + + + ); +}; diff --git a/src/components/table/index.tsx b/src/components/table/index.tsx new file mode 100644 index 0000000..f5f7f9b --- /dev/null +++ b/src/components/table/index.tsx @@ -0,0 +1,24 @@ +import { type ComponentPropsWithoutRef } from 'react'; +import { cx } from '../utils'; + +export const Table = ({ children, ...props }: ComponentPropsWithoutRef<'table'>) => ( +
+ + {children} +
+
+); + +export const THead = (p: ComponentPropsWithoutRef<'thead'>) => ; +export const TBody = (p: ComponentPropsWithoutRef<'tbody'>) => ; + +export const Tr = ({ + selected, + className, + ...props +}: ComponentPropsWithoutRef<'tr'> & { selected?: boolean }) => ( + +); + +export const Th = (p: ComponentPropsWithoutRef<'th'>) => ; +export const Td = (p: ComponentPropsWithoutRef<'td'>) => ; diff --git a/src/components/tabs/index.tsx b/src/components/tabs/index.tsx new file mode 100644 index 0000000..df63206 --- /dev/null +++ b/src/components/tabs/index.tsx @@ -0,0 +1,28 @@ +import { type ComponentPropsWithoutRef } from 'react'; +import { Tabs as RTabs } from 'radix-ui'; +import { cx } from '../utils'; + +export const Tabs = RTabs.Root; + +export const TabsList = ({ + items, + className, + ...props +}: { items: Array<{ value: string; label: string }> } & Omit< + ComponentPropsWithoutRef, + 'children' +>) => ( + + {items.map((it) => ( + + {it.label} + + ))} + +); + +export const TabsContent = RTabs.Content; diff --git a/src/components/text-field/index.tsx b/src/components/text-field/index.tsx new file mode 100644 index 0000000..17666fb --- /dev/null +++ b/src/components/text-field/index.tsx @@ -0,0 +1,26 @@ +import { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from 'react'; +import { cx } from '../utils'; + +export const TextField = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TextField.displayName = 'TextField'; + +export const TextArea = forwardRef>( + ({ className, ...props }, ref) => ( +