create-mikstack 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/README.md +54 -0
- package/dist/index.js +410 -0
- package/package.json +43 -0
- package/templates/adapters/cloudflare/package.json.partial +5 -0
- package/templates/adapters/cloudflare/svelte.config.js +19 -0
- package/templates/adapters/node/Dockerfile +30 -0
- package/templates/adapters/node/docker-compose.prod.yml +27 -0
- package/templates/adapters/node/package.json.partial +5 -0
- package/templates/adapters/node/svelte.config.js +19 -0
- package/templates/adapters/vercel/package.json.partial +5 -0
- package/templates/adapters/vercel/svelte.config.js +19 -0
- package/templates/base/.env.example +23 -0
- package/templates/base/.gitignore.append +2 -0
- package/templates/base/.mcp.json +9 -0
- package/templates/base/.prettierignore +10 -0
- package/templates/base/.vscode/extensions.json +3 -0
- package/templates/base/AGENTS.md +123 -0
- package/templates/base/README.md +27 -0
- package/templates/base/agents.md +28 -0
- package/templates/base/docker-compose.yml +15 -0
- package/templates/base/drizzle-zero.config.ts +17 -0
- package/templates/base/drizzle.config.ts +17 -0
- package/templates/base/eslint.config.ts +65 -0
- package/templates/base/package.json.partial +43 -0
- package/templates/base/prettier.config.js +6 -0
- package/templates/base/src/app.d.ts +12 -0
- package/templates/base/src/app.html +11 -0
- package/templates/base/src/hooks.server.ts +15 -0
- package/templates/base/src/lib/auth-client.ts +6 -0
- package/templates/base/src/lib/server/auth.ts +52 -0
- package/templates/base/src/lib/server/db/index.ts +19 -0
- package/templates/base/src/lib/server/db/schema.ts +117 -0
- package/templates/base/src/lib/server/db/seed.ts +21 -0
- package/templates/base/src/lib/server/emails/magic-link.ts +77 -0
- package/templates/base/src/lib/server/emails/send.ts +55 -0
- package/templates/base/src/lib/server/notifications/definitions.ts +12 -0
- package/templates/base/src/lib/server/notifications.ts +38 -0
- package/templates/base/src/lib/z.svelte.ts +14 -0
- package/templates/base/src/lib/zero/context.ts +9 -0
- package/templates/base/src/lib/zero/db-provider.server.ts +11 -0
- package/templates/base/src/lib/zero/mutators.ts +35 -0
- package/templates/base/src/lib/zero/queries.ts +21 -0
- package/templates/base/src/lib/zero/schema.ts +1 -0
- package/templates/base/src/routes/+layout.server.ts +5 -0
- package/templates/base/src/routes/+layout.svelte +7 -0
- package/templates/base/src/routes/+page.server.ts +7 -0
- package/templates/base/src/routes/+page.svelte +319 -0
- package/templates/base/src/routes/api/dev/emails/+server.ts +89 -0
- package/templates/base/src/routes/api/dev/emails/[id]/+server.ts +24 -0
- package/templates/base/src/routes/api/notifications/[...path]/+server.ts +10 -0
- package/templates/base/src/routes/api/zero/get-queries/+server.ts +29 -0
- package/templates/base/src/routes/api/zero/mutate/+server.ts +31 -0
- package/templates/base/src/routes/sign-in/+page.svelte +97 -0
- package/templates/base/tsconfig.json +40 -0
- package/templates/github-actions-bun/.github/workflows/ci.yml +22 -0
- package/templates/github-actions-npm/.github/workflows/ci.yml +25 -0
- package/templates/github-actions-pnpm/.github/workflows/ci.yml +27 -0
- package/templates/i18n/lingui.config.ts +16 -0
- package/templates/i18n/package.json.partial +14 -0
- package/templates/i18n/src/lib/i18n.ts +10 -0
- package/templates/i18n/src/locales/en.po +6 -0
- package/templates/i18n/src/po.d.ts +3 -0
- package/templates/i18n/vite.config.ts +7 -0
- package/templates/supply-chain-bun/bunfig.toml +3 -0
- package/templates/testing/package.json.partial +11 -0
- package/templates/testing/src/example.test.ts +7 -0
- package/templates/testing/src/lib/server/db/test-utils.ts +25 -0
- package/templates/testing/vitest.config.ts +9 -0
- package/templates/ui/.vscode/extensions.json +8 -0
- package/templates/ui/package.json.partial +13 -0
- package/templates/ui/src/app.css +94 -0
- package/templates/ui/src/routes/+layout.svelte +12 -0
- package/templates/ui/stylelint.config.js +7 -0
- package/templates/ui/vite.config.ts +6 -0
- package/templates/ui-dependency/package.json.partial +5 -0
- package/templates/ui-vendor/src/lib/components/ui/Accordion/Accordion.svelte +71 -0
- package/templates/ui-vendor/src/lib/components/ui/Accordion/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Alert/Alert.svelte +60 -0
- package/templates/ui-vendor/src/lib/components/ui/Alert/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Badge/Badge.svelte +48 -0
- package/templates/ui-vendor/src/lib/components/ui/Badge/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Button/Button.svelte +77 -0
- package/templates/ui-vendor/src/lib/components/ui/Button/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Card/Card.svelte +49 -0
- package/templates/ui-vendor/src/lib/components/ui/Card/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Dialog/Dialog.svelte +70 -0
- package/templates/ui-vendor/src/lib/components/ui/Dialog/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/FormField/FormField.svelte +53 -0
- package/templates/ui-vendor/src/lib/components/ui/FormField/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Input/Input.svelte +27 -0
- package/templates/ui-vendor/src/lib/components/ui/Input/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Separator/Separator.svelte +26 -0
- package/templates/ui-vendor/src/lib/components/ui/Separator/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Skeleton/Skeleton.svelte +40 -0
- package/templates/ui-vendor/src/lib/components/ui/Skeleton/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Switch/Switch.svelte +86 -0
- package/templates/ui-vendor/src/lib/components/ui/Switch/index.ts +1 -0
- package/templates/ui-vendor/src/lib/components/ui/Textarea/Textarea.svelte +29 -0
- package/templates/ui-vendor/src/lib/components/ui/Textarea/index.ts +1 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
summary: Snippet;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
open?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { summary, children, open = false }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<details {open}>
|
|
14
|
+
<summary>
|
|
15
|
+
{@render summary()}
|
|
16
|
+
<svg class="chevron" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
17
|
+
<path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
18
|
+
</svg>
|
|
19
|
+
</summary>
|
|
20
|
+
<div class="content">
|
|
21
|
+
{@render children()}
|
|
22
|
+
</div>
|
|
23
|
+
</details>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
details {
|
|
27
|
+
border: 1px solid var(--border);
|
|
28
|
+
border-radius: var(--radius-md);
|
|
29
|
+
background-color: var(--surface-1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
summary {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
padding: var(--space-3) var(--space-4);
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
font-weight: 500;
|
|
39
|
+
list-style: none;
|
|
40
|
+
user-select: none;
|
|
41
|
+
|
|
42
|
+
&::-webkit-details-marker {
|
|
43
|
+
display: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:hover {
|
|
47
|
+
background-color: var(--surface-2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&:focus-visible {
|
|
51
|
+
outline: 2px solid var(--focus);
|
|
52
|
+
outline-offset: -2px;
|
|
53
|
+
border-radius: var(--radius-md);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.chevron {
|
|
58
|
+
transition: transform 0.2s;
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
details[open] > summary .chevron {
|
|
63
|
+
transform: rotate(180deg);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.content {
|
|
67
|
+
padding: 0 var(--space-4) var(--space-4);
|
|
68
|
+
color: var(--text-2);
|
|
69
|
+
font-size: var(--text-sm);
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Accordion.svelte";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'info' | 'success' | 'warning' | 'danger';
|
|
6
|
+
children: Snippet;
|
|
7
|
+
title?: Snippet;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { variant = 'info', children, title }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div role="alert" data-variant={variant}>
|
|
14
|
+
{#if title}
|
|
15
|
+
<strong class="alert-title">{@render title()}</strong>
|
|
16
|
+
{/if}
|
|
17
|
+
<div class="alert-body">
|
|
18
|
+
{@render children()}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<style>
|
|
23
|
+
[role='alert'] {
|
|
24
|
+
padding: var(--space-3) var(--space-4);
|
|
25
|
+
border-radius: var(--radius-md);
|
|
26
|
+
border: 1px solid;
|
|
27
|
+
font-size: var(--text-sm);
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
gap: var(--space-1);
|
|
31
|
+
|
|
32
|
+
&[data-variant='info'] {
|
|
33
|
+
background-color: oklch(from var(--accent) l c h / 10%);
|
|
34
|
+
border-color: oklch(from var(--accent) l c h / 30%);
|
|
35
|
+
color: var(--text-1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&[data-variant='success'] {
|
|
39
|
+
background-color: oklch(55% 0.15 145 / 10%);
|
|
40
|
+
border-color: oklch(55% 0.15 145 / 30%);
|
|
41
|
+
color: var(--text-1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&[data-variant='warning'] {
|
|
45
|
+
background-color: oklch(75% 0.15 85 / 10%);
|
|
46
|
+
border-color: oklch(75% 0.15 85 / 30%);
|
|
47
|
+
color: var(--text-1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&[data-variant='danger'] {
|
|
51
|
+
background-color: oklch(from var(--danger) l c h / 10%);
|
|
52
|
+
border-color: oklch(from var(--danger) l c h / 30%);
|
|
53
|
+
color: var(--text-1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.alert-title {
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Alert.svelte";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'default' | 'secondary' | 'success' | 'danger';
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { variant = 'default', children }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<span data-variant={variant}>
|
|
13
|
+
{@render children()}
|
|
14
|
+
</span>
|
|
15
|
+
|
|
16
|
+
<style>
|
|
17
|
+
span {
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: var(--space-1);
|
|
21
|
+
padding: var(--space-1) var(--space-2);
|
|
22
|
+
border-radius: 9999px;
|
|
23
|
+
font-size: var(--text-xs);
|
|
24
|
+
font-weight: 500;
|
|
25
|
+
line-height: 1;
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
|
|
28
|
+
&[data-variant='default'] {
|
|
29
|
+
background-color: var(--accent);
|
|
30
|
+
color: white;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&[data-variant='secondary'] {
|
|
34
|
+
background-color: var(--surface-3);
|
|
35
|
+
color: var(--text-1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&[data-variant='success'] {
|
|
39
|
+
background-color: oklch(55% 0.15 145);
|
|
40
|
+
color: white;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&[data-variant='danger'] {
|
|
44
|
+
background-color: var(--danger);
|
|
45
|
+
color: white;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Badge.svelte";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props extends HTMLButtonAttributes {
|
|
6
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
|
|
7
|
+
children: Snippet;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { variant = 'primary', children, ...rest }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<button data-variant={variant} {...rest}>
|
|
14
|
+
{@render children()}
|
|
15
|
+
</button>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
button {
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: var(--space-2);
|
|
22
|
+
padding: var(--space-2) var(--space-4);
|
|
23
|
+
border: 1px solid transparent;
|
|
24
|
+
border-radius: var(--radius-md);
|
|
25
|
+
font-size: var(--text-sm);
|
|
26
|
+
font-weight: 500;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
transition: background-color 0.15s, border-color 0.15s;
|
|
29
|
+
|
|
30
|
+
&:focus-visible {
|
|
31
|
+
outline: 2px solid var(--focus);
|
|
32
|
+
outline-offset: 2px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&:disabled {
|
|
36
|
+
opacity: 0.5;
|
|
37
|
+
cursor: not-allowed;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&[data-variant='primary'] {
|
|
41
|
+
background-color: var(--accent);
|
|
42
|
+
color: white;
|
|
43
|
+
|
|
44
|
+
&:hover:not(:disabled) {
|
|
45
|
+
background-color: oklch(from var(--accent) calc(l - 0.05) c h);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&[data-variant='secondary'] {
|
|
50
|
+
background-color: var(--surface-2);
|
|
51
|
+
border-color: var(--border);
|
|
52
|
+
color: var(--text-1);
|
|
53
|
+
|
|
54
|
+
&:hover:not(:disabled) {
|
|
55
|
+
background-color: var(--surface-3);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&[data-variant='ghost'] {
|
|
60
|
+
background-color: transparent;
|
|
61
|
+
color: var(--text-1);
|
|
62
|
+
|
|
63
|
+
&:hover:not(:disabled) {
|
|
64
|
+
background-color: var(--surface-2);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&[data-variant='danger'] {
|
|
69
|
+
background-color: var(--danger);
|
|
70
|
+
color: white;
|
|
71
|
+
|
|
72
|
+
&:hover:not(:disabled) {
|
|
73
|
+
background-color: oklch(from var(--danger) calc(l - 0.05) c h);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Button.svelte";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
header?: Snippet;
|
|
8
|
+
footer?: Snippet;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { children, header, footer, ...rest }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<article {...rest}>
|
|
15
|
+
{#if header}
|
|
16
|
+
<div class="card-header">
|
|
17
|
+
{@render header()}
|
|
18
|
+
</div>
|
|
19
|
+
{/if}
|
|
20
|
+
<div class="card-body">
|
|
21
|
+
{@render children()}
|
|
22
|
+
</div>
|
|
23
|
+
{#if footer}
|
|
24
|
+
<div class="card-footer">
|
|
25
|
+
{@render footer()}
|
|
26
|
+
</div>
|
|
27
|
+
{/if}
|
|
28
|
+
</article>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
article {
|
|
32
|
+
border: 1px solid var(--border);
|
|
33
|
+
border-radius: var(--radius-lg);
|
|
34
|
+
background-color: var(--surface-1);
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.card-header {
|
|
39
|
+
padding: var(--space-4) var(--space-4) 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.card-body {
|
|
43
|
+
padding: var(--space-4);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.card-footer {
|
|
47
|
+
padding: 0 var(--space-4) var(--space-4);
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Card.svelte";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
open?: boolean;
|
|
6
|
+
onclose?: () => void;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { open = $bindable(false), onclose, children }: Props = $props();
|
|
11
|
+
|
|
12
|
+
let dialogEl: HTMLDialogElement | undefined = $state();
|
|
13
|
+
|
|
14
|
+
$effect(() => {
|
|
15
|
+
if (!dialogEl) return;
|
|
16
|
+
if (open && !dialogEl.open) {
|
|
17
|
+
dialogEl.showModal();
|
|
18
|
+
} else if (!open && dialogEl.open) {
|
|
19
|
+
dialogEl.close();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function handleClose() {
|
|
24
|
+
open = false;
|
|
25
|
+
onclose?.();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleClick(e: MouseEvent) {
|
|
29
|
+
if (e.target === dialogEl) {
|
|
30
|
+
handleClose();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<dialog bind:this={dialogEl} onclose={handleClose} onclick={handleClick}>
|
|
36
|
+
<div class="dialog-content">
|
|
37
|
+
{@render children()}
|
|
38
|
+
</div>
|
|
39
|
+
</dialog>
|
|
40
|
+
|
|
41
|
+
<style>
|
|
42
|
+
dialog {
|
|
43
|
+
border: none;
|
|
44
|
+
border-radius: var(--radius-lg);
|
|
45
|
+
padding: 0;
|
|
46
|
+
max-width: min(32rem, calc(100vw - var(--space-6)));
|
|
47
|
+
max-height: min(85vh, calc(100vh - var(--space-6)));
|
|
48
|
+
background-color: var(--surface-1);
|
|
49
|
+
color: var(--text-1);
|
|
50
|
+
box-shadow:
|
|
51
|
+
0 10px 15px -3px rgb(0 0 0 / 10%),
|
|
52
|
+
0 4px 6px -4px rgb(0 0 0 / 10%);
|
|
53
|
+
|
|
54
|
+
&::backdrop {
|
|
55
|
+
background-color: rgb(0 0 0 / 50%);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&[open] {
|
|
59
|
+
display: flex;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.dialog-content {
|
|
64
|
+
padding: var(--space-5);
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
gap: var(--space-4);
|
|
68
|
+
overflow-y: auto;
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Dialog.svelte";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface LabelAttrs {
|
|
5
|
+
for: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ErrorAttrs {
|
|
9
|
+
id: string;
|
|
10
|
+
role: 'alert';
|
|
11
|
+
'aria-live': 'polite';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
for: string;
|
|
16
|
+
label: Snippet<[LabelAttrs]>;
|
|
17
|
+
children: Snippet;
|
|
18
|
+
error?: Snippet<[ErrorAttrs]>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { for: htmlFor, label, children, error }: Props = $props();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<div class="field">
|
|
25
|
+
<div class="field-label">
|
|
26
|
+
{@render label({ for: htmlFor })}
|
|
27
|
+
</div>
|
|
28
|
+
{@render children()}
|
|
29
|
+
{#if error}
|
|
30
|
+
<div class="field-error">
|
|
31
|
+
{@render error({ id: `${htmlFor}-error`, role: 'alert', 'aria-live': 'polite' })}
|
|
32
|
+
</div>
|
|
33
|
+
{/if}
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.field {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
gap: var(--space-1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.field-label {
|
|
44
|
+
font-size: var(--text-sm);
|
|
45
|
+
font-weight: 500;
|
|
46
|
+
color: var(--text-1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.field-error {
|
|
50
|
+
font-size: var(--text-sm);
|
|
51
|
+
color: var(--danger);
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./FormField.svelte";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
let { ...rest }: HTMLInputAttributes = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<input {...rest} />
|
|
8
|
+
|
|
9
|
+
<style>
|
|
10
|
+
input {
|
|
11
|
+
padding: var(--space-2) var(--space-3);
|
|
12
|
+
border: 1px solid var(--border);
|
|
13
|
+
border-radius: var(--radius-md);
|
|
14
|
+
background-color: var(--surface-1);
|
|
15
|
+
font-size: var(--text-base);
|
|
16
|
+
width: 100%;
|
|
17
|
+
|
|
18
|
+
&:focus-visible {
|
|
19
|
+
outline: 2px solid var(--focus);
|
|
20
|
+
outline-offset: 2px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&[aria-invalid] {
|
|
24
|
+
border-color: var(--danger);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Input.svelte";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
orientation?: 'horizontal' | 'vertical';
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let { orientation = 'horizontal' }: Props = $props();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<hr data-orientation={orientation} aria-orientation={orientation} />
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
hr {
|
|
13
|
+
border: none;
|
|
14
|
+
background-color: var(--border);
|
|
15
|
+
|
|
16
|
+
&[data-orientation='horizontal'] {
|
|
17
|
+
height: 1px;
|
|
18
|
+
width: 100%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&[data-orientation='vertical'] {
|
|
22
|
+
width: 1px;
|
|
23
|
+
height: 100%;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Separator.svelte";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
width?: string;
|
|
4
|
+
height?: string;
|
|
5
|
+
circle?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { width, height = '1rem', circle = false }: Props = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div
|
|
12
|
+
class="skeleton"
|
|
13
|
+
class:circle
|
|
14
|
+
style:width={circle ? height : width}
|
|
15
|
+
style:height
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
></div>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
.skeleton {
|
|
21
|
+
background-color: var(--surface-3);
|
|
22
|
+
border-radius: var(--radius-md);
|
|
23
|
+
animation: pulse 2s ease-in-out infinite;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.circle {
|
|
27
|
+
border-radius: 9999px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@keyframes pulse {
|
|
31
|
+
0%,
|
|
32
|
+
100% {
|
|
33
|
+
opacity: 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
50% {
|
|
37
|
+
opacity: 0.5;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Skeleton.svelte";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
interface Props extends Omit<HTMLInputAttributes, 'type'> {
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { label, id, ...rest }: Props = $props();
|
|
9
|
+
|
|
10
|
+
const inputId = $derived(id ?? (label ? `switch-${label.toLowerCase().replace(/\s+/g, '-')}` : undefined));
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<label class="switch-wrapper" for={inputId}>
|
|
14
|
+
<input type="checkbox" role="switch" id={inputId} {...rest} />
|
|
15
|
+
<span class="track">
|
|
16
|
+
<span class="thumb"></span>
|
|
17
|
+
</span>
|
|
18
|
+
{#if label}
|
|
19
|
+
<span class="switch-label">{label}</span>
|
|
20
|
+
{/if}
|
|
21
|
+
</label>
|
|
22
|
+
|
|
23
|
+
<style>
|
|
24
|
+
.switch-wrapper {
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: var(--space-2);
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
input {
|
|
32
|
+
position: absolute;
|
|
33
|
+
width: 1px;
|
|
34
|
+
height: 1px;
|
|
35
|
+
padding: 0;
|
|
36
|
+
margin: -1px;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
clip: rect(0, 0, 0, 0);
|
|
39
|
+
white-space: nowrap;
|
|
40
|
+
border: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.track {
|
|
44
|
+
position: relative;
|
|
45
|
+
width: 2.75rem;
|
|
46
|
+
height: 1.5rem;
|
|
47
|
+
background-color: var(--surface-3);
|
|
48
|
+
border-radius: 9999px;
|
|
49
|
+
transition: background-color 0.15s;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.thumb {
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 2px;
|
|
55
|
+
left: 2px;
|
|
56
|
+
width: 1.25rem;
|
|
57
|
+
height: 1.25rem;
|
|
58
|
+
background-color: white;
|
|
59
|
+
border-radius: 9999px;
|
|
60
|
+
transition: transform 0.15s;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
input:checked + .track {
|
|
64
|
+
background-color: var(--accent);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
input:checked + .track .thumb {
|
|
68
|
+
transform: translateX(1.25rem);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
input:focus-visible + .track {
|
|
72
|
+
outline: 2px solid var(--focus);
|
|
73
|
+
outline-offset: 2px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
input:disabled + .track {
|
|
77
|
+
opacity: 0.5;
|
|
78
|
+
cursor: not-allowed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.switch-label {
|
|
82
|
+
font-size: var(--text-sm);
|
|
83
|
+
color: var(--text-1);
|
|
84
|
+
user-select: none;
|
|
85
|
+
}
|
|
86
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Switch.svelte";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
let { ...rest }: HTMLTextareaAttributes = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<textarea {...rest}></textarea>
|
|
8
|
+
|
|
9
|
+
<style>
|
|
10
|
+
textarea {
|
|
11
|
+
padding: var(--space-2) var(--space-3);
|
|
12
|
+
border: 1px solid var(--border);
|
|
13
|
+
border-radius: var(--radius-md);
|
|
14
|
+
background-color: var(--surface-1);
|
|
15
|
+
font-size: var(--text-base);
|
|
16
|
+
width: 100%;
|
|
17
|
+
min-height: 5rem;
|
|
18
|
+
resize: vertical;
|
|
19
|
+
|
|
20
|
+
&:focus-visible {
|
|
21
|
+
outline: 2px solid var(--focus);
|
|
22
|
+
outline-offset: 2px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&[aria-invalid] {
|
|
26
|
+
border-color: var(--danger);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Textarea.svelte";
|