This commit is contained in:
17
dev/App.tsx
Normal file
17
dev/App.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import type { RouteSectionProps } from '@solidjs/router';
|
||||
import { HashRouter } from '@solidjs/router';
|
||||
import { SSModalsProvider } from 'src';
|
||||
import { routes } from './routes';
|
||||
|
||||
const AppRoot: Component<RouteSectionProps> = (props) => (
|
||||
<SSModalsProvider>{props.children}</SSModalsProvider>
|
||||
);
|
||||
|
||||
const App: Component = () => (
|
||||
<HashRouter root={AppRoot}>
|
||||
{routes}
|
||||
</HashRouter>
|
||||
);
|
||||
|
||||
export default App;
|
||||
106
dev/demo/content.tsx
Normal file
106
dev/demo/content.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { JSXElement } from 'solid-js';
|
||||
import { SSChip } from 'src';
|
||||
|
||||
export const IconSpark = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-sparkles"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M16 18a2 2 0 0 1 2 2a2 2 0 0 1 2 -2a2 2 0 0 1 -2 -2a2 2 0 0 1 -2 2a2 2 0 0 1 -2 2a2 2 0 0 1 2 -2" /><path d="M12 4a2 2 0 0 1 2 2a2 2 0 0 1 2 -2a2 2 0 0 1 -2 -2a2 2 0 0 1 -2 2a2 2 0 0 1 -2 2a2 2 0 0 1 2 -2" /><path d="M5 14a2 2 0 0 1 2 2a2 2 0 0 1 2 -2a2 2 0 0 1 -2 -2a2 2 0 0 1 -2 2a2 2 0 0 1 -2 2a2 2 0 0 1 2 -2" /></svg>
|
||||
);
|
||||
|
||||
export const IconBolt = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-bolt"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M13 3v6h6l-8 12v-6h-6l8 -12" /></svg>
|
||||
);
|
||||
|
||||
export const IconShield = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-shield"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M12 3l8 4v5c0 5 -3 9 -8 9s-8 -4 -8 -9v-5l8 -4" /></svg>
|
||||
);
|
||||
|
||||
export const IconUser = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-user"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg>
|
||||
);
|
||||
|
||||
export const IconChart = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-chart-bar"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M3 12m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v7a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M9 8m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v11a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M15 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v15a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M4 20l14 0" /></svg>
|
||||
);
|
||||
|
||||
export const IconSettings = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-settings"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" /><path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" /></svg>
|
||||
);
|
||||
|
||||
export const IconBell = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-bell"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M10 5a2 2 0 0 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6" /><path d="M9 17v1a3 3 0 0 0 6 0v-1" /></svg>
|
||||
);
|
||||
|
||||
export const IconCheck = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-check"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M5 12l5 5l10 -10" /></svg>
|
||||
);
|
||||
|
||||
export const IconLink = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M9 15l6 -6" /><path d="M11 6l-2.5 2.5a3.535 3.535 0 0 0 5 5l2.5 -2.5" /><path d="M13 18l2.5 -2.5a3.535 3.535 0 0 0 -5 -5l-2.5 2.5" /></svg>
|
||||
);
|
||||
|
||||
export const IconDots = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-dots-vertical"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M11 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M11 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M11 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg>
|
||||
);
|
||||
|
||||
export const demoRows = [
|
||||
{
|
||||
name: 'Lia Morgan',
|
||||
role: 'Design Ops',
|
||||
status: 'Active',
|
||||
updated: 'Vor 2 Tagen',
|
||||
},
|
||||
{
|
||||
name: 'Jonas Beck',
|
||||
role: 'Frontend',
|
||||
status: 'Review',
|
||||
updated: 'Heute',
|
||||
},
|
||||
{
|
||||
name: 'Ava Ruiz',
|
||||
role: 'Support',
|
||||
status: 'Active',
|
||||
updated: 'Vor 1 Stunde',
|
||||
},
|
||||
{
|
||||
name: 'Marek Ali',
|
||||
role: 'Platform',
|
||||
status: 'On hold',
|
||||
updated: 'Vor 5 Tagen',
|
||||
},
|
||||
{
|
||||
name: 'Sina Weber',
|
||||
role: 'Growth',
|
||||
status: 'Active',
|
||||
updated: 'Gestern',
|
||||
},
|
||||
];
|
||||
|
||||
export const teamOptions = [
|
||||
{ id: 'blue', label: 'Blue team' },
|
||||
{ id: 'gold', label: 'Gold team' },
|
||||
{ id: 'orchid', label: 'Orchid team' },
|
||||
];
|
||||
|
||||
export const peopleOptions = [
|
||||
{ id: 'lia', name: 'Lia Morgan', role: 'Design Ops' },
|
||||
{ id: 'jonas', name: 'Jonas Beck', role: 'Frontend' },
|
||||
{ id: 'ava', name: 'Ava Ruiz', role: 'Support' },
|
||||
{ id: 'marek', name: 'Marek Ali', role: 'Platform' },
|
||||
{ id: 'sina', name: 'Sina Weber', role: 'Growth' },
|
||||
{ id: 'tom', name: 'Tom Richter', role: 'Security' },
|
||||
];
|
||||
|
||||
export const stackSuggestions = [
|
||||
'SolidStart',
|
||||
'SolidJS',
|
||||
'TypeScript',
|
||||
'Vite',
|
||||
'Tsup',
|
||||
'Tailwind',
|
||||
'Radix',
|
||||
];
|
||||
|
||||
export const renderStatus = (status: string): JSXElement => (
|
||||
<SSChip color={status === 'Active' ? 'green' : status === 'Review' ? 'amber' : 'grey'}>
|
||||
{status}
|
||||
</SSChip>
|
||||
);
|
||||
18
dev/index.html
Normal file
18
dev/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>@sortsys/ui demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="./index.tsx" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
7
dev/index.tsx
Normal file
7
dev/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { render } from 'solid-js/web'
|
||||
import 'src/styles/default.css'
|
||||
import './styles.css'
|
||||
|
||||
import App from './App'
|
||||
|
||||
render(() => <App />, document.getElementById('root')!)
|
||||
1
dev/logo.svg
Normal file
1
dev/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 166 155.3"><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" fill="#76b3e1"/><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="27.5" y1="3" x2="152" y2="63.5"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" opacity=".3" fill="url(#a)"/><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" fill="#518ac8"/><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="95.8" y1="32.6" x2="74" y2="105.2"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" opacity=".3" fill="url(#b)"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="18.4" y1="64.2" x2="144.3" y2="149.8"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><path d="M134 80a45 45 0 00-48-15L24 85 4 120l112 19 20-36c4-7 3-15-2-23z" fill="url(#c)"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="75.2" y1="74.5" x2="24.4" y2="260.8"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path d="M114 115a45 45 0 00-48-15L4 120s53 40 94 30l3-1c17-5 23-21 13-34z" fill="url(#d)"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
39
dev/routes.ts
Normal file
39
dev/routes.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { lazy } from 'solid-js';
|
||||
import type { RouteDefinition } from '@solidjs/router';
|
||||
|
||||
export const routes: RouteDefinition[] = [
|
||||
{
|
||||
path: '/auth/login',
|
||||
component: lazy(() => import('./routes/auth/login')),
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: lazy(() => import('./routes/(shell)/_layout')),
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
component: lazy(() => import('./routes/(shell)/index')),
|
||||
},
|
||||
{
|
||||
path: '/forms',
|
||||
component: lazy(() => import('./routes/(shell)/forms')),
|
||||
},
|
||||
{
|
||||
path: '/data',
|
||||
component: lazy(() => import('./routes/(shell)/data')),
|
||||
},
|
||||
{
|
||||
path: '/modals',
|
||||
component: lazy(() => import('./routes/(shell)/modals')),
|
||||
},
|
||||
{
|
||||
path: '/layout',
|
||||
component: lazy(() => import('./routes/(shell)/layout')),
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
component: lazy(() => import('./routes/(shell)/_404')),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
17
dev/routes/(shell)/_404.tsx
Normal file
17
dev/routes/(shell)/_404.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { SSButton, SSCallout, SSHeader, SSSurface } from 'src';
|
||||
import { IconShield } from '../../demo/content';
|
||||
|
||||
const NotFoundPage: Component = () => (
|
||||
<div class="demo_page">
|
||||
<SSHeader title="Not found" subtitle="This route is not available in the demo." />
|
||||
<SSSurface class="demo_surface">
|
||||
<SSCallout color="amber" icon={<IconShield />}>
|
||||
The page you requested does not exist. Head back to the overview to continue exploring.
|
||||
</SSCallout>
|
||||
<SSButton onclick={() => (window.location.href = '#/')}>Back to overview</SSButton>
|
||||
</SSSurface>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default NotFoundPage;
|
||||
73
dev/routes/(shell)/_layout.tsx
Normal file
73
dev/routes/(shell)/_layout.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import type { RouteSectionProps } from '@solidjs/router';
|
||||
import { createSignal } from 'solid-js';
|
||||
import {
|
||||
SSButton,
|
||||
SSCallout,
|
||||
SSDropdown,
|
||||
SSShell,
|
||||
} from 'src';
|
||||
import {
|
||||
IconBell,
|
||||
IconChart,
|
||||
IconDots,
|
||||
IconLink,
|
||||
IconSettings,
|
||||
IconShield,
|
||||
IconSpark,
|
||||
IconBolt,
|
||||
IconCheck,
|
||||
} from '../../demo/content';
|
||||
|
||||
const ShellLayout: Component<RouteSectionProps> = (props) => {
|
||||
const [notice, setNotice] = createSignal(false);
|
||||
|
||||
return (
|
||||
<SSShell
|
||||
title="Sortsys UI"
|
||||
actions={
|
||||
<div class="demo_inline">
|
||||
<SSButton isIconOnly ariaLabel="Notifications">
|
||||
<IconBell />
|
||||
</SSButton>
|
||||
<SSButton class="secondary">Export</SSButton>
|
||||
<SSDropdown
|
||||
icon={<IconDots />}
|
||||
items={[
|
||||
{ label: 'Settings', icon: <IconSettings /> },
|
||||
{ label: 'Share', icon: <IconLink /> },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
nav={
|
||||
<SSShell.Nav>
|
||||
<SSShell.NavLink href="/" icon={<IconSpark />}>Overview</SSShell.NavLink>
|
||||
<SSShell.NavLink href="/forms" icon={<IconBolt />}>Forms</SSShell.NavLink>
|
||||
<SSShell.NavLink href="/data" icon={<IconChart />}>Data</SSShell.NavLink>
|
||||
<SSShell.NavLink href="/modals" icon={<IconShield />}>Modals</SSShell.NavLink>
|
||||
<SSShell.NavGroup title="Layout" icon={<IconSettings />} initiallyExpanded>
|
||||
<SSShell.NavLink href="/layout" icon={<IconLink />}>Surfaces & tiles</SSShell.NavLink>
|
||||
</SSShell.NavGroup>
|
||||
<SSShell.NavAction onclick={() => setNotice(true)} icon={<IconBell />}>
|
||||
Ping team
|
||||
</SSShell.NavAction>
|
||||
</SSShell.Nav>
|
||||
}
|
||||
>
|
||||
{notice() && (
|
||||
<div class="demo_notice">
|
||||
<SSCallout color="green" icon={<IconCheck />}>
|
||||
Team notified. The update will appear in the activity feed.
|
||||
</SSCallout>
|
||||
<SSButton class="tertiary" onclick={() => setNotice(false)}>
|
||||
Clear
|
||||
</SSButton>
|
||||
</div>
|
||||
)}
|
||||
{props.children}
|
||||
</SSShell>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShellLayout;
|
||||
43
dev/routes/(shell)/data.tsx
Normal file
43
dev/routes/(shell)/data.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { createMemo } from 'solid-js';
|
||||
import { SSDataTable, SSHeader, SSSurface } from 'src';
|
||||
import { demoRows, renderStatus } from '../../demo/content';
|
||||
|
||||
const DataPage: Component = () => {
|
||||
const columns = createMemo(() => [
|
||||
{
|
||||
label: 'Name',
|
||||
render: (row: (typeof demoRows)[number]) => row.name,
|
||||
sortKey: (row: (typeof demoRows)[number]) => row.name,
|
||||
},
|
||||
{
|
||||
label: 'Role',
|
||||
render: (row: (typeof demoRows)[number]) => row.role,
|
||||
sortKey: (row: (typeof demoRows)[number]) => row.role,
|
||||
},
|
||||
{
|
||||
label: 'Status',
|
||||
render: (row: (typeof demoRows)[number]) => renderStatus(row.status),
|
||||
sortKey: (row: (typeof demoRows)[number]) => row.status,
|
||||
},
|
||||
{
|
||||
label: 'Updated',
|
||||
render: (row: (typeof demoRows)[number]) => row.updated,
|
||||
sortKey: (row: (typeof demoRows)[number]) => row.updated,
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<div class="demo_page">
|
||||
<SSHeader title="Data table" subtitle="Sorting, paging, and quick actions." />
|
||||
<SSDataTable
|
||||
columns={columns()}
|
||||
rows={demoRows}
|
||||
pageSize={3}
|
||||
onRowClick={async () => {}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataPage;
|
||||
118
dev/routes/(shell)/forms.tsx
Normal file
118
dev/routes/(shell)/forms.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { createSignal } from 'solid-js';
|
||||
import {
|
||||
SSChip,
|
||||
SSDivider,
|
||||
SSForm,
|
||||
SSHeader,
|
||||
SSSurface,
|
||||
} from 'src';
|
||||
import { peopleOptions, stackSuggestions, teamOptions } from '../../demo/content';
|
||||
|
||||
const FormsPage: Component = () => {
|
||||
const [result, setResult] = createSignal<string | null>(null);
|
||||
|
||||
return (
|
||||
<div class="demo_page">
|
||||
<SSHeader title="Forms" subtitle="Inputs, selections, and validation in one place." />
|
||||
|
||||
<SSSurface class="demo_surface">
|
||||
<SSForm
|
||||
onsubmit={async (context) => {
|
||||
const values = await context.getValues();
|
||||
setResult(JSON.stringify(values, null, 2));
|
||||
}}
|
||||
>
|
||||
<div class="demo_form_grid">
|
||||
<SSForm.Input label="Project name" name="project" required />
|
||||
<SSForm.Input label="Email" name="email" type="email" required />
|
||||
<SSForm.Input label="Password" name="password" type="password" required />
|
||||
<SSForm.Input label="Contact phone" name="phone" type="tel" />
|
||||
</div>
|
||||
|
||||
<SSForm.Input
|
||||
label="Message"
|
||||
name="message"
|
||||
textArea
|
||||
/>
|
||||
|
||||
<SSForm.Input
|
||||
label="Tech stack"
|
||||
name="stack"
|
||||
suggestions={{
|
||||
prepare: () => stackSuggestions,
|
||||
getItems: ({ query, init }) =>
|
||||
init.filter((item) => item.toLowerCase().includes(query)).slice(0, 5),
|
||||
stringify: ({ item }) => item,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="demo_form_grid">
|
||||
<SSForm.Date label="Start date" name="startDate" editable />
|
||||
<SSForm.Date label="Target date" name="targetDate" />
|
||||
</div>
|
||||
|
||||
<div class="demo_form_grid">
|
||||
<SSForm.Select
|
||||
label="Team"
|
||||
name="team"
|
||||
getOptions={() => teamOptions}
|
||||
buildOption={(item) => item.label}
|
||||
/>
|
||||
<SSForm.Checkbox label="Notify stakeholders" name="notify" />
|
||||
</div>
|
||||
|
||||
<SSDivider />
|
||||
|
||||
<SSForm.ACSelect
|
||||
label="Invite members"
|
||||
name="members"
|
||||
minSelectedItems={1}
|
||||
getOptions={({ query }) =>
|
||||
peopleOptions.filter((item) =>
|
||||
item.name.toLowerCase().includes(query.toLowerCase())
|
||||
)
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<div class="demo_ac_item">
|
||||
<strong>{item.name}</strong>
|
||||
<span>{item.role}</span>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<SSForm.ACSelect
|
||||
label="Primary owner"
|
||||
name="owner"
|
||||
maxSelectedItems={1}
|
||||
getOptions={({ query }) =>
|
||||
peopleOptions.filter((item) =>
|
||||
item.name.toLowerCase().includes(query.toLowerCase())
|
||||
)
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<div class="demo_ac_item">
|
||||
<strong>{item.name}</strong>
|
||||
<span>{item.role}</span>
|
||||
</div>
|
||||
)}
|
||||
renderSelection={({ item }) => (
|
||||
<SSChip color="indigo">{item.name}</SSChip>
|
||||
)}
|
||||
/>
|
||||
|
||||
<SSForm.SubmitButton>Save form</SSForm.SubmitButton>
|
||||
</SSForm>
|
||||
</SSSurface>
|
||||
|
||||
{result() && (
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_section_title">Submitted values</div>
|
||||
<pre class="demo_code">{result()}</pre>
|
||||
</SSSurface>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormsPage;
|
||||
202
dev/routes/(shell)/index.tsx
Normal file
202
dev/routes/(shell)/index.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { For, createSignal } from 'solid-js';
|
||||
import {
|
||||
SSAttrList,
|
||||
SSButton,
|
||||
SSCallout,
|
||||
SSChip,
|
||||
SSDivider,
|
||||
SSDropdown,
|
||||
SSExpandable,
|
||||
SSHeader,
|
||||
SSSurface,
|
||||
SSTile,
|
||||
} from 'src';
|
||||
import {
|
||||
IconBolt,
|
||||
IconChart,
|
||||
IconDots,
|
||||
IconLink,
|
||||
IconSettings,
|
||||
IconShield,
|
||||
IconSpark,
|
||||
IconUser,
|
||||
IconBell,
|
||||
IconCheck,
|
||||
} from '../../demo/content';
|
||||
|
||||
const OverviewPage: Component = () => {
|
||||
const [bannerVisible, setBannerVisible] = createSignal(true);
|
||||
const [chipItems, setChipItems] = createSignal([
|
||||
{ id: 'launch', label: 'Launch ready', color: 'green' },
|
||||
{ id: 'urgent', label: 'Urgent', color: 'red' },
|
||||
{ id: 'info', label: 'Info', color: 'blue' },
|
||||
]);
|
||||
|
||||
return (
|
||||
<div class="demo_page">
|
||||
<SSHeader
|
||||
title="Sortsys UI"
|
||||
subtitle="A crisp SolidJS component suite for operational products."
|
||||
actions={
|
||||
<div class="demo_inline">
|
||||
<SSButton class="secondary">Preview</SSButton>
|
||||
<SSButton>Publish</SSButton>
|
||||
<SSDropdown
|
||||
ariaLabel="Aktionen"
|
||||
icon={<IconDots />}
|
||||
items={[
|
||||
{ label: 'Duplicate', icon: <IconSpark /> },
|
||||
{ label: 'Share link', icon: <IconLink />, checked: true },
|
||||
{ label: 'Archive', icon: <IconShield /> },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{bannerVisible() && (
|
||||
<div class="demo_notice">
|
||||
<SSCallout color="blue" icon={<IconSpark />}>
|
||||
A fresh demo layout that shows every component in context.
|
||||
</SSCallout>
|
||||
<SSButton class="tertiary" onclick={() => setBannerVisible(false)}>
|
||||
Dismiss
|
||||
</SSButton>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="demo_grid">
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_section_title">Status tiles</div>
|
||||
<div class="demo_tiles">
|
||||
<SSTile
|
||||
title="Design review"
|
||||
subtitle="Last updated 2 hours ago"
|
||||
icon={<IconSpark />}
|
||||
trailing="92%"
|
||||
/>
|
||||
<SSTile
|
||||
title="Security sweep"
|
||||
subtitle="Next window tomorrow"
|
||||
icon={<IconShield />}
|
||||
trailing="4 issues"
|
||||
/>
|
||||
<SSTile
|
||||
title="Capacity"
|
||||
subtitle="Quarterly planning"
|
||||
icon={<IconChart />}
|
||||
trailing="14 slots"
|
||||
/>
|
||||
</div>
|
||||
</SSSurface>
|
||||
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_section_title">Quick actions</div>
|
||||
<div class="demo_buttons">
|
||||
<SSButton>Primary action</SSButton>
|
||||
<SSButton class="secondary">Secondary</SSButton>
|
||||
<SSButton class="tertiary">Tertiary</SSButton>
|
||||
<SSButton class="danger">Danger</SSButton>
|
||||
<SSButton isIconOnly ariaLabel="Notifications">
|
||||
<IconBell />
|
||||
</SSButton>
|
||||
</div>
|
||||
|
||||
<SSDivider />
|
||||
|
||||
<div class="demo_section_title">Chips</div>
|
||||
<div class="demo_chips">
|
||||
<For each={chipItems()}>
|
||||
{(item) => (
|
||||
<SSChip
|
||||
color={item.color as any}
|
||||
ondismiss={() =>
|
||||
setChipItems((prev) => prev.filter((chip) => chip.id !== item.id))
|
||||
}
|
||||
>
|
||||
{item.label}
|
||||
</SSChip>
|
||||
)}
|
||||
</For>
|
||||
<SSChip color="purple" onclick={() => {}}>
|
||||
Clickable
|
||||
</SSChip>
|
||||
<SSChip color="amber">Display only</SSChip>
|
||||
</div>
|
||||
</SSSurface>
|
||||
</div>
|
||||
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_section_title">Attribute list</div>
|
||||
<SSAttrList>
|
||||
<SSAttrList.Attr
|
||||
name="Lifecycle"
|
||||
value="Production"
|
||||
third={
|
||||
<SSButton isIconOnly class="tertiary" ariaLabel="Edit">
|
||||
<IconSettings />
|
||||
</SSButton>
|
||||
}
|
||||
/>
|
||||
<SSAttrList.Attr
|
||||
name="Owner"
|
||||
value="Customer Ops"
|
||||
third="2 teams, 6 contributors"
|
||||
/>
|
||||
<SSAttrList.Attr name="Uptime" value="99.98%" third="Rolling 30 days" />
|
||||
<SSAttrList.Attr name="Latency" value="142ms avg" third="p95 down 12%" />
|
||||
<SSAttrList.Attr
|
||||
name="Alerts"
|
||||
value="2 open"
|
||||
third="1 critical"
|
||||
/>
|
||||
<SSAttrList.Attr
|
||||
name="Risk"
|
||||
value="Low"
|
||||
third="Review passed"
|
||||
/>
|
||||
</SSAttrList>
|
||||
</SSSurface>
|
||||
|
||||
<div class="demo_grid">
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_section_title">Callouts</div>
|
||||
<div class="demo_stack">
|
||||
<SSCallout color="green" icon={<IconCheck />}>
|
||||
All services are green. Next audit is scheduled for Friday.
|
||||
</SSCallout>
|
||||
<SSCallout color="amber" icon={<IconBolt />}>
|
||||
2 alerts are pending review in the on-call queue.
|
||||
</SSCallout>
|
||||
<SSCallout color="red" icon={<IconShield />}>
|
||||
Credentials rotation required within 14 days.
|
||||
</SSCallout>
|
||||
</div>
|
||||
</SSSurface>
|
||||
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_section_title">Expandable</div>
|
||||
<SSExpandable title="Deployment checklist" initiallyExpanded>
|
||||
<div class="demo_stack">
|
||||
<div class="demo_inline">
|
||||
<IconCheck />
|
||||
<span>Review release notes and merge outstanding fixes.</span>
|
||||
</div>
|
||||
<div class="demo_inline">
|
||||
<IconCheck />
|
||||
<span>Warm up cache nodes and verify rollout.</span>
|
||||
</div>
|
||||
<div class="demo_inline">
|
||||
<IconCheck />
|
||||
<span>Post update to #ops and notify stakeholders.</span>
|
||||
</div>
|
||||
</div>
|
||||
</SSExpandable>
|
||||
</SSSurface>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverviewPage;
|
||||
61
dev/routes/(shell)/layout.tsx
Normal file
61
dev/routes/(shell)/layout.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import {
|
||||
SSDivider,
|
||||
SSExpandable,
|
||||
SSHeader,
|
||||
SSSurface,
|
||||
SSTile,
|
||||
} from 'src';
|
||||
import { IconBolt, IconCheck, IconLink, IconShield } from '../../demo/content';
|
||||
|
||||
const LayoutPage: Component = () => (
|
||||
<div class="demo_page">
|
||||
<SSHeader title="Layout" subtitle="Surfaces, tiles, and dividers." />
|
||||
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_section_title">Surface stacks</div>
|
||||
<div class="demo_grid">
|
||||
<SSSurface class="demo_surface demo_surface--nested">
|
||||
<div class="demo_section_title">Sub-surface</div>
|
||||
<p class="demo_text">Perfect for grouped content, metrics, or compact forms.</p>
|
||||
</SSSurface>
|
||||
<SSSurface class="demo_surface demo_surface--nested">
|
||||
<div class="demo_section_title">Another surface</div>
|
||||
<p class="demo_text">Use consistent radii and subtle shadows for hierarchy.</p>
|
||||
</SSSurface>
|
||||
</div>
|
||||
</SSSurface>
|
||||
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_section_title">Tile list</div>
|
||||
<div class="demo_tiles">
|
||||
<SSTile
|
||||
title="Integrations"
|
||||
subtitle="24 connected services"
|
||||
icon={<IconLink />}
|
||||
trailing={<IconCheck />}
|
||||
/>
|
||||
<SSTile
|
||||
title="Automation"
|
||||
subtitle="5 workflows running"
|
||||
icon={<IconBolt />}
|
||||
trailing="Live"
|
||||
/>
|
||||
<SSTile
|
||||
title="Security"
|
||||
subtitle="Next scan Friday"
|
||||
icon={<IconShield />}
|
||||
trailing="Low risk"
|
||||
/>
|
||||
</div>
|
||||
<SSDivider />
|
||||
<SSExpandable title="Notes">
|
||||
<div class="demo_text">
|
||||
Combine tiles with expandables to progressively reveal details.
|
||||
</div>
|
||||
</SSExpandable>
|
||||
</SSSurface>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default LayoutPage;
|
||||
103
dev/routes/(shell)/modals.tsx
Normal file
103
dev/routes/(shell)/modals.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { createSignal } from 'solid-js';
|
||||
import {
|
||||
SSAttrList,
|
||||
SSButton,
|
||||
SSCallout,
|
||||
SSForm,
|
||||
SSHeader,
|
||||
SSModal,
|
||||
SSSurface,
|
||||
useSSModals,
|
||||
} from 'src';
|
||||
import { IconSpark } from '../../demo/content';
|
||||
|
||||
const ModalsPage: Component = () => {
|
||||
const modals = useSSModals();
|
||||
const [open, setOpen] = createSignal(false);
|
||||
|
||||
return (
|
||||
<div class="demo_page">
|
||||
<SSHeader title="Modals" subtitle="Inline and managed modal flows." />
|
||||
|
||||
<SSSurface class="demo_surface">
|
||||
<div class="demo_inline">
|
||||
<SSButton onclick={() => setOpen(true)}>Open modal</SSButton>
|
||||
<SSButton
|
||||
class="secondary"
|
||||
onclick={() =>
|
||||
modals.showDefault({
|
||||
content: () => (
|
||||
<div class="demo_stack">
|
||||
<strong>Delete pipeline?</strong>
|
||||
<span>This action will remove the pipeline and its history.</span>
|
||||
</div>
|
||||
),
|
||||
modalProps: () => ({
|
||||
title: 'Delete confirmation',
|
||||
danger: true,
|
||||
primaryButtonText: 'Delete',
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
Default modal
|
||||
</SSButton>
|
||||
<SSButton
|
||||
class="tertiary"
|
||||
onclick={() =>
|
||||
modals.showForm({
|
||||
content: ({ context }) => (
|
||||
<div class="demo_stack">
|
||||
<SSForm.Input label="Change label" name="label" required />
|
||||
<SSForm.Input label="Summary" name="summary" textArea />
|
||||
<SSForm.Checkbox label="Notify watchers" name="notify" />
|
||||
{context.hasError() && (
|
||||
<span class="demo_hint">Please fix the highlighted fields.</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
modalProps: () => ({
|
||||
title: 'Edit release',
|
||||
primaryButtonText: 'Save',
|
||||
}),
|
||||
onSubmit: async ({ hide }) => {
|
||||
hide();
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Form modal
|
||||
</SSButton>
|
||||
</div>
|
||||
</SSSurface>
|
||||
|
||||
<SSModal
|
||||
open={open()}
|
||||
title="Launch overview"
|
||||
onClose={() => setOpen(false)}
|
||||
footer={
|
||||
<>
|
||||
<SSButton class="secondary" onclick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</SSButton>
|
||||
<SSButton onclick={() => setOpen(false)}>Confirm</SSButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div class="demo_stack">
|
||||
<SSCallout color="blue" icon={<IconSpark />}>
|
||||
Everything is ready to go. Confirm to publish.
|
||||
</SSCallout>
|
||||
<SSAttrList>
|
||||
<SSAttrList.Attr name="Version" value="v1.18.0" />
|
||||
<SSAttrList.Attr name="Owner" value="Release team" />
|
||||
<SSAttrList.Attr name="Window" value="Tomorrow 09:00 CET" />
|
||||
</SSAttrList>
|
||||
</div>
|
||||
</SSModal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalsPage;
|
||||
25
dev/routes/auth/login.tsx
Normal file
25
dev/routes/auth/login.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { SSButton, SSCallout, SSForm, SSHeader, SSSurface } from 'src';
|
||||
import { IconSpark } from '../../demo/content';
|
||||
|
||||
const LoginPage: Component = () => (
|
||||
<div class="demo_page">
|
||||
<SSHeader title="Sign in" subtitle="Access the demo workspace." />
|
||||
<SSSurface class="demo_surface">
|
||||
<SSCallout color="blue" icon={<IconSpark />}>
|
||||
Use any email and password to explore the UI kit.
|
||||
</SSCallout>
|
||||
<SSForm onsubmit={() => {}}>
|
||||
<SSForm.Input label="Email" name="email" type="email" required />
|
||||
<SSForm.Input label="Password" name="password" type="password" required />
|
||||
<SSForm.Checkbox label="Remember me" name="remember" />
|
||||
<div class="demo_inline">
|
||||
<SSButton>Sign in</SSButton>
|
||||
<SSButton class="secondary">Request access</SSButton>
|
||||
</div>
|
||||
</SSForm>
|
||||
</SSSurface>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default LoginPage;
|
||||
111
dev/styles.css
Normal file
111
dev/styles.css
Normal file
@@ -0,0 +1,111 @@
|
||||
.demo_page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.demo_section_title {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fg-muted);
|
||||
}
|
||||
|
||||
.demo_grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
}
|
||||
|
||||
.demo_surface {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.demo_surface--nested {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.demo_tiles {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.demo_buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.demo_chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.demo_stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.demo_inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.demo_form_grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
}
|
||||
|
||||
.demo_ac_item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.demo_ac_item strong {
|
||||
font-size: 0.9rem;
|
||||
color: var(--fg-primary);
|
||||
}
|
||||
|
||||
.demo_ac_item span {
|
||||
font-size: 0.75rem;
|
||||
color: var(--fg-muted);
|
||||
}
|
||||
|
||||
.demo_text {
|
||||
margin: 0;
|
||||
color: var(--fg-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.demo_code {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background: var(--bg-surface-subtle);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.8rem;
|
||||
color: var(--fg-primary);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.demo_hint {
|
||||
font-size: 0.8rem;
|
||||
color: var(--fg-muted);
|
||||
}
|
||||
|
||||
.demo_notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
7
dev/tsconfig.json
Normal file
7
dev/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
37
dev/vite.config.ts
Normal file
37
dev/vite.config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import path from 'node:path'
|
||||
import solidPlugin from 'vite-plugin-solid'
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
src: path.resolve(__dirname, '../src'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
solidPlugin(),
|
||||
{
|
||||
name: 'Reaplace env variables',
|
||||
transform(code, id) {
|
||||
if (id.includes('node_modules')) {
|
||||
return code
|
||||
}
|
||||
return code
|
||||
.replace(/process\.env\.SSR/g, 'false')
|
||||
.replace(/process\.env\.DEV/g, 'true')
|
||||
.replace(/process\.env\.PROD/g, 'false')
|
||||
.replace(/process\.env\.NODE_ENV/g, '"development"')
|
||||
.replace(/import\.meta\.env\.SSR/g, 'false')
|
||||
.replace(/import\.meta\.env\.DEV/g, 'true')
|
||||
.replace(/import\.meta\.env\.PROD/g, 'false')
|
||||
.replace(/import\.meta\.env\.NODE_ENV/g, '"development"')
|
||||
},
|
||||
},
|
||||
],
|
||||
server: {
|
||||
port: 3001,
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user