feat: auth & admin
This commit is contained in:
+258
-46
@@ -1,11 +1,47 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Button, IconButton, TextField, TextArea, SearchField, Select,
|
||||
Switch, Checkbox, RadioGroup, RadioItem, Control, SegmentedControl,
|
||||
Slider, Stepper, Tabs, TabsList, TabsContent, Progress, Badge, Chip,
|
||||
Card, List, Row, Menu, MenuTrigger, MenuContent, MenuItem, MenuSeparator,
|
||||
Tooltip, Spinner, Callout, Table, THead, TBody, Tr, Th, Td,
|
||||
Dialog, DialogClose, AlertDialog, Window, useTheme,
|
||||
Button,
|
||||
IconButton,
|
||||
TextField,
|
||||
TextArea,
|
||||
SearchField,
|
||||
Select,
|
||||
Switch,
|
||||
Checkbox,
|
||||
RadioGroup,
|
||||
RadioItem,
|
||||
Control,
|
||||
SegmentedControl,
|
||||
Slider,
|
||||
Stepper,
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsContent,
|
||||
Progress,
|
||||
Badge,
|
||||
Chip,
|
||||
Card,
|
||||
List,
|
||||
Row,
|
||||
Menu,
|
||||
MenuTrigger,
|
||||
MenuContent,
|
||||
MenuItem,
|
||||
MenuSeparator,
|
||||
Tooltip,
|
||||
Spinner,
|
||||
Callout,
|
||||
Table,
|
||||
THead,
|
||||
TBody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
AlertDialog,
|
||||
Window,
|
||||
useTheme,
|
||||
} from 'modern-sk';
|
||||
|
||||
const sectionStyle: React.CSSProperties = {
|
||||
@@ -29,7 +65,13 @@ const labelStyle: React.CSSProperties = {
|
||||
color: 'var(--color-text-3)',
|
||||
};
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
function Section({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Card style={{ padding: '1.25rem', ...sectionStyle }}>
|
||||
<span style={labelStyle}>{title}</span>
|
||||
@@ -52,16 +94,42 @@ export function HomePage() {
|
||||
|
||||
return (
|
||||
<div style={{ overflow: 'auto', height: '100%' }}>
|
||||
<div style={{ padding: '1.5rem', display: 'flex', flexDirection: 'column', gap: '1.25rem', maxWidth: '64rem', margin: '0 auto' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div
|
||||
style={{
|
||||
padding: '1.5rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1.25rem',
|
||||
maxWidth: '64rem',
|
||||
margin: '0 auto',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1 style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700 }}>♫ MCMA — Component Kitchen Sink</h1>
|
||||
<p style={{ margin: '0.25rem 0 0', color: 'var(--color-text-2)', fontSize: '0.875rem' }}>
|
||||
<h1 style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700 }}>
|
||||
♫ MCMA — Component Kitchen Sink
|
||||
</h1>
|
||||
<p
|
||||
style={{
|
||||
margin: '0.25rem 0 0',
|
||||
color: 'var(--color-text-2)',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
modern-sk reference. Project base ready for development.
|
||||
</p>
|
||||
</div>
|
||||
<Tooltip content={`Switch to ${theme === 'dark' ? 'light' : 'dark'}`}>
|
||||
<Button variant="ghost" onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||
>
|
||||
{theme === 'dark' ? '☾ Dark' : '☀ Light'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@@ -73,22 +141,45 @@ export function HomePage() {
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="ember">Ember</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="primary" size="sm">Small</Button>
|
||||
<Button variant="primary" disabled>Disabled</Button>
|
||||
<Button variant="primary" size="sm">
|
||||
Small
|
||||
</Button>
|
||||
<Button variant="primary" disabled>
|
||||
Disabled
|
||||
</Button>
|
||||
</div>
|
||||
<div style={rowWrap}>
|
||||
<IconButton variant="primary" aria-label="Play">▶</IconButton>
|
||||
<IconButton variant="ghost" aria-label="Next">⏭</IconButton>
|
||||
<IconButton variant="ember" size="lg" aria-label="Stop">⏹</IconButton>
|
||||
<Stepper onDecrement={() => setCount((c) => c - 1)} onIncrement={() => setCount((c) => c + 1)} />
|
||||
<span style={{ fontSize: '0.875rem', color: 'var(--color-text-2)' }}>count: {count}</span>
|
||||
<IconButton variant="primary" aria-label="Play">
|
||||
▶
|
||||
</IconButton>
|
||||
<IconButton variant="ghost" aria-label="Next">
|
||||
⏭
|
||||
</IconButton>
|
||||
<IconButton variant="ember" size="lg" aria-label="Stop">
|
||||
⏹
|
||||
</IconButton>
|
||||
<Stepper
|
||||
onDecrement={() => setCount((c) => c - 1)}
|
||||
onIncrement={() => setCount((c) => c + 1)}
|
||||
/>
|
||||
<span
|
||||
style={{ fontSize: '0.875rem', color: 'var(--color-text-2)' }}
|
||||
>
|
||||
count: {count}
|
||||
</span>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Inputs">
|
||||
<div style={rowWrap}>
|
||||
<TextField placeholder="Text field" style={{ width: '14rem' }} />
|
||||
<SearchField icon="⌕" value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search…" style={{ width: '14rem' }} />
|
||||
<SearchField
|
||||
icon="⌕"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder="Search…"
|
||||
style={{ width: '14rem' }}
|
||||
/>
|
||||
<Select
|
||||
placeholder="Pick genre"
|
||||
aria-label="Genre"
|
||||
@@ -106,10 +197,20 @@ export function HomePage() {
|
||||
|
||||
<Section title="Toggles & selection">
|
||||
<div style={rowWrap}>
|
||||
<Control control={<Switch checked={switchOn} onCheckedChange={setSwitchOn} />}>Switch</Control>
|
||||
<Control
|
||||
control={
|
||||
<Switch checked={switchOn} onCheckedChange={setSwitchOn} />
|
||||
}
|
||||
>
|
||||
Switch
|
||||
</Control>
|
||||
<Control control={<Checkbox defaultChecked />}>Checkbox</Control>
|
||||
</div>
|
||||
<RadioGroup value={radio} onValueChange={setRadio} style={{ display: 'flex', gap: '1rem' }}>
|
||||
<RadioGroup
|
||||
value={radio}
|
||||
onValueChange={setRadio}
|
||||
style={{ display: 'flex', gap: '1rem' }}
|
||||
>
|
||||
<Control control={<RadioItem value="a" />}>Option A</Control>
|
||||
<Control control={<RadioItem value="b" />}>Option B</Control>
|
||||
<Control control={<RadioItem value="c" />}>Option C</Control>
|
||||
@@ -126,15 +227,28 @@ export function HomePage() {
|
||||
</Section>
|
||||
|
||||
<Section title="Sliders & progress">
|
||||
<Slider min={0} max={100} step={1} value={vol} onValueChange={setVol} marks notches="bottom" />
|
||||
<span style={{ fontSize: '0.875rem', color: 'var(--color-text-2)' }}>value: {vol[0]}</span>
|
||||
<Slider
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={vol}
|
||||
onValueChange={setVol}
|
||||
notches="bottom"
|
||||
/>
|
||||
<span style={{ fontSize: '0.875rem', color: 'var(--color-text-2)' }}>
|
||||
value: {vol[0]}
|
||||
</span>
|
||||
<Progress value={vol[0]} />
|
||||
</Section>
|
||||
|
||||
<Section title="Badges, chips, spinner">
|
||||
<div style={rowWrap}>
|
||||
<Badge variant="lime" dot>On server</Badge>
|
||||
<Badge variant="ember" dot>Error</Badge>
|
||||
<Badge variant="lime" dot>
|
||||
On server
|
||||
</Badge>
|
||||
<Badge variant="ember" dot>
|
||||
Error
|
||||
</Badge>
|
||||
<Badge variant="neutral">Neutral</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
<Spinner size="sm" />
|
||||
@@ -142,45 +256,118 @@ export function HomePage() {
|
||||
</div>
|
||||
<div style={rowWrap}>
|
||||
{chips.map((c) => (
|
||||
<Chip key={c} onRemove={() => setChips((prev) => prev.filter((x) => x !== c))}>{c}</Chip>
|
||||
<Chip
|
||||
key={c}
|
||||
onRemove={() => setChips((prev) => prev.filter((x) => x !== c))}
|
||||
>
|
||||
{c}
|
||||
</Chip>
|
||||
))}
|
||||
{chips.length === 0 && <span style={{ fontSize: '0.875rem', color: 'var(--color-text-3)' }}>all removed</span>}
|
||||
{chips.length === 0 && (
|
||||
<span
|
||||
style={{ fontSize: '0.875rem', color: 'var(--color-text-3)' }}
|
||||
>
|
||||
all removed
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Callouts">
|
||||
<Callout variant="info">Info — backend address resolves from runtime → env → relative /api/v1.</Callout>
|
||||
<Callout variant="success">Success — typecheck and lint pass clean.</Callout>
|
||||
<Callout variant="warning">Warning — most feature screens are still stubs.</Callout>
|
||||
<Callout variant="danger">Danger — destructive actions use AlertDialog.</Callout>
|
||||
<Callout variant="info">
|
||||
Info — backend address resolves from runtime → env → relative
|
||||
/api/v1.
|
||||
</Callout>
|
||||
<Callout variant="success">
|
||||
Success — typecheck and lint pass clean.
|
||||
</Callout>
|
||||
<Callout variant="warning">
|
||||
Warning — most feature screens are still stubs.
|
||||
</Callout>
|
||||
<Callout variant="danger">
|
||||
Danger — destructive actions use AlertDialog.
|
||||
</Callout>
|
||||
</Section>
|
||||
|
||||
<Section title="Tabs">
|
||||
<Tabs value={tab} onValueChange={setTab}>
|
||||
<TabsList items={[{ value: 'one', label: 'First' }, { value: 'two', label: 'Second' }, { value: 'three', label: 'Third' }]} />
|
||||
<TabsContent value="one" style={{ padding: '0.75rem 0', color: 'var(--color-text-2)', fontSize: '0.875rem' }}>First panel</TabsContent>
|
||||
<TabsContent value="two" style={{ padding: '0.75rem 0', color: 'var(--color-text-2)', fontSize: '0.875rem' }}>Second panel</TabsContent>
|
||||
<TabsContent value="three" style={{ padding: '0.75rem 0', color: 'var(--color-text-2)', fontSize: '0.875rem' }}>Third panel</TabsContent>
|
||||
<TabsList
|
||||
items={[
|
||||
{ value: 'one', label: 'First' },
|
||||
{ value: 'two', label: 'Second' },
|
||||
{ value: 'three', label: 'Third' },
|
||||
]}
|
||||
/>
|
||||
<TabsContent
|
||||
value="one"
|
||||
style={{
|
||||
padding: '0.75rem 0',
|
||||
color: 'var(--color-text-2)',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
First panel
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="two"
|
||||
style={{
|
||||
padding: '0.75rem 0',
|
||||
color: 'var(--color-text-2)',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
Second panel
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="three"
|
||||
style={{
|
||||
padding: '0.75rem 0',
|
||||
color: 'var(--color-text-2)',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
Third panel
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</Section>
|
||||
|
||||
<Section title="List & rows">
|
||||
<List>
|
||||
<Row style={{ padding: '0.5rem 0.75rem' }}>Track one — Artist</Row>
|
||||
<Row selected style={{ padding: '0.5rem 0.75rem' }}>Track two — Artist (selected)</Row>
|
||||
<Row style={{ padding: '0.5rem 0.75rem' }}>Track three — Artist</Row>
|
||||
<Row selected style={{ padding: '0.5rem 0.75rem' }}>
|
||||
Track two — Artist (selected)
|
||||
</Row>
|
||||
<Row style={{ padding: '0.5rem 0.75rem' }}>
|
||||
Track three — Artist
|
||||
</Row>
|
||||
</List>
|
||||
</Section>
|
||||
|
||||
<Section title="Table">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr><Th>Title</Th><Th>Artist</Th><Th>Duration</Th></Tr>
|
||||
<Tr>
|
||||
<Th>Title</Th>
|
||||
<Th>Artist</Th>
|
||||
<Th>Duration</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
<Tr><Td>Intro</Td><Td>Aphex</Td><Td>2:14</Td></Tr>
|
||||
<Tr selected><Td>Windowlicker</Td><Td>Aphex</Td><Td>6:07</Td></Tr>
|
||||
<Tr><Td>Avril 14th</Td><Td>Aphex</Td><Td>2:01</Td></Tr>
|
||||
<Tr>
|
||||
<Td>Intro</Td>
|
||||
<Td>Aphex</Td>
|
||||
<Td>2:14</Td>
|
||||
</Tr>
|
||||
<Tr selected>
|
||||
<Td>Windowlicker</Td>
|
||||
<Td>Aphex</Td>
|
||||
<Td>6:07</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>Avril 14th</Td>
|
||||
<Td>Aphex</Td>
|
||||
<Td>2:01</Td>
|
||||
</Tr>
|
||||
</TBody>
|
||||
</Table>
|
||||
</Section>
|
||||
@@ -203,9 +390,21 @@ export function HomePage() {
|
||||
trigger={<Button variant="primary">Open dialog</Button>}
|
||||
title="Dialog title"
|
||||
description="Composed from modern-sk primitives."
|
||||
footer={<DialogClose asChild><Button variant="primary">Done</Button></DialogClose>}
|
||||
footer={
|
||||
<DialogClose asChild>
|
||||
<Button variant="primary">Done</Button>
|
||||
</DialogClose>
|
||||
}
|
||||
>
|
||||
<p style={{ color: 'var(--color-text-2)', fontSize: '0.875rem', margin: 0 }}>Dialog body content.</p>
|
||||
<p
|
||||
style={{
|
||||
color: 'var(--color-text-2)',
|
||||
fontSize: '0.875rem',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Dialog body content.
|
||||
</p>
|
||||
</Dialog>
|
||||
|
||||
<AlertDialog
|
||||
@@ -220,8 +419,21 @@ export function HomePage() {
|
||||
</Section>
|
||||
|
||||
<Section title="Window">
|
||||
<Window title="Now Playing" badge={<Badge variant="lime" dot>live</Badge>}>
|
||||
<p style={{ color: 'var(--color-text-2)', fontSize: '0.875rem', margin: 0 }}>
|
||||
<Window
|
||||
title="Now Playing"
|
||||
badge={
|
||||
<Badge variant="lime" dot>
|
||||
live
|
||||
</Badge>
|
||||
}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
color: 'var(--color-text-2)',
|
||||
fontSize: '0.875rem',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Window chrome for grouped content.
|
||||
</p>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user