feat: auth & admin

This commit is contained in:
2026-06-03 10:41:53 +03:00
parent 612d0f0125
commit 7dc59fb3c4
120 changed files with 4683 additions and 2159 deletions
+258 -46
View File
@@ -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>