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,116 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cva } from 'class-variance-authority';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
import { Button } from './button.jsx';
|
|
5
|
+
import { Input } from './input.jsx';
|
|
6
|
+
import { Textarea } from './textarea.jsx';
|
|
7
|
+
function InputGroup({ className, ...props }) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="input-group"
|
|
11
|
+
role="group"
|
|
12
|
+
className={cn(
|
|
13
|
+
'group/input-group cn-input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto',
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const inputGroupAddonVariants = cva(
|
|
21
|
+
'cn-input-group-addon flex cursor-text items-center justify-center select-none',
|
|
22
|
+
{
|
|
23
|
+
variants: {
|
|
24
|
+
align: {
|
|
25
|
+
'inline-start': 'cn-input-group-addon-align-inline-start order-first',
|
|
26
|
+
'inline-end': 'cn-input-group-addon-align-inline-end order-last',
|
|
27
|
+
'block-start': 'cn-input-group-addon-align-block-start order-first w-full justify-start',
|
|
28
|
+
'block-end': 'cn-input-group-addon-align-block-end order-last w-full justify-start',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
align: 'inline-start',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
function InputGroupAddon({ className, align = 'inline-start', ...props }) {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
role="group"
|
|
40
|
+
data-slot="input-group-addon"
|
|
41
|
+
data-align={align}
|
|
42
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
43
|
+
onClick={(e) => {
|
|
44
|
+
if (e.target.closest('button')) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
e.currentTarget.parentElement?.querySelector('input')?.focus();
|
|
48
|
+
}}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const inputGroupButtonVariants = cva('cn-input-group-button shadow-none flex items-center', {
|
|
54
|
+
variants: {
|
|
55
|
+
size: {
|
|
56
|
+
xs: 'cn-input-group-button-size-xs',
|
|
57
|
+
sm: 'cn-input-group-button-size-sm',
|
|
58
|
+
'icon-xs': 'cn-input-group-button-size-icon-xs',
|
|
59
|
+
'icon-sm': 'cn-input-group-button-size-icon-sm',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
defaultVariants: {
|
|
63
|
+
size: 'xs',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
function InputGroupButton({
|
|
67
|
+
className,
|
|
68
|
+
type = 'button',
|
|
69
|
+
variant = 'ghost',
|
|
70
|
+
size = 'xs',
|
|
71
|
+
...props
|
|
72
|
+
}) {
|
|
73
|
+
return (
|
|
74
|
+
<Button
|
|
75
|
+
type={type}
|
|
76
|
+
data-size={size}
|
|
77
|
+
variant={variant}
|
|
78
|
+
className={cn(inputGroupButtonVariants({ size }), className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function InputGroupText({ className, ...props }) {
|
|
84
|
+
return (
|
|
85
|
+
<span
|
|
86
|
+
className={cn('cn-input-group-text flex items-center [&_svg]:pointer-events-none', className)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
function InputGroupInput({ className, ...props }) {
|
|
92
|
+
return (
|
|
93
|
+
<Input
|
|
94
|
+
data-slot="input-group-control"
|
|
95
|
+
className={cn('cn-input-group-input flex-1', className)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
function InputGroupTextarea({ className, ...props }) {
|
|
101
|
+
return (
|
|
102
|
+
<Textarea
|
|
103
|
+
data-slot="input-group-control"
|
|
104
|
+
className={cn('cn-input-group-textarea flex-1 resize-none', className)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
InputGroup,
|
|
111
|
+
InputGroupAddon,
|
|
112
|
+
InputGroupButton,
|
|
113
|
+
InputGroupInput,
|
|
114
|
+
InputGroupText,
|
|
115
|
+
InputGroupTextarea,
|
|
116
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { expect, within } from 'storybook/test';
|
|
3
|
+
import { SearchIcon, MailIcon } from 'lucide-react';
|
|
4
|
+
import {
|
|
5
|
+
InputGroup,
|
|
6
|
+
InputGroupAddon,
|
|
7
|
+
InputGroupButton,
|
|
8
|
+
InputGroupText,
|
|
9
|
+
InputGroupInput,
|
|
10
|
+
} from './input-group.jsx';
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'UI/InputGroup',
|
|
13
|
+
component: InputGroup,
|
|
14
|
+
};
|
|
15
|
+
var stdin_default = meta;
|
|
16
|
+
const WithAddon = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<InputGroup>
|
|
19
|
+
<InputGroupAddon align="inline-start">
|
|
20
|
+
<InputGroupText>
|
|
21
|
+
<SearchIcon className="size-4" />
|
|
22
|
+
</InputGroupText>
|
|
23
|
+
</InputGroupAddon>
|
|
24
|
+
<InputGroupInput placeholder="Search..." />
|
|
25
|
+
</InputGroup>
|
|
26
|
+
),
|
|
27
|
+
play: async ({ canvasElement }) => {
|
|
28
|
+
const canvas = within(canvasElement);
|
|
29
|
+
const group = canvasElement.querySelector('[data-slot="input-group"]');
|
|
30
|
+
await expect(group).toBeInTheDocument();
|
|
31
|
+
await expect(canvas.getByPlaceholderText('Search...')).toBeInTheDocument();
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const WithButton = {
|
|
35
|
+
render: () => (
|
|
36
|
+
<InputGroup>
|
|
37
|
+
<InputGroupInput placeholder="Email" />
|
|
38
|
+
<InputGroupAddon align="inline-end">
|
|
39
|
+
<InputGroupButton>
|
|
40
|
+
<MailIcon className="size-4" />
|
|
41
|
+
</InputGroupButton>
|
|
42
|
+
</InputGroupAddon>
|
|
43
|
+
</InputGroup>
|
|
44
|
+
),
|
|
45
|
+
play: async ({ canvasElement }) => {
|
|
46
|
+
const canvas = within(canvasElement);
|
|
47
|
+
await expect(canvas.getByRole('button')).toBeInTheDocument();
|
|
48
|
+
await expect(canvas.getByPlaceholderText('Email')).toBeInTheDocument();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const WithTextAddon = {
|
|
52
|
+
render: () => (
|
|
53
|
+
<InputGroup>
|
|
54
|
+
<InputGroupAddon align="inline-start">
|
|
55
|
+
<InputGroupText>https://</InputGroupText>
|
|
56
|
+
</InputGroupAddon>
|
|
57
|
+
<InputGroupInput placeholder="example.com" />
|
|
58
|
+
</InputGroup>
|
|
59
|
+
),
|
|
60
|
+
play: async ({ canvasElement }) => {
|
|
61
|
+
const canvas = within(canvasElement);
|
|
62
|
+
await expect(canvas.getByText('https://')).toBeInTheDocument();
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
export { WithAddon, WithButton, WithTextAddon, stdin_default as default };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { OTPInput, OTPInputContext } from 'input-otp';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
import { MinusIcon } from 'lucide-react';
|
|
5
|
+
function InputOTP({ className, containerClassName, ...props }) {
|
|
6
|
+
return (
|
|
7
|
+
<OTPInput
|
|
8
|
+
data-slot="input-otp"
|
|
9
|
+
containerClassName={cn(
|
|
10
|
+
'cn-input-otp flex items-center has-disabled:opacity-50',
|
|
11
|
+
containerClassName,
|
|
12
|
+
)}
|
|
13
|
+
spellCheck={false}
|
|
14
|
+
className={cn('cn-input-otp-input disabled:cursor-not-allowed', className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function InputOTPGroup({ className, ...props }) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
data-slot="input-otp-group"
|
|
23
|
+
className={cn('cn-input-otp-group flex items-center', className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function InputOTPSlot({ index, className, ...props }) {
|
|
29
|
+
const inputOTPContext = React.useContext(OTPInputContext);
|
|
30
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
data-slot="input-otp-slot"
|
|
34
|
+
data-active={isActive}
|
|
35
|
+
className={cn(
|
|
36
|
+
'cn-input-otp-slot relative flex items-center justify-center data-[active=true]:z-10',
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{char}
|
|
42
|
+
{hasFakeCaret && (
|
|
43
|
+
<div className="cn-input-otp-caret pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
44
|
+
<div className="cn-input-otp-caret-line" />
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function InputOTPSeparator({ ...props }) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-slot="input-otp-separator"
|
|
54
|
+
className="cn-input-otp-separator flex items-center"
|
|
55
|
+
role="separator"
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
<MinusIcon />
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cn } from './lib/utils.js';
|
|
3
|
+
function Input({ className, type, ...props }) {
|
|
4
|
+
return (
|
|
5
|
+
<input
|
|
6
|
+
type={type}
|
|
7
|
+
data-slot="input"
|
|
8
|
+
className={cn(
|
|
9
|
+
'cn-input file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
10
|
+
className,
|
|
11
|
+
)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
export { Input };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { expect, within } from 'storybook/test';
|
|
2
|
+
import { Input } from './input.jsx';
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'UI/Input',
|
|
5
|
+
component: Input,
|
|
6
|
+
};
|
|
7
|
+
var stdin_default = meta;
|
|
8
|
+
const Default = {
|
|
9
|
+
args: { placeholder: 'Enter text...' },
|
|
10
|
+
play: async ({ canvasElement }) => {
|
|
11
|
+
const canvas = within(canvasElement);
|
|
12
|
+
const input = canvas.getByPlaceholderText('Enter text...');
|
|
13
|
+
await expect(input).toBeInTheDocument();
|
|
14
|
+
await expect(input.dataset.slot).toBe('input');
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const WithType = {
|
|
18
|
+
args: { type: 'email', placeholder: 'Email address' },
|
|
19
|
+
};
|
|
20
|
+
const Disabled = {
|
|
21
|
+
args: { placeholder: 'Disabled', disabled: true },
|
|
22
|
+
play: async ({ canvasElement }) => {
|
|
23
|
+
const canvas = within(canvasElement);
|
|
24
|
+
await expect(canvas.getByPlaceholderText('Disabled')).toBeDisabled();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export { Default, Disabled, WithType, stdin_default as default };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cva } from 'class-variance-authority';
|
|
3
|
+
import { Slot } from 'radix-ui';
|
|
4
|
+
import { cn } from './lib/utils.js';
|
|
5
|
+
import { Separator } from './separator.jsx';
|
|
6
|
+
function ItemGroup({ className, ...props }) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
role="list"
|
|
10
|
+
data-slot="item-group"
|
|
11
|
+
className={cn('cn-item-group group/item-group flex w-full flex-col', className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
function ItemSeparator({ className, ...props }) {
|
|
17
|
+
return (
|
|
18
|
+
<Separator
|
|
19
|
+
data-slot="item-separator"
|
|
20
|
+
orientation="horizontal"
|
|
21
|
+
className={cn('cn-item-separator', className)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const itemVariants = cva(
|
|
27
|
+
'cn-item w-full group/item focus-visible:border-ring focus-visible:ring-ring/50 flex items-center flex-wrap outline-none transition-colors duration-100 focus-visible:ring-[3px] [a]:transition-colors',
|
|
28
|
+
{
|
|
29
|
+
variants: {
|
|
30
|
+
variant: {
|
|
31
|
+
default: 'cn-item-variant-default',
|
|
32
|
+
outline: 'cn-item-variant-outline',
|
|
33
|
+
muted: 'cn-item-variant-muted',
|
|
34
|
+
},
|
|
35
|
+
size: {
|
|
36
|
+
default: 'cn-item-size-default',
|
|
37
|
+
sm: 'cn-item-size-sm',
|
|
38
|
+
xs: 'cn-item-size-xs',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
defaultVariants: {
|
|
42
|
+
variant: 'default',
|
|
43
|
+
size: 'default',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
function Item({ className, variant = 'default', size = 'default', asChild = false, ...props }) {
|
|
48
|
+
const Comp = asChild ? Slot.Root : 'div';
|
|
49
|
+
return (
|
|
50
|
+
<Comp
|
|
51
|
+
data-slot="item"
|
|
52
|
+
data-variant={variant}
|
|
53
|
+
data-size={size}
|
|
54
|
+
className={cn(itemVariants({ variant, size, className }))}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const itemMediaVariants = cva(
|
|
60
|
+
'cn-item-media flex shrink-0 items-center justify-center [&_svg]:pointer-events-none',
|
|
61
|
+
{
|
|
62
|
+
variants: {
|
|
63
|
+
variant: {
|
|
64
|
+
default: 'cn-item-media-variant-default',
|
|
65
|
+
icon: 'cn-item-media-variant-icon',
|
|
66
|
+
image: 'cn-item-media-variant-image',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
defaultVariants: {
|
|
70
|
+
variant: 'default',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
function ItemMedia({ className, variant = 'default', ...props }) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="item-media"
|
|
78
|
+
data-variant={variant}
|
|
79
|
+
className={cn(itemMediaVariants({ variant, className }))}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function ItemContent({ className, ...props }) {
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
data-slot="item-content"
|
|
88
|
+
className={cn(
|
|
89
|
+
'cn-item-content flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none',
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function ItemTitle({ className, ...props }) {
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
data-slot="item-title"
|
|
100
|
+
className={cn('cn-item-title line-clamp-1 flex w-fit items-center', className)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
function ItemDescription({ className, ...props }) {
|
|
106
|
+
return (
|
|
107
|
+
<p
|
|
108
|
+
data-slot="item-description"
|
|
109
|
+
className={cn(
|
|
110
|
+
'cn-item-description [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4',
|
|
111
|
+
className,
|
|
112
|
+
)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function ItemActions({ className, ...props }) {
|
|
118
|
+
return (
|
|
119
|
+
<div
|
|
120
|
+
data-slot="item-actions"
|
|
121
|
+
className={cn('cn-item-actions flex items-center', className)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
function ItemHeader({ className, ...props }) {
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
data-slot="item-header"
|
|
130
|
+
className={cn('cn-item-header flex basis-full items-center justify-between', className)}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
function ItemFooter({ className, ...props }) {
|
|
136
|
+
return (
|
|
137
|
+
<div
|
|
138
|
+
data-slot="item-footer"
|
|
139
|
+
className={cn('cn-item-footer flex basis-full items-center justify-between', className)}
|
|
140
|
+
{...props}
|
|
141
|
+
/>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
Item,
|
|
146
|
+
ItemActions,
|
|
147
|
+
ItemContent,
|
|
148
|
+
ItemDescription,
|
|
149
|
+
ItemFooter,
|
|
150
|
+
ItemGroup,
|
|
151
|
+
ItemHeader,
|
|
152
|
+
ItemMedia,
|
|
153
|
+
ItemSeparator,
|
|
154
|
+
ItemTitle,
|
|
155
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { expect, within } from 'storybook/test';
|
|
3
|
+
import { UserIcon } from 'lucide-react';
|
|
4
|
+
import {
|
|
5
|
+
Item,
|
|
6
|
+
ItemMedia,
|
|
7
|
+
ItemContent,
|
|
8
|
+
ItemTitle,
|
|
9
|
+
ItemDescription,
|
|
10
|
+
ItemActions,
|
|
11
|
+
ItemGroup,
|
|
12
|
+
ItemSeparator,
|
|
13
|
+
} from './item.jsx';
|
|
14
|
+
import { Button } from './button.jsx';
|
|
15
|
+
const meta = {
|
|
16
|
+
title: 'UI/Item',
|
|
17
|
+
component: Item,
|
|
18
|
+
};
|
|
19
|
+
var stdin_default = meta;
|
|
20
|
+
const Default = {
|
|
21
|
+
render: () => (
|
|
22
|
+
<Item>
|
|
23
|
+
<ItemMedia variant="icon">
|
|
24
|
+
<UserIcon className="size-4" />
|
|
25
|
+
</ItemMedia>
|
|
26
|
+
<ItemContent>
|
|
27
|
+
<ItemTitle>John Doe</ItemTitle>
|
|
28
|
+
<ItemDescription>Software Engineer</ItemDescription>
|
|
29
|
+
</ItemContent>
|
|
30
|
+
</Item>
|
|
31
|
+
),
|
|
32
|
+
play: async ({ canvasElement }) => {
|
|
33
|
+
const canvas = within(canvasElement);
|
|
34
|
+
const item = canvasElement.querySelector('[data-slot="item"]');
|
|
35
|
+
await expect(item).toBeInTheDocument();
|
|
36
|
+
await expect(canvas.getByText('John Doe')).toBeInTheDocument();
|
|
37
|
+
await expect(canvas.getByText('Software Engineer')).toBeInTheDocument();
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const WithActions = {
|
|
41
|
+
render: () => (
|
|
42
|
+
<Item>
|
|
43
|
+
<ItemContent>
|
|
44
|
+
<ItemTitle>Task Item</ItemTitle>
|
|
45
|
+
<ItemDescription>Complete this task</ItemDescription>
|
|
46
|
+
</ItemContent>
|
|
47
|
+
<ItemActions>
|
|
48
|
+
<Button size="sm" variant="outline">
|
|
49
|
+
Edit
|
|
50
|
+
</Button>
|
|
51
|
+
</ItemActions>
|
|
52
|
+
</Item>
|
|
53
|
+
),
|
|
54
|
+
play: async ({ canvasElement }) => {
|
|
55
|
+
const canvas = within(canvasElement);
|
|
56
|
+
await expect(canvas.getByRole('button', { name: 'Edit' })).toBeInTheDocument();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const GroupWithSeparators = {
|
|
60
|
+
render: () => (
|
|
61
|
+
<ItemGroup>
|
|
62
|
+
<Item>
|
|
63
|
+
<ItemContent>
|
|
64
|
+
<ItemTitle>Item 1</ItemTitle>
|
|
65
|
+
</ItemContent>
|
|
66
|
+
</Item>
|
|
67
|
+
<ItemSeparator />
|
|
68
|
+
<Item>
|
|
69
|
+
<ItemContent>
|
|
70
|
+
<ItemTitle>Item 2</ItemTitle>
|
|
71
|
+
</ItemContent>
|
|
72
|
+
</Item>
|
|
73
|
+
<ItemSeparator />
|
|
74
|
+
<Item>
|
|
75
|
+
<ItemContent>
|
|
76
|
+
<ItemTitle>Item 3</ItemTitle>
|
|
77
|
+
</ItemContent>
|
|
78
|
+
</Item>
|
|
79
|
+
</ItemGroup>
|
|
80
|
+
),
|
|
81
|
+
play: async ({ canvasElement }) => {
|
|
82
|
+
const canvas = within(canvasElement);
|
|
83
|
+
const list = canvas.getByRole('list');
|
|
84
|
+
await expect(list).toBeInTheDocument();
|
|
85
|
+
await expect(canvas.getByText('Item 1')).toBeInTheDocument();
|
|
86
|
+
await expect(canvas.getByText('Item 2')).toBeInTheDocument();
|
|
87
|
+
await expect(canvas.getByText('Item 3')).toBeInTheDocument();
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
const Variants = {
|
|
91
|
+
render: () => (
|
|
92
|
+
<ItemGroup>
|
|
93
|
+
<Item variant="default">
|
|
94
|
+
<ItemContent>
|
|
95
|
+
<ItemTitle>Default</ItemTitle>
|
|
96
|
+
</ItemContent>
|
|
97
|
+
</Item>
|
|
98
|
+
<Item variant="outline">
|
|
99
|
+
<ItemContent>
|
|
100
|
+
<ItemTitle>Outline</ItemTitle>
|
|
101
|
+
</ItemContent>
|
|
102
|
+
</Item>
|
|
103
|
+
<Item variant="muted">
|
|
104
|
+
<ItemContent>
|
|
105
|
+
<ItemTitle>Muted</ItemTitle>
|
|
106
|
+
</ItemContent>
|
|
107
|
+
</Item>
|
|
108
|
+
</ItemGroup>
|
|
109
|
+
),
|
|
110
|
+
play: async ({ canvasElement }) => {
|
|
111
|
+
const items = canvasElement.querySelectorAll('[data-slot="item"]');
|
|
112
|
+
await expect(items).toHaveLength(3);
|
|
113
|
+
await expect(items[0].getAttribute('data-variant')).toBe('default');
|
|
114
|
+
await expect(items[1].getAttribute('data-variant')).toBe('outline');
|
|
115
|
+
await expect(items[2].getAttribute('data-variant')).toBe('muted');
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
export { Default, GroupWithSeparators, Variants, WithActions, stdin_default as default };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cn } from './lib/utils.js';
|
|
3
|
+
function Kbd({ className, ...props }) {
|
|
4
|
+
return (
|
|
5
|
+
<kbd
|
|
6
|
+
data-slot="kbd"
|
|
7
|
+
className={cn(
|
|
8
|
+
'cn-kbd pointer-events-none inline-flex items-center justify-center select-none',
|
|
9
|
+
className,
|
|
10
|
+
)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function KbdGroup({ className, ...props }) {
|
|
16
|
+
return (
|
|
17
|
+
<kbd
|
|
18
|
+
data-slot="kbd-group"
|
|
19
|
+
className={cn('cn-kbd-group inline-flex items-center', className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
export { Kbd, KbdGroup };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { expect, within } from 'storybook/test';
|
|
3
|
+
import { Kbd, KbdGroup } from './kbd.jsx';
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'UI/Kbd',
|
|
6
|
+
component: Kbd,
|
|
7
|
+
};
|
|
8
|
+
var stdin_default = meta;
|
|
9
|
+
const Default = {
|
|
10
|
+
args: { children: 'K' },
|
|
11
|
+
play: async ({ canvasElement }) => {
|
|
12
|
+
const canvas = within(canvasElement);
|
|
13
|
+
const kbd = canvas.getByText('K');
|
|
14
|
+
await expect(kbd).toBeInTheDocument();
|
|
15
|
+
await expect(kbd.dataset.slot).toBe('kbd');
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
const Group = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<KbdGroup>
|
|
21
|
+
<Kbd>Ctrl</Kbd>
|
|
22
|
+
<Kbd>K</Kbd>
|
|
23
|
+
</KbdGroup>
|
|
24
|
+
),
|
|
25
|
+
play: async ({ canvasElement }) => {
|
|
26
|
+
const group = canvasElement.querySelector('[data-slot="kbd-group"]');
|
|
27
|
+
await expect(group).toBeInTheDocument();
|
|
28
|
+
const keys = canvasElement.querySelectorAll('[data-slot="kbd"]');
|
|
29
|
+
await expect(keys.length).toBe(2);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export { Default, Group, stdin_default as default };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Label as LabelPrimitive } from 'radix-ui';
|
|
3
|
+
import { cn } from './lib/utils.js';
|
|
4
|
+
function Label({ className, ...props }) {
|
|
5
|
+
return (
|
|
6
|
+
<LabelPrimitive.Root
|
|
7
|
+
data-slot="label"
|
|
8
|
+
className={cn(
|
|
9
|
+
'cn-label flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed',
|
|
10
|
+
className,
|
|
11
|
+
)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
export { Label };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { expect, within } from 'storybook/test';
|
|
2
|
+
import { Label } from './label.jsx';
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'UI/Label',
|
|
5
|
+
component: Label,
|
|
6
|
+
};
|
|
7
|
+
var stdin_default = meta;
|
|
8
|
+
const Default = {
|
|
9
|
+
args: { children: 'Email address' },
|
|
10
|
+
play: async ({ canvasElement }) => {
|
|
11
|
+
const canvas = within(canvasElement);
|
|
12
|
+
const label = canvas.getByText('Email address');
|
|
13
|
+
await expect(label).toBeInTheDocument();
|
|
14
|
+
await expect(label.dataset.slot).toBe('label');
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const WithHtmlFor = {
|
|
18
|
+
args: { children: 'Username', htmlFor: 'username' },
|
|
19
|
+
play: async ({ canvasElement }) => {
|
|
20
|
+
const canvas = within(canvasElement);
|
|
21
|
+
const label = canvas.getByText('Username');
|
|
22
|
+
await expect(label).toHaveAttribute('for', 'username');
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export { Default, WithHtmlFor, stdin_default as default };
|