@noahbaumgartner/svelte-ui 0.0.3 → 0.0.5

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 (36) hide show
  1. package/README.md +69 -38
  2. package/dist/components/accordion/Accordion.svelte +34 -0
  3. package/dist/components/accordion/Accordion.svelte.d.ts +11 -0
  4. package/dist/components/accordion/AccordionItem.svelte +106 -0
  5. package/dist/components/accordion/AccordionItem.svelte.d.ts +13 -0
  6. package/dist/components/accordion/context.d.ts +5 -0
  7. package/dist/components/accordion/context.js +2 -0
  8. package/dist/components/avatar-group/AvatarGroup.svelte +76 -0
  9. package/dist/components/avatar-group/AvatarGroup.svelte.d.ts +15 -0
  10. package/dist/components/drawer/Drawer.svelte +368 -0
  11. package/dist/components/drawer/Drawer.svelte.d.ts +23 -0
  12. package/dist/components/empty-state/EmptyState.svelte +86 -0
  13. package/dist/components/empty-state/EmptyState.svelte.d.ts +16 -0
  14. package/dist/components/entry/Entry.svelte +83 -0
  15. package/dist/components/entry/Entry.svelte.d.ts +15 -0
  16. package/dist/components/footer/Footer.svelte +89 -0
  17. package/dist/components/footer/Footer.svelte.d.ts +13 -0
  18. package/dist/components/footer/FooterColumn.svelte +34 -0
  19. package/dist/components/footer/FooterColumn.svelte.d.ts +10 -0
  20. package/dist/components/mesh-gradient/MeshGradient.svelte +86 -0
  21. package/dist/components/mesh-gradient/MeshGradient.svelte.d.ts +12 -0
  22. package/dist/components/number-stepper/NumberStepper.svelte +82 -0
  23. package/dist/components/number-stepper/NumberStepper.svelte.d.ts +16 -0
  24. package/dist/components/preview-card/PreviewCard.svelte +166 -0
  25. package/dist/components/preview-card/PreviewCard.svelte.d.ts +20 -0
  26. package/dist/components/progress/Progress.svelte +88 -0
  27. package/dist/components/progress/Progress.svelte.d.ts +12 -0
  28. package/dist/components/select/Select.svelte +391 -0
  29. package/dist/components/select/Select.svelte.d.ts +39 -0
  30. package/dist/components/sidebar/Sidebar.svelte +37 -0
  31. package/dist/components/sidebar/SidebarHeader.svelte +26 -2
  32. package/dist/components/tabs/Tabs.svelte +280 -0
  33. package/dist/components/tabs/Tabs.svelte.d.ts +48 -0
  34. package/dist/index.d.ts +15 -0
  35. package/dist/index.js +14 -0
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -1,65 +1,96 @@
1
- # Svelte library
1
+ # svelte-ui
2
2
 
3
- Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv).
4
-
5
- Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
6
-
7
- ## Creating a project
8
-
9
- If you're seeing this, you've probably already done this step. Congrats!
3
+ A minimal, monochrome component library for Svelte 5, built with runes, TypeScript and plain CSS custom properties.
10
4
 
11
5
  ```sh
12
- # create a new project in the current directory
13
- npx sv create
14
-
15
- # create a new project in my-app
16
- npx sv create my-app
6
+ npm install @noahbaumgartner/svelte-ui
17
7
  ```
18
8
 
19
- To recreate this project with the same configuration:
9
+ ## Usage
20
10
 
21
- ```sh
22
- # recreate this project
23
- npx sv@0.17.1 create --template library --types ts --add storybook vitest="usages:unit,component" eslint prettier --install npm svelte-ui
24
- ```
11
+ Import the design tokens once, at the root of your app (e.g. `src/routes/+layout.svelte`):
25
12
 
26
- ## Developing
13
+ ```svelte
14
+ <script lang="ts">
15
+ import '@noahbaumgartner/svelte-ui/tokens.css';
27
16
 
28
- Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
17
+ let { children } = $props();
18
+ </script>
29
19
 
30
- ```sh
31
- npm run dev
20
+ {@render children()}
21
+ ```
32
22
 
33
- # or start the server and open the app in a new browser tab
34
- npm run dev -- --open
23
+ Then use the components:
24
+
25
+ ```svelte
26
+ <script lang="ts">
27
+ import { Button, Card, CardHeader, CardTitle, CardContent } from '@noahbaumgartner/svelte-ui';
28
+ import { Plus } from '@noahbaumgartner/svelte-ui/icons';
29
+ </script>
30
+
31
+ <Card>
32
+ <CardHeader>
33
+ <CardTitle>Projects</CardTitle>
34
+ </CardHeader>
35
+ <CardContent>
36
+ <Button icon={Plus} variant="primary">New project</Button>
37
+ </CardContent>
38
+ </Card>
35
39
  ```
36
40
 
37
- Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
41
+ ### Icons
38
42
 
39
- ## Building
43
+ [Lucide](https://lucide.dev) icons are re-exported from `@noahbaumgartner/svelte-ui/icons`, so you don't need a separate `@lucide/svelte` install. They live on their own entry point because some icon names (`Link`, `Image`, …) collide with component names.
40
44
 
41
- To build your library:
45
+ ### Theming
42
46
 
43
- ```sh
44
- npm pack
47
+ All colours are derived from two variables, `--base` and `--ink`, mixed into a grey scale. Light and dark mode follow `prefers-color-scheme` by default; force one with a `data-theme` attribute on `<html>`:
48
+
49
+ ```html
50
+ <html data-theme="dark"></html>
45
51
  ```
46
52
 
47
- To create a production version of your showcase app:
53
+ Set `--accent` to colour checked controls, primary buttons and focus rings. It defaults to `--ink`, and the foreground colour on top of it is picked automatically for contrast:
48
54
 
49
- ```sh
50
- npm run build
55
+ ```css
56
+ :root {
57
+ --accent: #2563eb;
58
+ }
51
59
  ```
52
60
 
53
- You can preview the production build with `npm run preview`.
61
+ See [`src/lib/styles/tokens.css`](src/lib/styles/tokens.css) for every token.
54
62
 
55
- > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
63
+ ## Components
56
64
 
57
- ## Publishing
65
+ | Category | Components |
66
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
67
+ | Actions | `Button`, `ButtonGroup`, `Toggle`, `Link` |
68
+ | Forms | `Input`, `InputGroup`, `Textarea`, `Select`, `NumberStepper`, `Checkbox`, `RadioGroup`, `Switch`, `Label`, `Field` family |
69
+ | Pickers | `Calendar`, `TimePicker`, `ColorPicker` |
70
+ | Overlays | `Dialog`, `AlertDialog`, `Drawer`, `Popover`, `Tooltip`, `ContextMenu`, `Toaster` + `toast()` |
71
+ | Navigation | `Navbar`, `NavigationMenu`, `Sidebar` family, `Breadcrumb`, `Tabs` |
72
+ | Display | `Card` family, `Table`, `Accordion`, `Avatar`, `AvatarGroup`, `Badge`, `EmptyState`, `Kbd`, `Progress`, `Separator`, `Skeleton`, `Spinner` |
58
73
 
59
- Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
74
+ Every component is documented in Storybook, with one story per prop.
60
75
 
61
- To publish your library to [npm](https://www.npmjs.com):
76
+ ## Development
62
77
 
63
78
  ```sh
64
- npm publish
79
+ npm install
80
+ npm run storybook # component workbench on http://localhost:6006
65
81
  ```
82
+
83
+ | Script | Description |
84
+ | ------------------- | ------------------------------------------------ |
85
+ | `npm run storybook` | Start Storybook |
86
+ | `npm run check` | Type-check with `svelte-check` |
87
+ | `npm run test` | Run unit and Storybook tests with Vitest |
88
+ | `npm run lint` | Check formatting (Prettier) and lint (ESLint) |
89
+ | `npm run format` | Format all files |
90
+ | `npm run build` | Build the package into `dist/` and run `publint` |
91
+
92
+ Components live in `src/lib/components/<name>/`, each next to its `.stories.svelte` file, and are exported from [`src/lib/index.ts`](src/lib/index.ts).
93
+
94
+ ## Releasing
95
+
96
+ Publishing a GitHub release triggers [`.github/workflows/publish.yml`](.github/workflows/publish.yml), which sets the package version from the release tag (e.g. `v0.2.0` → `0.2.0`), type-checks, and stages the publish on npm with provenance. The staged version then has to be approved with 2FA (`npm stage approve <stage-id>` or on npmjs.com).
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { setAccordionContext } from './context.js';
5
+
6
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
7
+ /** Lets several items be open at once. By default opening one closes the others. */
8
+ multiple?: boolean;
9
+ /** AccordionItems. */
10
+ children: Snippet;
11
+ };
12
+
13
+ let { multiple = false, class: className, children, ...rest }: Props = $props();
14
+
15
+ const name = $props.id();
16
+
17
+ setAccordionContext({
18
+ get name() {
19
+ return multiple ? undefined : name;
20
+ }
21
+ });
22
+ </script>
23
+
24
+ <div {...rest} class={['accordion', className]}>
25
+ {@render children()}
26
+ </div>
27
+
28
+ <style>
29
+ .accordion {
30
+ display: flex;
31
+ flex-direction: column;
32
+ width: 100%;
33
+ }
34
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /** Lets several items be open at once. By default opening one closes the others. */
5
+ multiple?: boolean;
6
+ /** AccordionItems. */
7
+ children: Snippet;
8
+ };
9
+ declare const Accordion: import("svelte").Component<Props, {}, "">;
10
+ type Accordion = ReturnType<typeof Accordion>;
11
+ export default Accordion;
@@ -0,0 +1,106 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLDetailsAttributes } from 'svelte/elements';
4
+ import { ChevronDown } from '@lucide/svelte';
5
+ import { getAccordionContext } from './context.js';
6
+
7
+ type Props = Omit<HTMLDetailsAttributes, 'children' | 'title' | 'name'> & {
8
+ /** Whether the item is expanded. Bindable. */
9
+ open?: boolean;
10
+ /** The always visible heading that toggles the item. */
11
+ title: string;
12
+ /** The content shown while the item is open. */
13
+ children: Snippet;
14
+ };
15
+
16
+ let { open = $bindable(false), title, class: className, children, ...rest }: Props = $props();
17
+
18
+ const accordion = getAccordionContext();
19
+ </script>
20
+
21
+ <details {...rest} bind:open name={accordion.name} class={['accordion-item', className]}>
22
+ <summary class="accordion-trigger">
23
+ <span class="accordion-title">{title}</span>
24
+ <ChevronDown class="accordion-chevron" aria-hidden="true" />
25
+ </summary>
26
+ <div class="accordion-content">{@render children()}</div>
27
+ </details>
28
+
29
+ <style>
30
+ .accordion-item {
31
+ font-size: 13px;
32
+ color: var(--color-text);
33
+ border-bottom: 1px solid var(--color-border);
34
+ /* Lets the height animate to and from `auto` where the browser supports it */
35
+ interpolate-size: allow-keywords;
36
+ }
37
+
38
+ .accordion-item:last-child {
39
+ border-bottom: 0;
40
+ }
41
+
42
+ .accordion-trigger {
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: space-between;
46
+ gap: 12px;
47
+ padding: 14px 0;
48
+ font-weight: 500;
49
+ list-style: none;
50
+ border-radius: 6px;
51
+ outline: none;
52
+ cursor: pointer;
53
+ user-select: none;
54
+ }
55
+
56
+ .accordion-trigger::-webkit-details-marker {
57
+ display: none;
58
+ }
59
+
60
+ .accordion-trigger:hover .accordion-title {
61
+ text-decoration: underline;
62
+ text-underline-offset: 3px;
63
+ }
64
+
65
+ .accordion-trigger:focus-visible {
66
+ outline: 2px solid var(--color-accent);
67
+ outline-offset: 2px;
68
+ }
69
+
70
+ .accordion-trigger :global(.accordion-chevron) {
71
+ width: 16px;
72
+ height: 16px;
73
+ flex-shrink: 0;
74
+ color: var(--color-text-muted);
75
+ transition: rotate 200ms ease;
76
+ }
77
+
78
+ .accordion-item[open] > .accordion-trigger :global(.accordion-chevron) {
79
+ rotate: 180deg;
80
+ }
81
+
82
+ .accordion-item::details-content {
83
+ block-size: 0;
84
+ overflow: clip;
85
+ transition:
86
+ block-size 200ms ease,
87
+ content-visibility 200ms allow-discrete;
88
+ }
89
+
90
+ .accordion-item[open]::details-content {
91
+ block-size: auto;
92
+ }
93
+
94
+ .accordion-content {
95
+ padding-bottom: 14px;
96
+ line-height: 1.5;
97
+ color: var(--color-text-secondary);
98
+ }
99
+
100
+ @media (prefers-reduced-motion: reduce) {
101
+ .accordion-item::details-content,
102
+ .accordion-trigger :global(.accordion-chevron) {
103
+ transition: none;
104
+ }
105
+ }
106
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLDetailsAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLDetailsAttributes, 'children' | 'title' | 'name'> & {
4
+ /** Whether the item is expanded. Bindable. */
5
+ open?: boolean;
6
+ /** The always visible heading that toggles the item. */
7
+ title: string;
8
+ /** The content shown while the item is open. */
9
+ children: Snippet;
10
+ };
11
+ declare const AccordionItem: import("svelte").Component<Props, {}, "open">;
12
+ type AccordionItem = ReturnType<typeof AccordionItem>;
13
+ export default AccordionItem;
@@ -0,0 +1,5 @@
1
+ export type AccordionContext = {
2
+ /** Shared `name` of the items' <details>, so the browser keeps only one open. Undefined with `multiple`. */
3
+ readonly name: string | undefined;
4
+ };
5
+ export declare const getAccordionContext: () => AccordionContext, setAccordionContext: (context: AccordionContext) => AccordionContext;
@@ -0,0 +1,2 @@
1
+ import { createContext } from 'svelte';
2
+ export const [getAccordionContext, setAccordionContext] = createContext();
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
6
+ /** Accessible name for the group, e.g. "Members". */
7
+ label?: string;
8
+ /** How many more there are than the Avatars shown; rendered as "+3" at the end. */
9
+ more?: number;
10
+ /** Size of the "+3"; match it to the Avatars' `size`. */
11
+ size?: 'sm' | 'md' | 'lg';
12
+ /** Avatars, overlapping each other. */
13
+ children: Snippet;
14
+ };
15
+
16
+ let { label, more = 0, size = 'md', class: className, children, ...rest }: Props = $props();
17
+ </script>
18
+
19
+ <div {...rest} role="group" aria-label={label} class={['avatar-group', className]}>
20
+ {@render children()}
21
+ {#if more > 0}
22
+ <span class={['avatar-group-more', `avatar-group-more--${size}`]}>+{more}</span>
23
+ {/if}
24
+ </div>
25
+
26
+ <style>
27
+ .avatar-group {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ }
31
+
32
+ /* The ring in the page colour separates the overlapping avatars */
33
+ .avatar-group > :global(*) {
34
+ box-shadow: 0 0 0 2px var(--color-bg);
35
+ }
36
+
37
+ .avatar-group > :global(:not(:first-child)) {
38
+ margin-inline-start: -6px;
39
+ }
40
+
41
+ .avatar-group-more {
42
+ display: inline-flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ box-sizing: border-box;
46
+ flex-shrink: 0;
47
+ padding: 0 6px;
48
+ font-weight: 500;
49
+ font-variant-numeric: tabular-nums;
50
+ color: var(--color-text-secondary);
51
+ background-color: var(--color-surface);
52
+ user-select: none;
53
+ }
54
+
55
+ /* Same sizes and corners as Avatar */
56
+ .avatar-group-more--sm {
57
+ min-width: 24px;
58
+ height: 24px;
59
+ border-radius: 8px;
60
+ font-size: 10px;
61
+ }
62
+
63
+ .avatar-group-more--md {
64
+ min-width: 32px;
65
+ height: 32px;
66
+ border-radius: 10px;
67
+ font-size: 12px;
68
+ }
69
+
70
+ .avatar-group-more--lg {
71
+ min-width: 40px;
72
+ height: 40px;
73
+ border-radius: 12px;
74
+ font-size: 14px;
75
+ }
76
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /** Accessible name for the group, e.g. "Members". */
5
+ label?: string;
6
+ /** How many more there are than the Avatars shown; rendered as "+3" at the end. */
7
+ more?: number;
8
+ /** Size of the "+3"; match it to the Avatars' `size`. */
9
+ size?: 'sm' | 'md' | 'lg';
10
+ /** Avatars, overlapping each other. */
11
+ children: Snippet;
12
+ };
13
+ declare const AvatarGroup: import("svelte").Component<Props, {}, "">;
14
+ type AvatarGroup = ReturnType<typeof AvatarGroup>;
15
+ export default AvatarGroup;