@atmo-dev/kit 0.0.0-stage → 0.0.1

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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -2
  3. package/dist/components/Avatar.svelte +35 -0
  4. package/dist/components/Avatar.svelte.d.ts +9 -0
  5. package/dist/components/Badge.svelte +21 -0
  6. package/dist/components/Badge.svelte.d.ts +7 -0
  7. package/dist/components/Button.svelte +94 -0
  8. package/dist/components/Button.svelte.d.ts +13 -0
  9. package/dist/components/Checkbox.svelte +29 -0
  10. package/dist/components/Checkbox.svelte.d.ts +8 -0
  11. package/dist/components/Heading.svelte +24 -0
  12. package/dist/components/Heading.svelte.d.ts +8 -0
  13. package/dist/components/Icon.svelte +24 -0
  14. package/dist/components/Icon.svelte.d.ts +8 -0
  15. package/dist/components/IconButton.svelte +24 -0
  16. package/dist/components/IconButton.svelte.d.ts +8 -0
  17. package/dist/components/IconRail.svelte +186 -0
  18. package/dist/components/IconRail.svelte.d.ts +26 -0
  19. package/dist/components/InlineEditor.svelte +120 -0
  20. package/dist/components/InlineEditor.svelte.d.ts +19 -0
  21. package/dist/components/Input.svelte +32 -0
  22. package/dist/components/Input.svelte.d.ts +9 -0
  23. package/dist/components/InsetImage.svelte +29 -0
  24. package/dist/components/InsetImage.svelte.d.ts +9 -0
  25. package/dist/components/Label.svelte +19 -0
  26. package/dist/components/Label.svelte.d.ts +7 -0
  27. package/dist/components/LinkCard.svelte +47 -0
  28. package/dist/components/LinkCard.svelte.d.ts +12 -0
  29. package/dist/components/Modal.svelte +93 -0
  30. package/dist/components/Modal.svelte.d.ts +12 -0
  31. package/dist/components/NestedComments.svelte +61 -0
  32. package/dist/components/NestedComments.svelte.d.ts +8 -0
  33. package/dist/components/Popover.svelte +49 -0
  34. package/dist/components/Popover.svelte.d.ts +12 -0
  35. package/dist/components/PostComposer.svelte +41 -0
  36. package/dist/components/PostComposer.svelte.d.ts +16 -0
  37. package/dist/components/PostContent.svelte +62 -0
  38. package/dist/components/PostContent.svelte.d.ts +8 -0
  39. package/dist/components/PostVideo.svelte +74 -0
  40. package/dist/components/PostVideo.svelte.d.ts +9 -0
  41. package/dist/components/Select.svelte +34 -0
  42. package/dist/components/Select.svelte.d.ts +9 -0
  43. package/dist/components/Subheading.svelte +22 -0
  44. package/dist/components/Subheading.svelte.d.ts +8 -0
  45. package/dist/components/Surface.svelte +40 -0
  46. package/dist/components/Surface.svelte.d.ts +11 -0
  47. package/dist/components/Tabs.svelte +47 -0
  48. package/dist/components/Tabs.svelte.d.ts +16 -0
  49. package/dist/components/Textarea.svelte +20 -0
  50. package/dist/components/Textarea.svelte.d.ts +7 -0
  51. package/dist/components/ToggleGroup.svelte +32 -0
  52. package/dist/components/ToggleGroup.svelte.d.ts +12 -0
  53. package/dist/components/ToggleGroupItem.svelte +26 -0
  54. package/dist/components/ToggleGroupItem.svelte.d.ts +8 -0
  55. package/dist/components/WorkspaceSidebar.svelte +82 -0
  56. package/dist/components/WorkspaceSidebar.svelte.d.ts +10 -0
  57. package/dist/components/login/ActorSearchField.svelte +238 -0
  58. package/dist/components/login/ActorSearchField.svelte.d.ts +19 -0
  59. package/dist/components/login/AtmosphereApps.svelte +19 -0
  60. package/dist/components/login/AtmosphereApps.svelte.d.ts +18 -0
  61. package/dist/components/login/LoginDialog.svelte +78 -0
  62. package/dist/components/login/LoginDialog.svelte.d.ts +9 -0
  63. package/dist/components/login/LoginForm.svelte +402 -0
  64. package/dist/components/login/LoginForm.svelte.d.ts +28 -0
  65. package/dist/components/login/RecentAccounts.svelte +57 -0
  66. package/dist/components/login/RecentAccounts.svelte.d.ts +10 -0
  67. package/dist/components/login/brands/README.md +7 -0
  68. package/dist/components/login/brands/blacksky.svg +3 -0
  69. package/dist/components/login/brands/bluesky.svg +3 -0
  70. package/dist/components/login/brands/bookhive.svg +3 -0
  71. package/dist/components/login/brands/graze.svg +3 -0
  72. package/dist/components/login/brands/leaflet.svg +3 -0
  73. package/dist/components/login/brands/pckt.svg +3 -0
  74. package/dist/components/login/brands/surf.svg +3 -0
  75. package/dist/components/login/brands/tangled.svg +3 -0
  76. package/dist/components/login/handle.d.ts +3 -0
  77. package/dist/components/login/handle.js +11 -0
  78. package/dist/components/login/permissions.d.ts +3 -0
  79. package/dist/components/login/permissions.js +13 -0
  80. package/dist/components/login/recent-accounts.d.ts +16 -0
  81. package/dist/components/login/recent-accounts.js +87 -0
  82. package/dist/components/login/search.d.ts +9 -0
  83. package/dist/components/login/search.js +47 -0
  84. package/dist/components/login/types.d.ts +27 -0
  85. package/dist/components/login/types.js +1 -0
  86. package/dist/confetti.d.ts +2 -0
  87. package/dist/confetti.js +13 -0
  88. package/dist/index.d.ts +51 -0
  89. package/dist/index.js +39 -0
  90. package/dist/palettes.d.ts +2 -0
  91. package/dist/palettes.js +30 -0
  92. package/dist/social/mention-suggestions.d.ts +35 -0
  93. package/dist/social/mention-suggestions.js +122 -0
  94. package/dist/social/posts.d.ts +95 -0
  95. package/dist/social/posts.js +59 -0
  96. package/dist/social/richtext.d.ts +25 -0
  97. package/dist/social/richtext.js +100 -0
  98. package/dist/styles.d.ts +2 -0
  99. package/dist/styles.js +3 -0
  100. package/dist/theme.css +503 -0
  101. package/dist/theme.d.ts +2 -0
  102. package/dist/theme.js +23 -0
  103. package/dist/utils.d.ts +2 -0
  104. package/dist/utils.js +6 -0
  105. package/package.json +99 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 flo-bit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,215 @@
1
- # Temporary Holding Version
1
+ # Atmo Kit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Svelte 5 / Tailwind 4 UI kit for Florian’s Atmosphere apps, packaged as `@atmo-dev/kit` under the MIT license. Figtree, Sketchyicons, inherited base/accent palettes, vibrant primary actions, and inset-only depth. It is currently developed through local links; publishing is a separate release step.
4
+
5
+ ## Develop
6
+
7
+ ```sh
8
+ pnpm install
9
+ pnpm dev --host 127.0.0.1 --port 5190
10
+ pnpm check
11
+ pnpm test
12
+ pnpm build
13
+ ```
14
+
15
+ - `/` — foundation and native controls
16
+ - `/login` — interactive login examples, with local data and callbacks only
17
+ - `/components` — popovers, segmented controls, editors, posts, and comments, with local data only
18
+ - `/preview` — eight app mockups, separate from the library
19
+
20
+ Mockups own their layouts and demo interactions, not a parallel set of control skins. Buttons, icon buttons, fields, avatars, inset images, surfaces, dialogs, and navigation use the actual library components. Shared component changes therefore appear in the mockups too; there is no separate preview compatibility stylesheet.
21
+
22
+ `src/lib` is the library. `src/playground`, routes, and tests are not public exports. The design brief lives beside this repository, at `../DESIGN.md`.
23
+
24
+ ## Theme and icons
25
+
26
+ In a locally linked consumer with Tailwind 4, import the theme after Tailwind:
27
+
28
+ ```css
29
+ @import 'tailwindcss';
30
+ @import '@atmo-dev/kit/theme.css';
31
+ ```
32
+
33
+ The theme includes an `@source './'` declaration for the packaged components. Your app must also scan its own Svelte source. Import this stylesheet once in the root layout. It includes the Figtree font.
34
+
35
+ ```svelte
36
+ <script lang="ts">
37
+ import { Button, Icon } from '@atmo-dev/kit';
38
+ import { Plus } from '@sketchyicons/svelte';
39
+ </script>
40
+
41
+ <div class="atmo zinc pink dark min-h-dvh">
42
+ <Button><Icon img={Plus} />Create</Button>
43
+ <Button class="red">Delete</Button>
44
+ <section class="blue"><Button>Local blue accent</Button></section>
45
+ </div>
46
+ ```
47
+
48
+ Use `.light` / `.dark`, one neutral base class, and one chromatic accent class. `baseColors` and `accentColors` list the supported ramps. All ramps include shades 50–950. Local hue classes update the inherited semantic colors without changing siblings. CSS variables configure typography, radii, duration, and inset depth.
49
+
50
+ Primary buttons use `bg-accent-400 text-base-950 hover:bg-accent-300 active:bg-accent-400` directly in both modes—no primary-specific color tokens or mixed shades. Primary `IconButton`s inherit the same classes, and local hue classes still work. Secondary, ghost, and link treatments stay quiet. Semantic tokens remain for surfaces, text, borders, and focus where light/dark mode needs different shades.
51
+
52
+ UI icons take explicit `img` data imports, not string names. Consumers importing icon data should declare `@sketchyicons/svelte` themselves. Caller classes are merged with Tailwind defaults; native control attributes, events, bindings, snippets, and refs remain available where appropriate. Modal and Tabs use Bits UI. Portaled content retains its source palette and font, including subsequent theme changes.
53
+
54
+ `Surface` supports semantic containers (`div`, `section`, `article`, `aside`, `main`) and clickable cards with `as="button"`. Clickable surfaces are native buttons, default to `type="button"`, and retain keyboard activation, disabled behavior, and kit focus styles. Apps still own card content and layout.
55
+
56
+ ## Navigation
57
+
58
+ `IconRail` is bare icon navigation, not an application shell:
59
+
60
+ ```svelte
61
+ <script lang="ts">
62
+ import { IconRail } from '@atmo-dev/kit';
63
+ import { Home, Plus } from '@sketchyicons/svelte';
64
+ </script>
65
+
66
+ <IconRail
67
+ label="App navigation"
68
+ items={[
69
+ { label: 'Home', icon: Home, href: '/', active: true },
70
+ { label: 'Create', icon: Plus, onclick: create, primary: true }
71
+ ]}
72
+ />
73
+ ```
74
+
75
+ Items support real `href` links or click handlers, disabled states, current-page state, and unread badges. `backed` adds quiet individual backings. A `footer` snippet can contain an app-owned account button or menu. Tooltips use Bits UI. The default rail is 72px wide and becomes a bottom bar at 600px and below; apps own their content spacing.
76
+
77
+ For a hamburger-opened mobile sidebar instead of a bottom bar, use `mobile="drawer"`. The same items become full-width icon-and-text rows; desktop keeps the compact rail:
78
+
79
+ ```svelte
80
+ <script lang="ts">
81
+ import { Button, Icon, IconButton, IconRail } from '@atmo-dev/kit';
82
+ import { Home, Menu, Plus, User } from '@sketchyicons/svelte';
83
+ let open = $state(false);
84
+ </script>
85
+
86
+ <header>
87
+ <IconButton
88
+ label="Open navigation"
89
+ aria-expanded={open}
90
+ aria-controls="app-navigation"
91
+ onclick={() => (open = true)}
92
+ class="min-[601px]:hidden"><Icon img={Menu} /></IconButton
93
+ >
94
+ </header>
95
+ <IconRail
96
+ id="app-navigation"
97
+ label="App navigation"
98
+ mobile="drawer"
99
+ bind:open
100
+ items={[
101
+ { label: 'Home', icon: Home, href: '/', active: true },
102
+ { label: 'Create', icon: Plus, onclick: create, primary: true }
103
+ ]}
104
+ >
105
+ {#snippet footer({ expanded, close })}
106
+ <Button href="/account" variant="ghost" onclick={close}>
107
+ <Icon img={User} />
108
+ <span class={expanded ? '' : 'sr-only'}>Your account</span>
109
+ </Button>
110
+ {/snippet}
111
+ </IconRail>
112
+ <main class="min-[601px]:ml-18">…</main>
113
+ ```
114
+
115
+ The drawer uses its label as a screen-reader-only title, leaving only the close button above the links. It slides in over 130ms and skips the animation when reduced motion is preferred. It traps focus, locks background scrolling, closes on Escape/outside interaction or item selection, and restores focus to its opener. Resizing an open drawer to desktop closes it and focuses the visible rail. Navigation contents mount once. `footer({ expanded, close })` lets app-owned account controls show text in the drawer and close it before another action. Existing argument-free footer snippets still work. Apps place their own trigger and close `open` on programmatic route changes; no bottom padding is needed in drawer mode.
116
+
117
+ `WorkspaceSidebar` is an inset desktop sidebar with a Bits-backed mobile drawer:
118
+
119
+ ```svelte
120
+ <script lang="ts">
121
+ import { Button, WorkspaceSidebar } from '@atmo-dev/kit';
122
+ let open = $state(false);
123
+ </script>
124
+
125
+ <Button class="min-[901px]:hidden" onclick={() => (open = true)}>Open navigation</Button>
126
+ <WorkspaceSidebar label="Workspace navigation" bind:open>
127
+ <strong class="block px-3 py-2">Studio</strong>
128
+ <div class="mt-4 grid gap-1">
129
+ <Button href="/notes" variant="ghost" class="justify-start">Notes</Button>
130
+ <Button href="/settings" variant="ghost" class="justify-start">Settings</Button>
131
+ </div>
132
+ </WorkspaceSidebar>
133
+ ```
134
+
135
+ The drawer is used at 900px and below. Its contents mount once, avoiding duplicate IDs. Escape/outside interaction closes it and focus returns to the trigger; resizing back to desktop moves focus into the visible navigation. Close it after an app-specific navigation action with `open = false`.
136
+
137
+ Both components accept caller classes and styles. Use `--atmo-nav-top` to clear a fixed app toolbar and `--atmo-sidebar-width` for desktop sidebar width. Navigation does not dictate a shared layout for every app.
138
+
139
+ ## Event-app building blocks
140
+
141
+ RSVP now consumes the kit locally. In its `apps/web` and `packages/ui` manifests the dependency is `"@atmo-dev/kit": "link:../../../00-atmo-kit/kit"`. Run `pnpm prepack` here before installing/running the consumer, or `pnpm exec svelte-package --watch` while editing components. The consumer deduplicates Svelte and allows this directory's font assets through Vite's development filesystem guard. No global links or publishing are involved.
142
+
143
+ - `ToggleGroup` / `ToggleGroupItem`: Bits-backed single-choice segmented controls with keyboard navigation and checked/disabled semantics.
144
+ - `Popover`: Bits-backed popover, with a `trigger({ props })` snippet for a real Button/IconButton and theme inheritance across its portal.
145
+ - `InlineEditor`: a small TipTap plain-text editor with `bind:value`, `bind:editor`, accessible labels, single-line titles, plain-text paste, undo/redo, and optional `searchActors` mention autocomplete. The app owns the search adapter; the editor makes no requests without it.
146
+ - `PostComposer`: inline editing plus a grapheme-aware character count. The app owns posting and must validate the final text/byte limit before writing a record. `editorJsonToBlueskyPost(editor.getJSON())` retains selected mention DIDs and generates UTF-8 facets for mentions, links, and hashtags. Manually typed handles remain app-resolved.
147
+ - `LinkCard`, `PostContent`, and `NestedComments`: data-driven post presentation, escaped rich text, nested replies, images, link previews, quotes, and lazily loaded HLS video. `blueskyPostToPostData` normalizes public AppView post views without fetching them or accessing a session. Reply/like links open Bluesky; authenticated mutations remain app-owned.
148
+ - `launchConfetti`: explicitly invoked and lazily loaded; respects reduced motion.
149
+
150
+ Additional Tailwind neutral ramps (`mist`, `mauve`, `olive`, `taupe`) preserve saved event themes. The app default remains zinc + pink; `sand` and `sage` are aliases for saved legacy names. Portaled dialogs and popovers follow subsequent ancestor palette changes. Non-dismissible Modal states also disable the close button.
151
+
152
+ Event-specific cards, calendars, scheduling, record writes and auth stay in RSVP. This is not a compatibility layer for the previous UI library.
153
+
154
+ ## Component examples
155
+
156
+ Open `/components` for interactive, local-only examples:
157
+
158
+ - Edit a link card in a Popover, or open a Popover inside a Modal. Escape closes the innermost layer and restores its trigger.
159
+ - Edit a single-line title with undo/redo, external reset, and disabled state.
160
+ - Compose text with local mention suggestions; inspect serialized UTF-8 facets and preview without publishing. Empty/over-limit submit rules belong to the example app, not the editor.
161
+ - Switch post attachments with ToggleGroup. Arrow keys move focus, Space selects, and disabled choices are skipped.
162
+ - Explore linked text, photos, link cards, unavailable quotes, native video controls, and nested comments.
163
+
164
+ All media and actor suggestions in this page are local. The [silent HLS fixture](static/playground/video/README.md) exercises actual playback, source changes, and cleanup without an external stream. Real media still needs suitable captions or an accessible source/transcript.
165
+
166
+ Tests cover these states in light/dark mode, scoped portals, nested focus restoration, empty and stale mention results, keyboard interaction, 320–1920px layouts, reduced motion, inset-only depth, and automated WCAG A/AA checks. Automated scans do not replace manual assistive-technology testing.
167
+
168
+ ## Login
169
+
170
+ `LoginForm` and `LoginDialog` rebuild the existing Atmo UI login flow with this kit’s controls and Sketchyicons. They support handle validation, optional actor typeahead, selected accounts, recent profiles, permission review, signup confirmation, busy states, errors, and retry.
171
+
172
+ ```svelte
173
+ <script lang="ts">
174
+ import { LoginDialog, createActorSearch, type LoginCallback } from '@atmo-dev/kit';
175
+ let open = $state(false);
176
+
177
+ // Explicit opt-in to public AppView search. Alternatively inject your own provider,
178
+ // or omit searchActors for a plain handle field with no search requests.
179
+ const searchActors = createActorSearch();
180
+ const login: LoginCallback = async (handle, actor, options) => {
181
+ await appLogin(handle, actor, options); // Your existing OAuth/session integration.
182
+ };
183
+ </script>
184
+
185
+ <LoginDialog bind:open {login} {searchActors} signup={() => appSignup()} />
186
+ ```
187
+
188
+ `LoginForm` takes the same login options without `open` / `closeOnSuccess`. It can be placed directly on a page; no enclosing Surface is required. Form attributes and `ref` refer to the form. `LoginDialog` uses `class` for its modal and `formClass` for the inner form.
189
+
190
+ ### Callback contract
191
+
192
+ - `login(handle, actor?, options?)` receives a normalized, syntactically valid handle. An actor is supplied only when the user selected one; a typed handle never produces invented DID/profile data.
193
+ - A thrown error is displayed. Returning `false` leaves the flow open without calling `onSuccess`. Returning `true` or `undefined` accepts the callback result; the dialog closes by default. Set `closeOnSuccess={false}` to keep it open.
194
+ - `onSuccess` means **the callback completed**, not that an OAuth redirect finished or a session was verified. Only your app’s verified session state should determine whether a user is authenticated.
195
+ - `signup` is called only after the separate confirmation step. Signup and OAuth redirects, cancellation, networking, sessions, granted-scope verification, and business rules remain app-owned.
196
+ - Closing/unmounting a pending form ignores its later UI completion. This cannot undo an app callback that has already started a request or redirect.
197
+ - `loginOnSelect` defaults to true. Set it to false to select a suggestion, then submit explicitly. Recent-account selections always start the login flow, with permission review first when configured.
198
+ - `initialHandle` initializes the field. `onStepChange` reports `login`, `permissions`, or `signup`.
199
+ - Customize `title`, `description`, `showApps`, and the `footer(step)` snippet. An optional `children` snippet sits before the handle-submit button. `ActorSearchField` and `RecentAccounts` are also available independently.
200
+
201
+ ### Search and recent profiles
202
+
203
+ Search is debounced, abortable, race-safe, and optional. Results do not validate ownership and failed search never blocks submitting a full handle. The included `createActorSearch({ host, limit, fetch })` helper performs no work until called. Configure hosts in trusted app code, not from untrusted user input.
204
+
205
+ `bind:recentAccounts` lets an app own its history. Persistence is **off by default**. Set `persistRecentAccounts` to use origin-local storage, optionally with `recentAccountOptions={{ storage, key, max }}`. Storage failures are non-fatal; only profile display fields and a last-used timestamp are retained, never credentials or tokens. Forgetting a recent profile does not log out an account.
206
+
207
+ For explicit persistence outside the component, use `loadRecentAccounts`, `writeRecentAccounts`, `saveRecentAccount`, and `removeRecentAccount`. To import old Fox UI history without overwriting its format, read `loadRecentAccounts({ key: 'recent-logins' })` once and save those profiles under the new default key. No automatic migration occurs.
208
+
209
+ ### Permission review
210
+
211
+ Pass `permissions` to add a review step before invoking `login`. A permission has a unique `id`, `label`, `description`, `scopes`, optional Sketchyicons `icon`, `required`, and `defaultSelected`. Required items cannot be deselected. Optional items default to selected, matching the previous component; explicitly set `defaultSelected: false` when they should start off.
212
+
213
+ The callback receives `{ permissionIds, scopes }`; scopes are deduplicated with `baseScopes` (default `['atproto']`). These are requested permissions, **not a consent receipt or proof of granted OAuth access**. Your OAuth adapter must apply the requested scopes and verify what was granted. Without a review step, the third argument is omitted and the app retains its existing scope policy.
214
+
215
+ The service-logo strip is static and decorative. These are existing brand marks, separate from the handdrawn UI icons; see `src/lib/components/login/brands/README.md`. Login lives with the other components in `src/lib/components/login/` and shares the package's root MIT `LICENSE`.
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils.js';
4
+ import { UserRound } from '@sketchyicons/svelte';
5
+ import Icon from './Icon.svelte';
6
+ let {
7
+ name,
8
+ src,
9
+ size = 36,
10
+ class: className,
11
+ ...rest
12
+ }: Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
13
+ name: string;
14
+ src?: string;
15
+ size?: number;
16
+ } = $props();
17
+ let failedSrc = $state<string>();
18
+ </script>
19
+
20
+ <span
21
+ {...rest}
22
+ class={cn(
23
+ 'inline-grid size-(--ak-avatar-size) shrink-0 place-items-center overflow-hidden rounded-[45%_49%_44%_48%] border border-base-100 bg-accent-soft text-action dark:border-base-800',
24
+ className
25
+ )}
26
+ style:--ak-avatar-size={`${size}px`}
27
+ role="img"
28
+ aria-label={name}
29
+ >
30
+ {#if src && src !== failedSrc}
31
+ <img {src} alt="" class="block size-full object-cover" onerror={() => (failedSrc = src)} />
32
+ {:else}
33
+ <Icon img={UserRound} class="size-3/5" />
34
+ {/if}
35
+ </span>
@@ -0,0 +1,9 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
3
+ name: string;
4
+ src?: string;
5
+ size?: number;
6
+ };
7
+ declare const Avatar: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type Avatar = ReturnType<typeof Avatar>;
9
+ export default Avatar;
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils.js';
4
+ let {
5
+ class: className,
6
+ variant = 'soft',
7
+ children,
8
+ ...rest
9
+ }: HTMLAttributes<HTMLSpanElement> & { variant?: 'soft' | 'outline' } = $props();
10
+ </script>
11
+
12
+ <span
13
+ {...rest}
14
+ class={cn(
15
+ 'inline-flex w-fit items-center gap-1.5 rounded-md border px-2 py-0.75 text-[.6875rem] leading-normal font-medium',
16
+ variant === 'soft'
17
+ ? 'border-action/20 bg-accent-soft text-action'
18
+ : 'border-control-line bg-transparent text-muted',
19
+ className
20
+ )}>{@render children?.()}</span
21
+ >
@@ -0,0 +1,7 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type $$ComponentProps = HTMLAttributes<HTMLSpanElement> & {
3
+ variant?: 'soft' | 'outline';
4
+ };
5
+ declare const Badge: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Badge = ReturnType<typeof Badge>;
7
+ export default Badge;
@@ -0,0 +1,94 @@
1
+ <script lang="ts" module>
2
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
3
+ export type ButtonProps = Omit<HTMLButtonAttributes, 'onclick'> &
4
+ Omit<HTMLAnchorAttributes, 'type' | 'onclick'> & {
5
+ variant?: 'primary' | 'secondary' | 'ghost' | 'link';
6
+ size?: 'sm' | 'md' | 'lg' | 'icon';
7
+ loading?: boolean;
8
+ ref?: HTMLButtonElement | HTMLAnchorElement | null;
9
+ onclick?: (event: MouseEvent & { currentTarget: EventTarget & HTMLElement }) => void;
10
+ };
11
+ const variants = {
12
+ primary: 'bg-accent-400 text-base-950 hover:bg-accent-300 active:bg-accent-400',
13
+ secondary:
14
+ 'border-line bg-surface text-ink hover:border-control-line hover:bg-recess active:bg-recess',
15
+ ghost: 'bg-transparent text-muted hover:bg-recess hover:text-ink active:bg-recess',
16
+ link: 'min-h-0 rounded px-0 py-1 text-action hover:underline underline-offset-4'
17
+ };
18
+ const sizes = {
19
+ sm: 'min-h-8.5 gap-1.5 px-3 py-1.5 text-xs',
20
+ md: 'min-h-10 px-3.5 py-2 text-[.8125rem]',
21
+ lg: 'min-h-11 px-5 py-2.5 text-sm',
22
+ icon: 'size-10 p-0'
23
+ };
24
+ </script>
25
+
26
+ <script lang="ts">
27
+ import { cn } from '../utils.js';
28
+ import { focus } from '../styles.js';
29
+ import { LoaderCircle } from '@sketchyicons/svelte';
30
+ import Icon from './Icon.svelte';
31
+ let {
32
+ class: className,
33
+ variant = 'primary',
34
+ size = 'md',
35
+ loading = false,
36
+ disabled = false,
37
+ ref = $bindable(null),
38
+ href,
39
+ type = 'button',
40
+ onclick,
41
+ children,
42
+ ...rest
43
+ }: ButtonProps = $props();
44
+ let classes = $derived(
45
+ cn(
46
+ 'inline-flex shrink-0 cursor-pointer touch-manipulation items-center justify-center gap-2 rounded-(--atmo-radius-button) border border-transparent font-medium leading-snug no-underline transition-colors duration-(--atmo-duration) motion-reduce:transition-none disabled:pointer-events-none disabled:opacity-45 aria-disabled:pointer-events-none aria-disabled:opacity-45 aria-busy:opacity-100 [&_svg]:shrink-0',
47
+ focus,
48
+ sizes[size],
49
+ variants[variant],
50
+ className
51
+ )
52
+ );
53
+ </script>
54
+
55
+ {#snippet content()}
56
+ {#if loading}<Icon img={LoaderCircle} size={16} class="motion-safe:animate-spin" />{/if}
57
+ {@render children?.()}
58
+ {/snippet}
59
+ {#if href !== undefined}
60
+ <a
61
+ {...rest}
62
+ bind:this={ref}
63
+ href={disabled || loading ? undefined : href}
64
+ role={disabled || loading ? 'link' : undefined}
65
+ class={classes}
66
+ data-slot="button"
67
+ data-variant={variant}
68
+ data-size={size}
69
+ aria-disabled={disabled || loading ? true : rest['aria-disabled']}
70
+ aria-busy={loading || rest['aria-busy']}
71
+ tabindex={disabled || loading ? -1 : rest.tabindex}
72
+ onclick={(event) => {
73
+ if (disabled || loading) {
74
+ event.preventDefault();
75
+ event.stopImmediatePropagation();
76
+ return;
77
+ }
78
+ onclick?.(event);
79
+ }}>{@render content()}</a
80
+ >
81
+ {:else}
82
+ <button
83
+ {...rest}
84
+ bind:this={ref}
85
+ {type}
86
+ disabled={disabled || loading}
87
+ class={classes}
88
+ data-slot="button"
89
+ data-variant={variant}
90
+ data-size={size}
91
+ aria-busy={loading || rest['aria-busy']}
92
+ {onclick}>{@render content()}</button
93
+ >
94
+ {/if}
@@ -0,0 +1,13 @@
1
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
2
+ export type ButtonProps = Omit<HTMLButtonAttributes, 'onclick'> & Omit<HTMLAnchorAttributes, 'type' | 'onclick'> & {
3
+ variant?: 'primary' | 'secondary' | 'ghost' | 'link';
4
+ size?: 'sm' | 'md' | 'lg' | 'icon';
5
+ loading?: boolean;
6
+ ref?: HTMLButtonElement | HTMLAnchorElement | null;
7
+ onclick?: (event: MouseEvent & {
8
+ currentTarget: EventTarget & HTMLElement;
9
+ }) => void;
10
+ };
11
+ declare const Button: import("svelte").Component<ButtonProps, {}, "ref">;
12
+ type Button = ReturnType<typeof Button>;
13
+ export default Button;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils.js';
4
+ import { focus } from '../styles.js';
5
+ let {
6
+ class: className,
7
+ checked = $bindable(false),
8
+ indeterminate = $bindable(false),
9
+ ref = $bindable(null),
10
+ ...rest
11
+ }: Omit<HTMLInputAttributes, 'type'> & {
12
+ indeterminate?: boolean;
13
+ ref?: HTMLInputElement | null;
14
+ } = $props();
15
+ </script>
16
+
17
+ <input
18
+ {...rest}
19
+ type="checkbox"
20
+ bind:checked
21
+ bind:indeterminate
22
+ bind:this={ref}
23
+ data-slot="checkbox"
24
+ class={cn(
25
+ 'm-0 inline-block size-4.5 shrink-0 cursor-pointer appearance-auto align-middle accent-action focus:ring-0 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-45',
26
+ focus,
27
+ className
28
+ )}
29
+ />
@@ -0,0 +1,8 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ type $$ComponentProps = Omit<HTMLInputAttributes, 'type'> & {
3
+ indeterminate?: boolean;
4
+ ref?: HTMLInputElement | null;
5
+ };
6
+ declare const Checkbox: import("svelte").Component<$$ComponentProps, {}, "ref" | "checked" | "indeterminate">;
7
+ type Checkbox = ReturnType<typeof Checkbox>;
8
+ export default Checkbox;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils.js';
4
+ let {
5
+ class: className,
6
+ level = 1,
7
+ ref = $bindable(null),
8
+ children,
9
+ ...rest
10
+ }: HTMLAttributes<HTMLHeadingElement> & {
11
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
12
+ ref?: HTMLHeadingElement | null;
13
+ } = $props();
14
+ </script>
15
+
16
+ <svelte:element
17
+ this={`h${level}`}
18
+ {...rest}
19
+ bind:this={ref}
20
+ class={cn(
21
+ 'm-0 text-[clamp(2rem,4vw,2.75rem)] leading-[1.15] font-medium tracking-[-.035em] text-ink',
22
+ className
23
+ )}>{@render children?.()}</svelte:element
24
+ >
@@ -0,0 +1,8 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type $$ComponentProps = HTMLAttributes<HTMLHeadingElement> & {
3
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
4
+ ref?: HTMLHeadingElement | null;
5
+ };
6
+ declare const Heading: import("svelte").Component<$$ComponentProps, {}, "ref">;
7
+ type Heading = ReturnType<typeof Heading>;
8
+ export default Heading;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { SVGAttributes } from 'svelte/elements';
3
+ import { SketchyIcon, type SketchyIconProps } from '@sketchyicons/svelte';
4
+ let {
5
+ img,
6
+ size = 20,
7
+ strokeWidth = 1.75,
8
+ label,
9
+ 'aria-label': ariaLabel,
10
+ ...rest
11
+ }: SVGAttributes<SVGSVGElement> & SketchyIconProps & { label?: string } = $props();
12
+ let accessibleLabel = $derived(label ?? ariaLabel);
13
+ </script>
14
+
15
+ <SketchyIcon
16
+ {img}
17
+ {size}
18
+ {strokeWidth}
19
+ {...rest}
20
+ role={accessibleLabel ? 'img' : rest.role}
21
+ aria-label={accessibleLabel}
22
+ aria-hidden={accessibleLabel ? undefined : (rest['aria-hidden'] ?? true)}
23
+ focusable="false"
24
+ />
@@ -0,0 +1,8 @@
1
+ import type { SVGAttributes } from 'svelte/elements';
2
+ import { type SketchyIconProps } from '@sketchyicons/svelte';
3
+ type $$ComponentProps = SVGAttributes<SVGSVGElement> & SketchyIconProps & {
4
+ label?: string;
5
+ };
6
+ declare const Icon: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type Icon = ReturnType<typeof Icon>;
8
+ export default Icon;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import Button, { type ButtonProps } from './Button.svelte';
3
+ import { cn } from '../utils.js';
4
+ let {
5
+ label,
6
+ class: className,
7
+ variant = 'ghost',
8
+ size = 'md',
9
+ ref = $bindable(null),
10
+ children,
11
+ ...rest
12
+ }: Omit<ButtonProps, 'size'> & { label: string; size?: 'sm' | 'md' | 'lg' } = $props();
13
+ </script>
14
+
15
+ <Button
16
+ {...rest}
17
+ bind:ref
18
+ {variant}
19
+ size="icon"
20
+ aria-label={label}
21
+ title={rest.title ?? label}
22
+ class={cn(size === 'sm' && 'size-8', size === 'lg' && 'size-12', className)}
23
+ >{@render children?.()}</Button
24
+ >
@@ -0,0 +1,8 @@
1
+ import { type ButtonProps } from './Button.svelte';
2
+ type $$ComponentProps = Omit<ButtonProps, 'size'> & {
3
+ label: string;
4
+ size?: 'sm' | 'md' | 'lg';
5
+ };
6
+ declare const IconButton: import("svelte").Component<$$ComponentProps, {}, "ref">;
7
+ type IconButton = ReturnType<typeof IconButton>;
8
+ export default IconButton;