@marianmeres/stuic 2.5.0 → 2.6.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.
@@ -0,0 +1,123 @@
1
+ # SlidingPanels
2
+
3
+ A two-panel layout with smooth sliding transitions between panels. Useful for master-detail views, wizard flows, or progressive disclosure.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `initial` | `"A" \| "B"` | `"A"` | Initially active panel |
10
+ | `duration` | `number` | `300` | Transition duration (ms) |
11
+ | `destroyInactive` | `boolean` | `true` | Unmount inactive panel from DOM |
12
+ | `class` | `string` | - | CSS for container |
13
+ | `panelA` | `Snippet` | - | Content for Panel A (required) |
14
+ | `panelB` | `Snippet` | - | Content for Panel B |
15
+
16
+ ## Snippet Props
17
+
18
+ Both panel snippets receive:
19
+
20
+ | Prop | Type | Description |
21
+ |------|------|-------------|
22
+ | `show` | `(panel: "A" \| "B") => Promise<boolean>` | Function to switch panels |
23
+ | `active` | `"A" \| "B"` | Currently active panel |
24
+ | `inTransition` | `boolean` | Whether transition is in progress |
25
+
26
+ ## Methods
27
+
28
+ | Method | Description |
29
+ |--------|-------------|
30
+ | `show(panel)` | Switch to specified panel, returns `false` if already active or transitioning |
31
+ | `current()` | Returns object with `active` and `isTransitioning` getters |
32
+
33
+ ## Usage
34
+
35
+ ### Basic Two-Panel Layout
36
+
37
+ ```svelte
38
+ <script lang="ts">
39
+ import { SlidingPanels } from 'stuic';
40
+ </script>
41
+
42
+ <div class="h-96">
43
+ <SlidingPanels>
44
+ {#snippet panelA({ show })}
45
+ <div class="p-4">
46
+ <h2>Panel A</h2>
47
+ <button onclick={() => show('B')}>Go to Panel B</button>
48
+ </div>
49
+ {/snippet}
50
+
51
+ {#snippet panelB({ show })}
52
+ <div class="p-4">
53
+ <h2>Panel B</h2>
54
+ <button onclick={() => show('A')}>Back to Panel A</button>
55
+ </div>
56
+ {/snippet}
57
+ </SlidingPanels>
58
+ </div>
59
+ ```
60
+
61
+ ### Master-Detail Pattern
62
+
63
+ ```svelte
64
+ <script lang="ts">
65
+ let selectedItem = $state(null);
66
+ </script>
67
+
68
+ <SlidingPanels duration={200}>
69
+ {#snippet panelA({ show })}
70
+ <ul>
71
+ {#each items as item}
72
+ <li>
73
+ <button onclick={() => {
74
+ selectedItem = item;
75
+ show('B');
76
+ }}>
77
+ {item.name}
78
+ </button>
79
+ </li>
80
+ {/each}
81
+ </ul>
82
+ {/snippet}
83
+
84
+ {#snippet panelB({ show })}
85
+ {#if selectedItem}
86
+ <article>
87
+ <button onclick={() => show('A')}>← Back</button>
88
+ <h1>{selectedItem.name}</h1>
89
+ <p>{selectedItem.description}</p>
90
+ </article>
91
+ {/if}
92
+ {/snippet}
93
+ </SlidingPanels>
94
+ ```
95
+
96
+ ### Keep Inactive Panel in DOM
97
+
98
+ ```svelte
99
+ <SlidingPanels destroyInactive={false}>
100
+ {#snippet panelA({ show })}
101
+ <!-- Panel A content persists when switching -->
102
+ {/snippet}
103
+ {#snippet panelB({ show })}
104
+ <!-- Panel B content persists when switching -->
105
+ {/snippet}
106
+ </SlidingPanels>
107
+ ```
108
+
109
+ ### With Component Reference
110
+
111
+ ```svelte
112
+ <script lang="ts">
113
+ let panels: SlidingPanels;
114
+ </script>
115
+
116
+ <SlidingPanels bind:this={panels}>
117
+ <!-- panels -->
118
+ </SlidingPanels>
119
+
120
+ <button onclick={() => panels.show('B')}>
121
+ Go to B
122
+ </button>
123
+ ```
@@ -0,0 +1,80 @@
1
+ # Spinner
2
+
3
+ A customizable loading spinner with rotating segments. Pure CSS animation with configurable appearance.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `duration` | `number` | `750` | One full rotation duration (ms) |
10
+ | `count` | `number` | `8` | Number of segments/hands (3-12) |
11
+ | `thickness` | `"thin" \| "normal" \| "thick"` | `"thick"` | Segment width |
12
+ | `height` | `"short" \| "normal" \| "tall"` | `"normal"` | Segment length |
13
+ | `direction` | `"cw" \| "ccw"` | `"cw"` | Rotation direction (clockwise/counter-clockwise) |
14
+ | `class` | `string` | - | CSS classes for sizing |
15
+
16
+ ## Usage
17
+
18
+ ### Basic Spinner
19
+
20
+ ```svelte
21
+ <script lang="ts">
22
+ import { Spinner } from 'stuic';
23
+ </script>
24
+
25
+ <Spinner />
26
+ ```
27
+
28
+ ### Different Sizes
29
+
30
+ ```svelte
31
+ <Spinner class="w-4" />
32
+ <Spinner class="w-6" />
33
+ <Spinner class="w-8" />
34
+ <Spinner class="w-12" />
35
+ ```
36
+
37
+ ### Customized Appearance
38
+
39
+ ```svelte
40
+ <!-- More segments, thinner -->
41
+ <Spinner count={12} thickness="thin" />
42
+
43
+ <!-- Fewer segments, thicker, taller -->
44
+ <Spinner count={4} thickness="thick" height="tall" />
45
+ ```
46
+
47
+ ### Slower/Faster Animation
48
+
49
+ ```svelte
50
+ <Spinner duration={500} /> <!-- Faster -->
51
+ <Spinner duration={1500} /> <!-- Slower -->
52
+ ```
53
+
54
+ ### Counter-Clockwise
55
+
56
+ ```svelte
57
+ <Spinner direction="ccw" />
58
+ ```
59
+
60
+ ### With Custom Color
61
+
62
+ ```svelte
63
+ <Spinner class="w-8 text-blue-500" />
64
+ <Spinner class="w-8 text-green-500" />
65
+ ```
66
+
67
+ ### Loading Button
68
+
69
+ ```svelte
70
+ <script lang="ts">
71
+ let loading = $state(false);
72
+ </script>
73
+
74
+ <button disabled={loading}>
75
+ {#if loading}
76
+ <Spinner class="w-4 mr-2" />
77
+ {/if}
78
+ Submit
79
+ </button>
80
+ ```
@@ -0,0 +1,113 @@
1
+ # Switch
2
+
3
+ A toggle switch component with size variants, keyboard support, and optional async validation.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `checked` | `boolean` | - | Toggle state (bindable) |
10
+ | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl" \| string` | `"md"` | Switch size |
11
+ | `name` | `string` | - | Form field name for hidden checkbox |
12
+ | `label` | `string` | - | Screen reader label (visually hidden) |
13
+ | `required` | `boolean` | `false` | Mark as required |
14
+ | `disabled` | `boolean` | `false` | Disable toggle |
15
+ | `tabindex` | `number` | `0` | Tab index |
16
+ | `preHook` | `(current: boolean) => Promise<false \| any>` | - | Async validation (return `false` to prevent) |
17
+ | `validate` | `boolean \| ValidateOptions` | - | Enable validation |
18
+ | `class` | `string` | - | CSS for switch container |
19
+ | `dotClass` | `string` | - | CSS for toggle knob |
20
+ | `button` | `HTMLButtonElement` | - | Button element reference (bindable) |
21
+
22
+ ## Snippets
23
+
24
+ | Snippet | Description |
25
+ |---------|-------------|
26
+ | `on` | Content inside knob when checked |
27
+ | `off` | Content inside knob when unchecked |
28
+
29
+ ## Usage
30
+
31
+ ### Basic Toggle
32
+
33
+ ```svelte
34
+ <script lang="ts">
35
+ import { Switch } from 'stuic';
36
+
37
+ let enabled = $state(false);
38
+ </script>
39
+
40
+ <Switch bind:checked={enabled} />
41
+ <span>{enabled ? 'On' : 'Off'}</span>
42
+ ```
43
+
44
+ ### Different Sizes
45
+
46
+ ```svelte
47
+ <Switch size="xs" />
48
+ <Switch size="sm" />
49
+ <Switch size="md" />
50
+ <Switch size="lg" />
51
+ <Switch size="xl" />
52
+ ```
53
+
54
+ ### With Icons Inside
55
+
56
+ ```svelte
57
+ <Switch bind:checked={darkMode}>
58
+ {#snippet on()}
59
+ <span class="text-xs">🌙</span>
60
+ {/snippet}
61
+ {#snippet off()}
62
+ <span class="text-xs">☀️</span>
63
+ {/snippet}
64
+ </Switch>
65
+ ```
66
+
67
+ ### With Async Validation
68
+
69
+ ```svelte
70
+ <script lang="ts">
71
+ let premium = $state(false);
72
+
73
+ async function checkPremium(current: boolean) {
74
+ if (!current) {
75
+ // Turning on - check if user can enable premium
76
+ const canEnable = await checkSubscription();
77
+ if (!canEnable) {
78
+ alert('Premium subscription required');
79
+ return false; // Prevent toggle
80
+ }
81
+ }
82
+ return true;
83
+ }
84
+ </script>
85
+
86
+ <Switch
87
+ bind:checked={premium}
88
+ preHook={checkPremium}
89
+ />
90
+ ```
91
+
92
+ ### In a Form
93
+
94
+ ```svelte
95
+ <form>
96
+ <label class="flex items-center gap-2">
97
+ <Switch name="notifications" bind:checked={notifications} />
98
+ <span>Enable notifications</span>
99
+ </label>
100
+ </form>
101
+ ```
102
+
103
+ ### Disabled State
104
+
105
+ ```svelte
106
+ <Switch checked={true} disabled />
107
+ <Switch checked={false} disabled />
108
+ ```
109
+
110
+ ## Keyboard Support
111
+
112
+ - **Space**: Toggle switch
113
+ - **Enter**: Toggle switch
@@ -0,0 +1,113 @@
1
+ # Thc
2
+
3
+ A flexible content renderer supporting multiple content formats: Text, Html, or Component (THC). Used throughout stuic for flexible content rendering in labels, messages, and other dynamic content areas.
4
+
5
+ ## THC Type
6
+
7
+ ```ts
8
+ type THC =
9
+ | string // Plain string
10
+ | { text: string } // Explicit text
11
+ | { html: string } // HTML (rendered with @html)
12
+ | { component: Component, props?: {} } // Svelte component
13
+ | { snippet: Snippet } // Svelte snippet
14
+ | Snippet // Direct snippet function
15
+ ```
16
+
17
+ ## Props
18
+
19
+ | Prop | Type | Default | Description |
20
+ |------|------|---------|-------------|
21
+ | `thc` | `THC` | - | Content to render |
22
+ | `forceAsHtml` | `boolean` | `false` | Render strings as HTML |
23
+ | `allowCastToStringFallback` | `boolean` | `true` | Cast unknown types to string |
24
+
25
+ ## Utility Functions
26
+
27
+ ### `isTHCNotEmpty(value)`
28
+
29
+ Checks if a THC value has renderable content.
30
+
31
+ ```ts
32
+ isTHCNotEmpty("Hello"); // true
33
+ isTHCNotEmpty({ text: "Hi" }); // true
34
+ isTHCNotEmpty(""); // false
35
+ isTHCNotEmpty(null); // false
36
+ ```
37
+
38
+ ### `getTHCStringContent(value)`
39
+
40
+ Extracts string content from a THC value.
41
+
42
+ ```ts
43
+ getTHCStringContent("Hello"); // "Hello"
44
+ getTHCStringContent({ text: "Hi" }); // "Hi"
45
+ getTHCStringContent({ html: "<b>X</b>" }); // "<b>X</b>"
46
+ getTHCStringContent(null); // ""
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ### Plain String
52
+
53
+ ```svelte
54
+ <script lang="ts">
55
+ import { Thc } from 'stuic';
56
+ </script>
57
+
58
+ <Thc thc="Hello World" />
59
+ ```
60
+
61
+ ### HTML Content
62
+
63
+ ```svelte
64
+ <Thc thc={{ html: "<strong>Bold</strong> and <em>italic</em>" }} />
65
+ ```
66
+
67
+ ### Force String as HTML
68
+
69
+ ```svelte
70
+ <Thc thc="<strong>Bold</strong>" forceAsHtml />
71
+ ```
72
+
73
+ ### Component Content
74
+
75
+ ```svelte
76
+ <script lang="ts">
77
+ import MyIcon from './MyIcon.svelte';
78
+ </script>
79
+
80
+ <Thc thc={{
81
+ component: MyIcon,
82
+ props: { size: 24, color: 'blue' }
83
+ }} />
84
+ ```
85
+
86
+ ### Snippet Content
87
+
88
+ ```svelte
89
+ {#snippet myContent()}
90
+ <span class="custom">Custom snippet content</span>
91
+ {/snippet}
92
+
93
+ <Thc thc={{ snippet: myContent }} />
94
+
95
+ <!-- Or directly -->
96
+ <Thc thc={myContent} />
97
+ ```
98
+
99
+ ### In Other Components
100
+
101
+ Many stuic components accept THC for labels and content:
102
+
103
+ ```svelte
104
+ <FieldInput
105
+ label="Username"
106
+ description={{ html: "Enter your <strong>unique</strong> username" }}
107
+ />
108
+
109
+ <DismissibleMessage
110
+ message={{ text: "Operation completed" }}
111
+ theme="green"
112
+ />
113
+ ```
@@ -0,0 +1,54 @@
1
+ # TwCheck
2
+
3
+ A development utility component to verify that Tailwind CSS is properly loaded and working. Displays differently styled content at different breakpoints.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `theme` | `string` | - | Tailwind color theme for background |
10
+ | `class` | `string` | - | Additional CSS classes |
11
+
12
+ ## Usage
13
+
14
+ ### Basic Check
15
+
16
+ ```svelte
17
+ <script lang="ts">
18
+ import { TwCheck } from 'stuic';
19
+ </script>
20
+
21
+ <TwCheck>
22
+ TW Check
23
+ </TwCheck>
24
+ ```
25
+
26
+ ### With Theme
27
+
28
+ ```svelte
29
+ <TwCheck theme="blue">
30
+ Tailwind is working!
31
+ </TwCheck>
32
+ ```
33
+
34
+ ## Visual Indicators
35
+
36
+ When Tailwind CSS is properly loaded:
37
+ - **Mobile**: Yellow text, no border
38
+ - **Desktop (sm+)**: White text, teal border, larger font
39
+
40
+ If the component appears unstyled (plain text), Tailwind CSS is not loading correctly.
41
+
42
+ ## Development Use
43
+
44
+ Add temporarily during development to verify Tailwind setup:
45
+
46
+ ```svelte
47
+ <script lang="ts">
48
+ import { dev } from '$app/environment';
49
+ </script>
50
+
51
+ {#if dev}
52
+ <TwCheck>CSS OK</TwCheck>
53
+ {/if}
54
+ ```
@@ -0,0 +1,116 @@
1
+ # TypeaheadInput
2
+
3
+ An input with typeahead/autocomplete suggestions. Shows inline suggestions that can be accepted with Tab, with support for async data fetching and keyboard navigation.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `value` | `any` | - | Current input value (bindable) |
10
+ | `input` | `HTMLInputElement` | - | Input element reference (bindable) |
11
+ | `placeholder` | `string` | - | Input placeholder |
12
+ | `getOptions` | `(q: string, current: T[]) => Promise<T[]>` | - | Async function to fetch suggestions |
13
+ | `renderOptionLabel` | `(item: T) => string` | - | Render label for an option |
14
+ | `itemIdPropName` | `string` | `"id"` | Property name for item ID |
15
+ | `name` | `string` | `"text_input"` | Input name attribute |
16
+ | `onSubmit` | `(value: string) => void` | - | Called on Enter or blur |
17
+ | `onDeleteRequest` | `() => void` | - | Called on Backspace at position 0 |
18
+ | `noSpinner` | `boolean` | `false` | Hide loading spinner |
19
+ | `noListAllOnEmptyQ` | `boolean` | `false` | Don't show all options on empty query |
20
+ | `appendHint` | `string` | `" [tab]"` | Hint text appended to suggestion |
21
+ | `class` | `string` | - | CSS for container |
22
+ | `classInput` | `string` | - | CSS for input element |
23
+
24
+ ## Usage
25
+
26
+ ### Basic Typeahead
27
+
28
+ ```svelte
29
+ <script lang="ts">
30
+ import { TypeaheadInput } from 'stuic';
31
+
32
+ let city = $state('');
33
+
34
+ const cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'];
35
+
36
+ async function getOptions(q: string) {
37
+ return cities
38
+ .filter(c => c.toLowerCase().startsWith(q.toLowerCase()))
39
+ .map((label, id) => ({ id, label }));
40
+ }
41
+ </script>
42
+
43
+ <TypeaheadInput
44
+ bind:value={city}
45
+ {getOptions}
46
+ renderOptionLabel={(item) => item.label}
47
+ placeholder="Enter city name"
48
+ onSubmit={(value) => console.log('Selected:', value)}
49
+ />
50
+ ```
51
+
52
+ ### With API Fetch
53
+
54
+ ```svelte
55
+ <script lang="ts">
56
+ async function getOptions(q: string) {
57
+ if (!q) return [];
58
+ const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
59
+ return res.json();
60
+ }
61
+ </script>
62
+
63
+ <TypeaheadInput
64
+ bind:value
65
+ {getOptions}
66
+ renderOptionLabel={(item) => item.name}
67
+ />
68
+ ```
69
+
70
+ ### Tag Input Pattern
71
+
72
+ ```svelte
73
+ <script lang="ts">
74
+ let tags = $state<string[]>([]);
75
+ let currentTag = $state('');
76
+ </script>
77
+
78
+ <div class="flex gap-2 flex-wrap">
79
+ {#each tags as tag}
80
+ <span class="bg-gray-200 px-2 py-1 rounded">{tag}</span>
81
+ {/each}
82
+
83
+ <TypeaheadInput
84
+ bind:value={currentTag}
85
+ {getOptions}
86
+ renderOptionLabel={(item) => item.label}
87
+ onSubmit={(value) => {
88
+ if (value && !tags.includes(value)) {
89
+ tags = [...tags, value];
90
+ currentTag = '';
91
+ }
92
+ }}
93
+ onDeleteRequest={() => {
94
+ if (tags.length) {
95
+ tags = tags.slice(0, -1);
96
+ }
97
+ }}
98
+ />
99
+ </div>
100
+ ```
101
+
102
+ ## Keyboard Navigation
103
+
104
+ - **Arrow Down/Up**: Cycle through suggestions
105
+ - **Tab**: Accept current suggestion
106
+ - **Enter**: Submit current value
107
+ - **Arrow Right**: Accept suggestion (when cursor at end)
108
+ - **Backspace** (at position 0): Triggers `onDeleteRequest`
109
+
110
+ ## Features
111
+
112
+ - Case-insensitive and accent-insensitive matching
113
+ - Debounced search (150ms)
114
+ - Ghost text shows suggestion inline
115
+ - Optional spinner during fetch
116
+ - Supports listing all options on empty query with arrow keys
@@ -0,0 +1,69 @@
1
+ # X
2
+
3
+ A simple SVG close/dismiss icon (X shape) with configurable stroke width.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `strokeWidth` | `0.5 \| 1 \| 1.5 \| 2 \| 2.5 \| 3 \| 3.5 \| 4` | `2` | Stroke width of the X lines |
10
+ | `class` | `string` | - | CSS classes (default size is `size-6` / 1.5rem) |
11
+
12
+ ## Usage
13
+
14
+ ### Basic Close Icon
15
+
16
+ ```svelte
17
+ <script lang="ts">
18
+ import { X } from 'stuic';
19
+ </script>
20
+
21
+ <button>
22
+ <X />
23
+ </button>
24
+ ```
25
+
26
+ ### Different Sizes
27
+
28
+ ```svelte
29
+ <X class="size-4" />
30
+ <X class="size-6" />
31
+ <X class="size-8" />
32
+ ```
33
+
34
+ ### Different Stroke Widths
35
+
36
+ ```svelte
37
+ <X strokeWidth={1} />
38
+ <X strokeWidth={2} />
39
+ <X strokeWidth={3} />
40
+ ```
41
+
42
+ ### With Custom Color
43
+
44
+ ```svelte
45
+ <X class="text-red-500" />
46
+ <X class="text-gray-400" />
47
+ ```
48
+
49
+ ### In a Close Button
50
+
51
+ ```svelte
52
+ <button
53
+ onclick={handleClose}
54
+ class="p-2 hover:bg-gray-100 rounded-full"
55
+ >
56
+ <X strokeWidth={1.5} class="size-5" />
57
+ </button>
58
+ ```
59
+
60
+ ### In a Dismissible Element
61
+
62
+ ```svelte
63
+ <div class="flex items-center justify-between p-4 bg-blue-100 rounded">
64
+ <span>Notification message</span>
65
+ <button onclick={() => visible = false}>
66
+ <X class="size-4 text-blue-600 hover:text-blue-800" />
67
+ </button>
68
+ </div>
69
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",