arcway 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +711 -0
- package/client/env.js +55 -0
- package/client/fetcher.js +50 -0
- package/client/graphql.js +35 -0
- package/client/head.js +140 -0
- package/client/hooks/use-api.js +80 -0
- package/client/hooks/use-debounce.js +12 -0
- package/client/hooks/use-form.js +86 -0
- package/client/hooks/use-graphql.js +30 -0
- package/client/hooks/use-interval.js +12 -0
- package/client/hooks/use-mutation.js +27 -0
- package/client/hooks/use-query.js +45 -0
- package/client/hooks/web/use-click-outside.js +22 -0
- package/client/hooks/web/use-local-storage.js +42 -0
- package/client/index.js +62 -0
- package/client/page-loader.js +155 -0
- package/client/provider.js +53 -0
- package/client/query.js +13 -0
- package/client/router.jsx +303 -0
- package/client/ui/accordion.jsx +65 -0
- package/client/ui/accordion.stories.jsx +48 -0
- package/client/ui/alert-dialog.jsx +122 -0
- package/client/ui/alert-dialog.stories.jsx +44 -0
- package/client/ui/alert.jsx +52 -0
- package/client/ui/alert.stories.jsx +31 -0
- package/client/ui/app-shell.jsx +39 -0
- package/client/ui/app-shell.stories.jsx +51 -0
- package/client/ui/aspect-ratio.jsx +6 -0
- package/client/ui/aspect-ratio.stories.jsx +69 -0
- package/client/ui/avatar.jsx +78 -0
- package/client/ui/avatar.stories.jsx +62 -0
- package/client/ui/badge.jsx +34 -0
- package/client/ui/badge.stories.js +32 -0
- package/client/ui/breadcrumb.jsx +86 -0
- package/client/ui/breadcrumb.stories.jsx +43 -0
- package/client/ui/button-group.jsx +58 -0
- package/client/ui/button-group.stories.jsx +67 -0
- package/client/ui/button.jsx +46 -0
- package/client/ui/button.stories.js +72 -0
- package/client/ui/calendar.jsx +172 -0
- package/client/ui/card.jsx +57 -0
- package/client/ui/card.stories.jsx +33 -0
- package/client/ui/carousel.jsx +167 -0
- package/client/ui/chart.jsx +244 -0
- package/client/ui/checkbox.jsx +24 -0
- package/client/ui/checkbox.stories.js +33 -0
- package/client/ui/collapsible.jsx +12 -0
- package/client/ui/collapsible.stories.jsx +42 -0
- package/client/ui/combobox.jsx +223 -0
- package/client/ui/command.jsx +128 -0
- package/client/ui/context-menu.jsx +170 -0
- package/client/ui/context-menu.stories.jsx +35 -0
- package/client/ui/dialog.jsx +109 -0
- package/client/ui/dialog.stories.jsx +37 -0
- package/client/ui/direction.jsx +9 -0
- package/client/ui/drawer.jsx +87 -0
- package/client/ui/dropdown-menu.jsx +172 -0
- package/client/ui/dropdown-menu.stories.jsx +34 -0
- package/client/ui/empty.jsx +76 -0
- package/client/ui/empty.stories.jsx +64 -0
- package/client/ui/field.jsx +174 -0
- package/client/ui/field.stories.jsx +118 -0
- package/client/ui/form.jsx +17 -0
- package/client/ui/hooks/use-mobile.js +16 -0
- package/client/ui/hover-card.jsx +26 -0
- package/client/ui/hover-card.stories.jsx +28 -0
- package/client/ui/index.js +649 -0
- package/client/ui/input-group.jsx +116 -0
- package/client/ui/input-group.stories.jsx +65 -0
- package/client/ui/input-otp.jsx +62 -0
- package/client/ui/input.jsx +16 -0
- package/client/ui/input.stories.js +27 -0
- package/client/ui/item.jsx +155 -0
- package/client/ui/item.stories.jsx +118 -0
- package/client/ui/kbd.jsx +24 -0
- package/client/ui/kbd.stories.jsx +32 -0
- package/client/ui/label.jsx +16 -0
- package/client/ui/label.stories.js +25 -0
- package/client/ui/lib/utils.js +6 -0
- package/client/ui/main-content.jsx +30 -0
- package/client/ui/menubar.jsx +189 -0
- package/client/ui/menubar.stories.jsx +43 -0
- package/client/ui/native-select.jsx +34 -0
- package/client/ui/native-select.stories.jsx +67 -0
- package/client/ui/navigation-menu.jsx +120 -0
- package/client/ui/navigation-menu.stories.jsx +45 -0
- package/client/ui/pagination.jsx +92 -0
- package/client/ui/pagination.stories.jsx +52 -0
- package/client/ui/panel.jsx +66 -0
- package/client/ui/popover.jsx +54 -0
- package/client/ui/popover.stories.jsx +27 -0
- package/client/ui/progress.jsx +19 -0
- package/client/ui/progress.stories.js +34 -0
- package/client/ui/radio-group.jsx +33 -0
- package/client/ui/radio-group.stories.jsx +49 -0
- package/client/ui/resizable.jsx +33 -0
- package/client/ui/scroll-area.jsx +41 -0
- package/client/ui/scroll-area.stories.jsx +43 -0
- package/client/ui/select.jsx +145 -0
- package/client/ui/select.stories.jsx +80 -0
- package/client/ui/separator.jsx +18 -0
- package/client/ui/separator.stories.jsx +37 -0
- package/client/ui/sheet.jsx +95 -0
- package/client/ui/sheet.stories.jsx +56 -0
- package/client/ui/sidebar.jsx +544 -0
- package/client/ui/skeleton.jsx +8 -0
- package/client/ui/skeleton.stories.js +23 -0
- package/client/ui/slider.jsx +41 -0
- package/client/ui/slider.stories.js +31 -0
- package/client/ui/sonner.jsx +37 -0
- package/client/ui/spinner.jsx +14 -0
- package/client/ui/spinner.stories.js +16 -0
- package/client/ui/style-mira.css +1316 -0
- package/client/ui/switch.jsx +22 -0
- package/client/ui/switch.stories.js +44 -0
- package/client/ui/table.jsx +33 -0
- package/client/ui/table.stories.jsx +42 -0
- package/client/ui/tabs.jsx +63 -0
- package/client/ui/tabs.stories.jsx +45 -0
- package/client/ui/textarea.jsx +15 -0
- package/client/ui/textarea.stories.js +33 -0
- package/client/ui/theme.css +459 -0
- package/client/ui/toggle-group.jsx +62 -0
- package/client/ui/toggle-group.stories.jsx +68 -0
- package/client/ui/toggle.jsx +34 -0
- package/client/ui/toggle.stories.js +46 -0
- package/client/ui/tooltip.jsx +37 -0
- package/client/ui/tooltip.stories.jsx +32 -0
- package/client/ui/use-transition.js +35 -0
- package/client/ws.js +132 -0
- package/package.json +134 -0
- package/server/bin/cli.js +42 -0
- package/server/bin/commands/build.js +23 -0
- package/server/bin/commands/dev.js +57 -0
- package/server/bin/commands/docs.js +30 -0
- package/server/bin/commands/graphql-schema.js +32 -0
- package/server/bin/commands/lint.js +35 -0
- package/server/bin/commands/mcp.js +26 -0
- package/server/bin/commands/migrate.js +82 -0
- package/server/bin/commands/schema.js +41 -0
- package/server/bin/commands/seed.js +36 -0
- package/server/bin/commands/start.js +31 -0
- package/server/bin/commands/test.js +20 -0
- package/server/bin/solo.js +4 -0
- package/server/boot/index.js +150 -0
- package/server/boot.js +2 -0
- package/server/build.js +23 -0
- package/server/cache/drivers/memory.js +23 -0
- package/server/cache/drivers/redis.js +28 -0
- package/server/cache/index.js +69 -0
- package/server/config/loader.js +89 -0
- package/server/config/modules/api.js +17 -0
- package/server/config/modules/build.js +9 -0
- package/server/config/modules/cache.js +10 -0
- package/server/config/modules/database.js +29 -0
- package/server/config/modules/events.js +15 -0
- package/server/config/modules/files.js +15 -0
- package/server/config/modules/jobs.js +20 -0
- package/server/config/modules/logger.js +9 -0
- package/server/config/modules/mail.js +11 -0
- package/server/config/modules/mcp.js +9 -0
- package/server/config/modules/pages.js +20 -0
- package/server/config/modules/queue.js +10 -0
- package/server/config/modules/redis.js +9 -0
- package/server/config/modules/server.js +30 -0
- package/server/config/modules/session.js +9 -0
- package/server/config/modules/websocket.js +11 -0
- package/server/constants.js +67 -0
- package/server/context.js +15 -0
- package/server/db/index.js +87 -0
- package/server/db/schema/drivers/mysql.js +28 -0
- package/server/db/schema/drivers/pg.js +34 -0
- package/server/db/schema/drivers/sqlite.js +22 -0
- package/server/db/schema/index.js +78 -0
- package/server/db/seeds.js +22 -0
- package/server/discovery.js +67 -0
- package/server/docs/openapi.js +153 -0
- package/server/env.js +17 -0
- package/server/events/drivers/memory.js +45 -0
- package/server/events/drivers/redis.js +64 -0
- package/server/events/handler.js +67 -0
- package/server/events/index.js +35 -0
- package/server/events/pattern.js +5 -0
- package/server/files/drivers/local.js +83 -0
- package/server/files/drivers/s3.js +113 -0
- package/server/files/index.js +57 -0
- package/server/filewatcher/index.js +156 -0
- package/server/glob.js +6 -0
- package/server/graphql/discovery.js +70 -0
- package/server/graphql/handler.js +41 -0
- package/server/graphql/index.js +13 -0
- package/server/graphql/loaders.js +19 -0
- package/server/graphql/merge.js +48 -0
- package/server/graphql/subscriptions.js +43 -0
- package/server/health.js +34 -0
- package/server/helpers.js +9 -0
- package/server/index.js +55 -0
- package/server/internals.js +139 -0
- package/server/jobs/cron.js +10 -0
- package/server/jobs/drivers/knex-queue.js +207 -0
- package/server/jobs/drivers/lease.js +148 -0
- package/server/jobs/drivers/memory-queue.js +134 -0
- package/server/jobs/queue.js +27 -0
- package/server/jobs/runner.js +197 -0
- package/server/jobs/throughput.js +63 -0
- package/server/lib/vault/encrypt.js +40 -0
- package/server/lib/vault/ids.js +9 -0
- package/server/lib/vault/index.js +14 -0
- package/server/lib/vault/jwt.js +55 -0
- package/server/lib/vault/password.js +10 -0
- package/server/lint/boundaries.js +77 -0
- package/server/logger/index.js +130 -0
- package/server/mail/drivers/console.js +31 -0
- package/server/mail/drivers/smtp.js +34 -0
- package/server/mail/imap.js +105 -0
- package/server/mail/inbound-store.js +58 -0
- package/server/mail/inbound.js +79 -0
- package/server/mail/index.js +112 -0
- package/server/mcp/debug-api.js +137 -0
- package/server/mcp/helpers.js +30 -0
- package/server/mcp/index.js +77 -0
- package/server/mcp/runtime.js +7 -0
- package/server/mcp/server.js +19 -0
- package/server/mcp/tools/debugging.js +133 -0
- package/server/mcp/tools/introspection.js +87 -0
- package/server/middlewares/cors.js +30 -0
- package/server/middlewares/index.js +3 -0
- package/server/middlewares/require-session.js +15 -0
- package/server/module-loader.js +9 -0
- package/server/pages/build-client.js +187 -0
- package/server/pages/build-css.js +47 -0
- package/server/pages/build-manifest.js +55 -0
- package/server/pages/build-plugins.js +75 -0
- package/server/pages/build-server.js +115 -0
- package/server/pages/build.js +116 -0
- package/server/pages/discovery.js +120 -0
- package/server/pages/fonts.js +128 -0
- package/server/pages/handler.js +276 -0
- package/server/pages/hmr.js +176 -0
- package/server/pages/pages-router.js +78 -0
- package/server/pages/ssr.js +276 -0
- package/server/pages/static.js +92 -0
- package/server/pages/watcher.js +90 -0
- package/server/queue/drivers/knex.js +67 -0
- package/server/queue/drivers/redis.js +91 -0
- package/server/queue/index.js +61 -0
- package/server/rate-limit/consume.js +21 -0
- package/server/rate-limit/drivers/memory.js +24 -0
- package/server/rate-limit/drivers/redis.js +32 -0
- package/server/rate-limit/index.js +33 -0
- package/server/redis/index.js +67 -0
- package/server/ring-buffer.js +44 -0
- package/server/route.js +4 -0
- package/server/router/api-router.js +317 -0
- package/server/router/cors.js +31 -0
- package/server/router/middleware.js +91 -0
- package/server/router/routes.js +132 -0
- package/server/server.js +35 -0
- package/server/session/helpers.js +21 -0
- package/server/session/index.js +89 -0
- package/server/static/index.js +36 -0
- package/server/system-jobs/index.js +50 -0
- package/server/system-routes/index.js +84 -0
- package/server/testing/index.js +263 -0
- package/server/validation.js +41 -0
- package/server/watcher.js +34 -0
- package/server/web-server.js +231 -0
- package/server/ws/discovery.js +54 -0
- package/server/ws/index.js +14 -0
- package/server/ws/realtime.js +318 -0
- package/server/ws/registry.js +17 -0
- package/server/ws/server.js +152 -0
- package/server/ws/ws-router.js +335 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAppShell } from './app-shell.jsx';
|
|
3
|
+
function PanelHeader({ children }) {
|
|
4
|
+
return (
|
|
5
|
+
<header
|
|
6
|
+
data-panel-header
|
|
7
|
+
className="flex h-14 shrink-0 items-center border-b border-panel-border px-4"
|
|
8
|
+
>
|
|
9
|
+
{children}
|
|
10
|
+
</header>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function PanelBody({ children }) {
|
|
14
|
+
return (
|
|
15
|
+
<div data-panel-body className="flex-1 overflow-y-auto">
|
|
16
|
+
{children}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function PanelTrigger({ children }) {
|
|
21
|
+
const { togglePanel } = useAppShell();
|
|
22
|
+
return (
|
|
23
|
+
<button
|
|
24
|
+
data-panel-trigger
|
|
25
|
+
type="button"
|
|
26
|
+
onClick={togglePanel}
|
|
27
|
+
className="inline-flex items-center justify-center rounded-md p-2 text-foreground hover:bg-muted"
|
|
28
|
+
aria-label="Toggle panel"
|
|
29
|
+
>
|
|
30
|
+
{children || (
|
|
31
|
+
<svg
|
|
32
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
33
|
+
width="20"
|
|
34
|
+
height="20"
|
|
35
|
+
viewBox="0 0 24 24"
|
|
36
|
+
fill="none"
|
|
37
|
+
stroke="currentColor"
|
|
38
|
+
strokeWidth="2"
|
|
39
|
+
strokeLinecap="round"
|
|
40
|
+
strokeLinejoin="round"
|
|
41
|
+
>
|
|
42
|
+
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
43
|
+
<path d="M15 3v18" />
|
|
44
|
+
</svg>
|
|
45
|
+
)}
|
|
46
|
+
</button>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function PanelRoot({ children, width = 'w-80' }) {
|
|
50
|
+
const { panelOpen } = useAppShell();
|
|
51
|
+
if (!panelOpen) return null;
|
|
52
|
+
return (
|
|
53
|
+
<aside
|
|
54
|
+
data-panel
|
|
55
|
+
className={`@container/panel flex shrink-0 flex-col overflow-hidden border-l border-panel-border bg-panel text-panel-foreground ${width}`}
|
|
56
|
+
>
|
|
57
|
+
{children}
|
|
58
|
+
</aside>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const Panel = Object.assign(PanelRoot, {
|
|
62
|
+
Header: PanelHeader,
|
|
63
|
+
Body: PanelBody,
|
|
64
|
+
Trigger: PanelTrigger,
|
|
65
|
+
});
|
|
66
|
+
export { Panel };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Popover as PopoverPrimitive } from 'radix-ui';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
function Popover({ ...props }) {
|
|
5
|
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
|
6
|
+
}
|
|
7
|
+
function PopoverTrigger({ ...props }) {
|
|
8
|
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
function PopoverContent({ className, align = 'center', sideOffset = 4, ...props }) {
|
|
11
|
+
return (
|
|
12
|
+
<PopoverPrimitive.Portal>
|
|
13
|
+
<PopoverPrimitive.Content
|
|
14
|
+
data-slot="popover-content"
|
|
15
|
+
align={align}
|
|
16
|
+
sideOffset={sideOffset}
|
|
17
|
+
className={cn(
|
|
18
|
+
'cn-popover-content z-50 w-72 origin-(--radix-popover-content-transform-origin) outline-hidden',
|
|
19
|
+
className,
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
</PopoverPrimitive.Portal>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
function PopoverAnchor({ ...props }) {
|
|
27
|
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
|
28
|
+
}
|
|
29
|
+
function PopoverHeader({ className, ...props }) {
|
|
30
|
+
return (
|
|
31
|
+
<div data-slot="popover-header" className={cn('cn-popover-header', className)} {...props} />
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
function PopoverTitle({ className, ...props }) {
|
|
35
|
+
return <div data-slot="popover-title" className={cn('cn-popover-title', className)} {...props} />;
|
|
36
|
+
}
|
|
37
|
+
function PopoverDescription({ className, ...props }) {
|
|
38
|
+
return (
|
|
39
|
+
<p
|
|
40
|
+
data-slot="popover-description"
|
|
41
|
+
className={cn('cn-popover-description', className)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
Popover,
|
|
48
|
+
PopoverAnchor,
|
|
49
|
+
PopoverContent,
|
|
50
|
+
PopoverDescription,
|
|
51
|
+
PopoverHeader,
|
|
52
|
+
PopoverTitle,
|
|
53
|
+
PopoverTrigger,
|
|
54
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { expect, within } from 'storybook/test';
|
|
3
|
+
import { Popover, PopoverTrigger, PopoverContent } from './popover.jsx';
|
|
4
|
+
import { Button } from './button.jsx';
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'UI/Popover',
|
|
7
|
+
component: Popover,
|
|
8
|
+
};
|
|
9
|
+
var stdin_default = meta;
|
|
10
|
+
const Default = {
|
|
11
|
+
render: () => (
|
|
12
|
+
<Popover>
|
|
13
|
+
<PopoverTrigger asChild>
|
|
14
|
+
<Button>Open Popover</Button>
|
|
15
|
+
</PopoverTrigger>
|
|
16
|
+
<PopoverContent>
|
|
17
|
+
<p>Popover content goes here.</p>
|
|
18
|
+
</PopoverContent>
|
|
19
|
+
</Popover>
|
|
20
|
+
),
|
|
21
|
+
play: async ({ canvasElement }) => {
|
|
22
|
+
const canvas = within(canvasElement);
|
|
23
|
+
const trigger = canvas.getByRole('button', { name: 'Open Popover' });
|
|
24
|
+
await expect(trigger).toBeInTheDocument();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export { Default, stdin_default as default };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Progress as ProgressPrimitive } from 'radix-ui';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
function Progress({ className, value, ...props }) {
|
|
5
|
+
return (
|
|
6
|
+
<ProgressPrimitive.Root
|
|
7
|
+
data-slot="progress"
|
|
8
|
+
className={cn('cn-progress relative flex w-full items-center overflow-x-hidden', className)}
|
|
9
|
+
{...props}
|
|
10
|
+
>
|
|
11
|
+
<ProgressPrimitive.Indicator
|
|
12
|
+
data-slot="progress-indicator"
|
|
13
|
+
className="cn-progress-indicator size-full flex-1 transition-all"
|
|
14
|
+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
|
15
|
+
/>
|
|
16
|
+
</ProgressPrimitive.Root>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
export { Progress };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { expect } from 'storybook/test';
|
|
2
|
+
import { Progress } from './progress.jsx';
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'UI/Progress',
|
|
5
|
+
component: Progress,
|
|
6
|
+
argTypes: {
|
|
7
|
+
value: { control: { type: 'range', min: 0, max: 100 } },
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
var stdin_default = meta;
|
|
11
|
+
const Default = {
|
|
12
|
+
args: { value: 50 },
|
|
13
|
+
play: async ({ canvasElement }) => {
|
|
14
|
+
const el = canvasElement.querySelector('[data-slot="progress"]');
|
|
15
|
+
await expect(el).toBeInTheDocument();
|
|
16
|
+
const indicator = canvasElement.querySelector('[data-slot="progress-indicator"]');
|
|
17
|
+
await expect(indicator).toBeInTheDocument();
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
const Empty = {
|
|
21
|
+
args: { value: 0 },
|
|
22
|
+
play: async ({ canvasElement }) => {
|
|
23
|
+
const indicator = canvasElement.querySelector('[data-slot="progress-indicator"]');
|
|
24
|
+
await expect(indicator.style.transform).toBe('translateX(-100%)');
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const Full = {
|
|
28
|
+
args: { value: 100 },
|
|
29
|
+
play: async ({ canvasElement }) => {
|
|
30
|
+
const indicator = canvasElement.querySelector('[data-slot="progress-indicator"]');
|
|
31
|
+
await expect(indicator.style.transform).toBe('translateX(0%)');
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export { Default, Empty, Full, stdin_default as default };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { RadioGroup as RadioGroupPrimitive } from 'radix-ui';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
import { CircleIcon } from 'lucide-react';
|
|
5
|
+
function RadioGroup({ className, ...props }) {
|
|
6
|
+
return (
|
|
7
|
+
<RadioGroupPrimitive.Root
|
|
8
|
+
data-slot="radio-group"
|
|
9
|
+
className={cn('cn-radio-group w-full', className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
function RadioGroupItem({ className, ...props }) {
|
|
15
|
+
return (
|
|
16
|
+
<RadioGroupPrimitive.Item
|
|
17
|
+
data-slot="radio-group-item"
|
|
18
|
+
className={cn(
|
|
19
|
+
'cn-radio-group-item group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
<RadioGroupPrimitive.Indicator
|
|
25
|
+
data-slot="radio-group-indicator"
|
|
26
|
+
className="cn-radio-group-indicator"
|
|
27
|
+
>
|
|
28
|
+
<CircleIcon className="cn-radio-group-indicator-icon" />
|
|
29
|
+
</RadioGroupPrimitive.Indicator>
|
|
30
|
+
</RadioGroupPrimitive.Item>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
export { RadioGroup, RadioGroupItem };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { expect, within } from 'storybook/test';
|
|
3
|
+
import { RadioGroup, RadioGroupItem } from './radio-group.jsx';
|
|
4
|
+
import { Label } from './label.jsx';
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'UI/RadioGroup',
|
|
7
|
+
component: RadioGroup,
|
|
8
|
+
};
|
|
9
|
+
var stdin_default = meta;
|
|
10
|
+
const Default = {
|
|
11
|
+
render: (args) => (
|
|
12
|
+
<RadioGroup defaultValue="option-1" {...args}>
|
|
13
|
+
<div className="flex items-center gap-2">
|
|
14
|
+
<RadioGroupItem value="option-1" id="option-1" />
|
|
15
|
+
<Label htmlFor="option-1">Option 1</Label>
|
|
16
|
+
</div>
|
|
17
|
+
<div className="flex items-center gap-2">
|
|
18
|
+
<RadioGroupItem value="option-2" id="option-2" />
|
|
19
|
+
<Label htmlFor="option-2">Option 2</Label>
|
|
20
|
+
</div>
|
|
21
|
+
<div className="flex items-center gap-2">
|
|
22
|
+
<RadioGroupItem value="option-3" id="option-3" />
|
|
23
|
+
<Label htmlFor="option-3">Option 3</Label>
|
|
24
|
+
</div>
|
|
25
|
+
</RadioGroup>
|
|
26
|
+
),
|
|
27
|
+
play: async ({ canvasElement }) => {
|
|
28
|
+
const canvas = within(canvasElement);
|
|
29
|
+
const radios = canvas.getAllByRole('radio');
|
|
30
|
+
await expect(radios).toHaveLength(3);
|
|
31
|
+
await expect(radios[0]).toBeChecked();
|
|
32
|
+
await expect(radios[1]).not.toBeChecked();
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const Disabled = {
|
|
36
|
+
render: () => (
|
|
37
|
+
<RadioGroup disabled defaultValue="option-1">
|
|
38
|
+
<div className="flex items-center gap-2">
|
|
39
|
+
<RadioGroupItem value="option-1" id="d-1" />
|
|
40
|
+
<Label htmlFor="d-1">Disabled</Label>
|
|
41
|
+
</div>
|
|
42
|
+
</RadioGroup>
|
|
43
|
+
),
|
|
44
|
+
play: async ({ canvasElement }) => {
|
|
45
|
+
const canvas = within(canvasElement);
|
|
46
|
+
await expect(canvas.getByRole('radio')).toBeDisabled();
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
export { Default, Disabled, stdin_default as default };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import * as ResizablePrimitive from 'react-resizable-panels';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
function ResizablePanelGroup({ className, ...props }) {
|
|
5
|
+
return (
|
|
6
|
+
<ResizablePrimitive.Group
|
|
7
|
+
data-slot="resizable-panel-group"
|
|
8
|
+
className={cn(
|
|
9
|
+
'cn-resizable-panel-group flex h-full w-full aria-[orientation=vertical]:flex-col',
|
|
10
|
+
className,
|
|
11
|
+
)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
function ResizablePanel({ ...props }) {
|
|
17
|
+
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
|
|
18
|
+
}
|
|
19
|
+
function ResizableHandle({ withHandle, className, ...props }) {
|
|
20
|
+
return (
|
|
21
|
+
<ResizablePrimitive.Separator
|
|
22
|
+
data-slot="resizable-handle"
|
|
23
|
+
className={cn(
|
|
24
|
+
'cn-resizable-handle bg-border focus-visible:ring-ring ring-offset-background relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90',
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{withHandle && <div className="cn-resizable-handle-icon z-10 flex shrink-0" />}
|
|
30
|
+
</ResizablePrimitive.Separator>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollArea as ScrollAreaPrimitive } from 'radix-ui';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
function ScrollArea({ className, children, ...props }) {
|
|
5
|
+
return (
|
|
6
|
+
<ScrollAreaPrimitive.Root
|
|
7
|
+
data-slot="scroll-area"
|
|
8
|
+
className={cn('cn-scroll-area relative', className)}
|
|
9
|
+
{...props}
|
|
10
|
+
>
|
|
11
|
+
<ScrollAreaPrimitive.Viewport
|
|
12
|
+
data-slot="scroll-area-viewport"
|
|
13
|
+
className="cn-scroll-area-viewport focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</ScrollAreaPrimitive.Viewport>
|
|
17
|
+
<ScrollBar />
|
|
18
|
+
<ScrollAreaPrimitive.Corner />
|
|
19
|
+
</ScrollAreaPrimitive.Root>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function ScrollBar({ className, orientation = 'vertical', ...props }) {
|
|
23
|
+
return (
|
|
24
|
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
25
|
+
data-slot="scroll-area-scrollbar"
|
|
26
|
+
data-orientation={orientation}
|
|
27
|
+
orientation={orientation}
|
|
28
|
+
className={cn(
|
|
29
|
+
'cn-scroll-area-scrollbar flex touch-none p-px transition-colors select-none',
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
<ScrollAreaPrimitive.ScrollAreaThumb
|
|
35
|
+
data-slot="scroll-area-thumb"
|
|
36
|
+
className="cn-scroll-area-thumb bg-border relative flex-1"
|
|
37
|
+
/>
|
|
38
|
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
export { ScrollArea, ScrollBar };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { expect, within } from 'storybook/test';
|
|
3
|
+
import { ScrollArea, ScrollBar } from './scroll-area.jsx';
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'UI/ScrollArea',
|
|
6
|
+
component: ScrollArea,
|
|
7
|
+
};
|
|
8
|
+
var stdin_default = meta;
|
|
9
|
+
const Vertical = {
|
|
10
|
+
render: () => (
|
|
11
|
+
<ScrollArea style={{ height: 200, width: 300 }}>
|
|
12
|
+
<div style={{ padding: '1rem' }}>
|
|
13
|
+
{Array.from({ length: 20 }, (_, i) => (
|
|
14
|
+
<p key={i}>Scrollable item {i + 1}</p>
|
|
15
|
+
))}
|
|
16
|
+
</div>
|
|
17
|
+
</ScrollArea>
|
|
18
|
+
),
|
|
19
|
+
play: async ({ canvasElement }) => {
|
|
20
|
+
const canvas = within(canvasElement);
|
|
21
|
+
await expect(canvas.getByText('Scrollable item 1')).toBeInTheDocument();
|
|
22
|
+
await expect(canvas.getByText('Scrollable item 20')).toBeInTheDocument();
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const Horizontal = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<ScrollArea style={{ width: 300 }}>
|
|
28
|
+
<div style={{ display: 'flex', gap: '1rem', padding: '1rem', width: '800px' }}>
|
|
29
|
+
{Array.from({ length: 10 }, (_, i) => (
|
|
30
|
+
<div key={i} style={{ minWidth: 100, padding: '1rem', border: '1px solid gray' }}>
|
|
31
|
+
Item {i + 1}
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
</div>
|
|
35
|
+
<ScrollBar orientation="horizontal" />
|
|
36
|
+
</ScrollArea>
|
|
37
|
+
),
|
|
38
|
+
play: async ({ canvasElement }) => {
|
|
39
|
+
const canvas = within(canvasElement);
|
|
40
|
+
await expect(canvas.getByText('Item 1')).toBeInTheDocument();
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export { Horizontal, Vertical, stdin_default as default };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Select as SelectPrimitive } from 'radix-ui';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
|
|
5
|
+
function Select({ ...props }) {
|
|
6
|
+
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
|
7
|
+
}
|
|
8
|
+
function SelectGroup({ className, ...props }) {
|
|
9
|
+
return (
|
|
10
|
+
<SelectPrimitive.Group
|
|
11
|
+
data-slot="select-group"
|
|
12
|
+
className={cn('cn-select-group', className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
function SelectValue({ ...props }) {
|
|
18
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
|
19
|
+
}
|
|
20
|
+
function SelectTrigger({ className, size = 'default', children, ...props }) {
|
|
21
|
+
return (
|
|
22
|
+
<SelectPrimitive.Trigger
|
|
23
|
+
data-slot="select-trigger"
|
|
24
|
+
data-size={size}
|
|
25
|
+
className={cn(
|
|
26
|
+
'cn-select-trigger flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
<SelectPrimitive.Icon asChild>
|
|
33
|
+
<ChevronDownIcon className="cn-select-trigger-icon pointer-events-none" />
|
|
34
|
+
</SelectPrimitive.Icon>
|
|
35
|
+
</SelectPrimitive.Trigger>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
function SelectContent({
|
|
39
|
+
className,
|
|
40
|
+
children,
|
|
41
|
+
position = 'item-aligned',
|
|
42
|
+
align = 'center',
|
|
43
|
+
...props
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<SelectPrimitive.Portal>
|
|
47
|
+
<SelectPrimitive.Content
|
|
48
|
+
data-slot="select-content"
|
|
49
|
+
data-align-trigger={position === 'item-aligned'}
|
|
50
|
+
className={cn(
|
|
51
|
+
'cn-select-content cn-menu-target relative z-50 max-h-(--radix-select-content-available-height) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none',
|
|
52
|
+
position === 'popper' &&
|
|
53
|
+
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
position={position}
|
|
57
|
+
align={align}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
<SelectScrollUpButton />
|
|
61
|
+
<SelectPrimitive.Viewport
|
|
62
|
+
data-position={position}
|
|
63
|
+
className={cn(
|
|
64
|
+
'cn-select-viewport data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)',
|
|
65
|
+
position === 'popper' && '',
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</SelectPrimitive.Viewport>
|
|
70
|
+
<SelectScrollDownButton />
|
|
71
|
+
</SelectPrimitive.Content>
|
|
72
|
+
</SelectPrimitive.Portal>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
function SelectLabel({ className, ...props }) {
|
|
76
|
+
return (
|
|
77
|
+
<SelectPrimitive.Label
|
|
78
|
+
data-slot="select-label"
|
|
79
|
+
className={cn('cn-select-label', className)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function SelectItem({ className, children, ...props }) {
|
|
85
|
+
return (
|
|
86
|
+
<SelectPrimitive.Item
|
|
87
|
+
data-slot="select-item"
|
|
88
|
+
className={cn(
|
|
89
|
+
'cn-select-item relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
>
|
|
94
|
+
<span className="cn-select-item-indicator">
|
|
95
|
+
<SelectPrimitive.ItemIndicator>
|
|
96
|
+
<CheckIcon className="cn-select-item-indicator-icon pointer-events-none" />
|
|
97
|
+
</SelectPrimitive.ItemIndicator>
|
|
98
|
+
</span>
|
|
99
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
100
|
+
</SelectPrimitive.Item>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
function SelectSeparator({ className, ...props }) {
|
|
104
|
+
return (
|
|
105
|
+
<SelectPrimitive.Separator
|
|
106
|
+
data-slot="select-separator"
|
|
107
|
+
className={cn('cn-select-separator pointer-events-none', className)}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function SelectScrollUpButton({ className, ...props }) {
|
|
113
|
+
return (
|
|
114
|
+
<SelectPrimitive.ScrollUpButton
|
|
115
|
+
data-slot="select-scroll-up-button"
|
|
116
|
+
className={cn('cn-select-scroll-up-button', className)}
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
<ChevronUpIcon />
|
|
120
|
+
</SelectPrimitive.ScrollUpButton>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
function SelectScrollDownButton({ className, ...props }) {
|
|
124
|
+
return (
|
|
125
|
+
<SelectPrimitive.ScrollDownButton
|
|
126
|
+
data-slot="select-scroll-down-button"
|
|
127
|
+
className={cn('cn-select-scroll-down-button', className)}
|
|
128
|
+
{...props}
|
|
129
|
+
>
|
|
130
|
+
<ChevronDownIcon />
|
|
131
|
+
</SelectPrimitive.ScrollDownButton>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
Select,
|
|
136
|
+
SelectContent,
|
|
137
|
+
SelectGroup,
|
|
138
|
+
SelectItem,
|
|
139
|
+
SelectLabel,
|
|
140
|
+
SelectScrollDownButton,
|
|
141
|
+
SelectScrollUpButton,
|
|
142
|
+
SelectSeparator,
|
|
143
|
+
SelectTrigger,
|
|
144
|
+
SelectValue,
|
|
145
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { expect, within } from 'storybook/test';
|
|
3
|
+
import {
|
|
4
|
+
Select,
|
|
5
|
+
SelectContent,
|
|
6
|
+
SelectItem,
|
|
7
|
+
SelectTrigger,
|
|
8
|
+
SelectValue,
|
|
9
|
+
SelectGroup,
|
|
10
|
+
SelectLabel,
|
|
11
|
+
} from './select.jsx';
|
|
12
|
+
const meta = {
|
|
13
|
+
title: 'UI/Select',
|
|
14
|
+
component: Select,
|
|
15
|
+
};
|
|
16
|
+
var stdin_default = meta;
|
|
17
|
+
const Default = {
|
|
18
|
+
render: () => (
|
|
19
|
+
<Select>
|
|
20
|
+
<SelectTrigger className="w-[200px]">
|
|
21
|
+
<SelectValue placeholder="Pick a fruit" />
|
|
22
|
+
</SelectTrigger>
|
|
23
|
+
<SelectContent>
|
|
24
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
25
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
26
|
+
<SelectItem value="cherry">Cherry</SelectItem>
|
|
27
|
+
</SelectContent>
|
|
28
|
+
</Select>
|
|
29
|
+
),
|
|
30
|
+
play: async ({ canvasElement }) => {
|
|
31
|
+
const canvas = within(canvasElement);
|
|
32
|
+
const trigger = canvas.getByRole('combobox');
|
|
33
|
+
await expect(trigger).toBeInTheDocument();
|
|
34
|
+
await expect(trigger.dataset.slot).toBe('select-trigger');
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
const WithGroups = {
|
|
38
|
+
render: () => (
|
|
39
|
+
<Select>
|
|
40
|
+
<SelectTrigger className="w-[200px]">
|
|
41
|
+
<SelectValue placeholder="Select food" />
|
|
42
|
+
</SelectTrigger>
|
|
43
|
+
<SelectContent>
|
|
44
|
+
<SelectGroup>
|
|
45
|
+
<SelectLabel>Fruits</SelectLabel>
|
|
46
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
47
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
48
|
+
</SelectGroup>
|
|
49
|
+
<SelectGroup>
|
|
50
|
+
<SelectLabel>Vegetables</SelectLabel>
|
|
51
|
+
<SelectItem value="carrot">Carrot</SelectItem>
|
|
52
|
+
<SelectItem value="potato">Potato</SelectItem>
|
|
53
|
+
</SelectGroup>
|
|
54
|
+
</SelectContent>
|
|
55
|
+
</Select>
|
|
56
|
+
),
|
|
57
|
+
play: async ({ canvasElement }) => {
|
|
58
|
+
const canvas = within(canvasElement);
|
|
59
|
+
await expect(canvas.getByRole('combobox')).toBeInTheDocument();
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
const SmallSize = {
|
|
63
|
+
render: () => (
|
|
64
|
+
<Select>
|
|
65
|
+
<SelectTrigger size="sm" className="w-[200px]">
|
|
66
|
+
<SelectValue placeholder="Small select" />
|
|
67
|
+
</SelectTrigger>
|
|
68
|
+
<SelectContent>
|
|
69
|
+
<SelectItem value="a">Item A</SelectItem>
|
|
70
|
+
<SelectItem value="b">Item B</SelectItem>
|
|
71
|
+
</SelectContent>
|
|
72
|
+
</Select>
|
|
73
|
+
),
|
|
74
|
+
play: async ({ canvasElement }) => {
|
|
75
|
+
const canvas = within(canvasElement);
|
|
76
|
+
const trigger = canvas.getByRole('combobox');
|
|
77
|
+
await expect(trigger.dataset.size).toBe('sm');
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
export { Default, SmallSize, WithGroups, stdin_default as default };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Separator as SeparatorPrimitive } from 'radix-ui';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
function Separator({ className, orientation = 'horizontal', decorative = true, ...props }) {
|
|
5
|
+
return (
|
|
6
|
+
<SeparatorPrimitive.Root
|
|
7
|
+
data-slot="separator"
|
|
8
|
+
decorative={decorative}
|
|
9
|
+
orientation={orientation}
|
|
10
|
+
className={cn(
|
|
11
|
+
'bg-border shrink-0 data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch',
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
export { Separator };
|