@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.
- package/dist/actions/popover/README.md +145 -0
- package/dist/actions/tooltip/README.md +132 -0
- package/dist/components/AlertConfirmPrompt/README.md +139 -0
- package/dist/components/AnimatedElipsis/README.md +42 -0
- package/dist/components/AppShell/README.md +99 -0
- package/dist/components/Backdrop/README.md +81 -0
- package/dist/components/Button/README.md +83 -0
- package/dist/components/ButtonGroupRadio/README.md +94 -0
- package/dist/components/Circle/README.md +70 -0
- package/dist/components/ColorScheme/README.md +64 -0
- package/dist/components/CommandMenu/README.md +97 -0
- package/dist/components/DismissibleMessage/README.md +91 -0
- package/dist/components/Drawer/README.md +110 -0
- package/dist/components/HoverExpandableWidth/README.md +81 -0
- package/dist/components/Input/README.md +192 -0
- package/dist/components/KbdShortcut/README.md +81 -0
- package/dist/components/Modal/README.md +117 -0
- package/dist/components/ModalDialog/README.md +103 -0
- package/dist/components/Notifications/README.md +144 -0
- package/dist/components/Progress/README.md +71 -0
- package/dist/components/SlidingPanels/README.md +123 -0
- package/dist/components/Spinner/README.md +80 -0
- package/dist/components/Switch/README.md +113 -0
- package/dist/components/Thc/README.md +113 -0
- package/dist/components/TwCheck/README.md +54 -0
- package/dist/components/TypeaheadInput/README.md +116 -0
- package/dist/components/X/README.md +69 -0
- package/package.json +1 -1
|
@@ -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
|
+
```
|