diff --git a/webui/src/components/primitives/index.ts b/webui/src/components/primitives/index.ts index 849c5bcc..6b63ff8a 100644 --- a/webui/src/components/primitives/index.ts +++ b/webui/src/components/primitives/index.ts @@ -1 +1 @@ -export { Show } from './show'; +export * from './primitives'; diff --git a/webui/src/components/primitives/primitives.module.css b/webui/src/components/primitives/primitives.module.css new file mode 100644 index 00000000..23914559 --- /dev/null +++ b/webui/src/components/primitives/primitives.module.css @@ -0,0 +1,45 @@ +.notice { + box-sizing: border-box; + display: block; + width: 100%; + margin: 0 0 12px; + padding: 10px 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + background: rgba(255, 255, 255, 0.04); + color: rgba(255, 255, 255, 0.82); + font-size: 12px; + font-weight: 500; + line-height: 1.4; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); +} + +.notice[data-tone='neutral'] { + border-color: rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.04); + color: rgba(255, 255, 255, 0.82); +} + +.notice[data-tone='info'] { + border-color: rgba(var(--accent-light-rgb), 0.24); + background: rgba(var(--accent-light-rgb), 0.08); + color: rgba(255, 232, 188, 0.94); +} + +.notice[data-tone='success'] { + border-color: rgba(110, 220, 150, 0.26); + background: rgba(110, 220, 150, 0.08); + color: rgba(210, 255, 228, 0.94); +} + +.notice[data-tone='warning'] { + border-color: rgba(255, 200, 100, 0.26); + background: rgba(255, 200, 100, 0.08); + color: rgba(255, 232, 188, 0.94); +} + +.notice[data-tone='danger'] { + border-color: rgba(255, 120, 120, 0.26); + background: rgba(255, 120, 120, 0.08); + color: rgba(255, 214, 214, 0.94); +} diff --git a/webui/src/components/primitives/show.test.tsx b/webui/src/components/primitives/primitives.test.tsx similarity index 59% rename from webui/src/components/primitives/show.test.tsx rename to webui/src/components/primitives/primitives.test.tsx index ec090d2c..add08aeb 100644 --- a/webui/src/components/primitives/show.test.tsx +++ b/webui/src/components/primitives/primitives.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; -import { Show } from './show'; +import { Notice, Show } from './primitives'; describe('Show', () => { it('renders children when the condition is true', () => { @@ -31,3 +31,22 @@ describe('Show', () => { expect(screen.getByText('Ada')).toBeInTheDocument(); }); }); + +describe('Notice', () => { + it('renders as a note by default', () => { + render(Fallback message); + + expect(screen.getByText('Fallback message')).toHaveAttribute('role', 'note'); + expect(screen.getByText('Fallback message')).toHaveAttribute('data-tone', 'info'); + }); + + it('supports tone overrides', () => { + render( + + Provider fallback + , + ); + + expect(screen.getByRole('note')).toHaveAttribute('data-tone', 'warning'); + }); +}); diff --git a/webui/src/components/primitives/primitives.tsx b/webui/src/components/primitives/primitives.tsx new file mode 100644 index 00000000..1fec52b9 --- /dev/null +++ b/webui/src/components/primitives/primitives.tsx @@ -0,0 +1,46 @@ +import clsx from 'clsx'; +import { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from 'react'; + +import styles from './primitives.module.css'; + +type ShowChildren = ReactNode | ((value: NonNullable) => ReactNode); + +export interface ShowProps { + children: ShowChildren; + fallback?: ReactNode; + when: T; +} + +export function Show({ fallback = null, children, when }: ShowProps) { + if (!when) { + return <>{fallback}; + } + + if (typeof children === 'function') { + return <>{(children as (value: NonNullable) => ReactNode)(when as NonNullable)}; + } + + return <>{children}; +} + +export type NoticeTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger'; + +export type NoticeProps = Omit, 'className'> & { + className?: string; + tone?: NoticeTone; +}; + +export const Notice = forwardRef(function Notice( + { className, tone = 'info', role = 'note', ...props }, + ref, +) { + return ( +
+ ); +}); diff --git a/webui/src/components/primitives/show.tsx b/webui/src/components/primitives/show.tsx deleted file mode 100644 index 2265131a..00000000 --- a/webui/src/components/primitives/show.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { ReactNode } from 'react'; - -type ShowChildren = ReactNode | ((value: NonNullable) => ReactNode); - -export function Show({ - fallback = null, - children, - when, -}: { - children: ShowChildren; - fallback?: ReactNode; - when: T; -}) { - if (!when) { - return <>{fallback}; - } - - if (typeof children === 'function') { - return <>{(children as (value: NonNullable) => ReactNode)(when as NonNullable)}; - } - - return <>{children}; -}