@marianmeres/stuic 3.73.0 → 3.75.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/AGENTS.md +2 -2
- package/API.md +30 -0
- package/README.md +1 -1
- package/dist/components/Book/BookResponsive.svelte +2 -1
- package/dist/components/DismissibleMessage/index.css +13 -2
- package/dist/components/Input/FieldPhoneNumber.svelte +2 -3
- package/dist/components/LoginForm/LoginForm.svelte +1 -1
- package/dist/components/Pill/Pill.svelte +205 -0
- package/dist/components/Pill/Pill.svelte.d.ts +51 -0
- package/dist/components/Pill/README.md +211 -0
- package/dist/components/Pill/index.css +488 -0
- package/dist/components/Pill/index.d.ts +1 -0
- package/dist/components/Pill/index.js +1 -0
- package/dist/components/PricingTable/PricingTable.svelte +0 -2
- package/dist/components/RegisterForm/RegisterForm.svelte +1 -1
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp.js +1 -0
- package/docs/domains/components.md +2 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
src/lib/
|
|
26
|
-
├── components/ #
|
|
26
|
+
├── components/ # 56 UI components
|
|
27
27
|
├── actions/ # 15 Svelte actions
|
|
28
28
|
├── utils/ # 43 utility modules
|
|
29
29
|
├── themes/ # Generated theme CSS (css/) — definitions from @marianmeres/design-tokens
|
|
@@ -116,7 +116,7 @@ Global tokens that control cross-component visual properties. Defined in `src/li
|
|
|
116
116
|
|
|
117
117
|
### Domain Docs
|
|
118
118
|
|
|
119
|
-
- [Components](./docs/domains/components.md) —
|
|
119
|
+
- [Components](./docs/domains/components.md) — 56 component directories, Props pattern, snippets
|
|
120
120
|
- [Theming](./docs/domains/theming.md) — CSS tokens, dark mode, themes
|
|
121
121
|
- [Actions](./docs/domains/actions.md) — 15 Svelte directives
|
|
122
122
|
- [Utils](./docs/domains/utils.md) — 43 utility modules
|
package/API.md
CHANGED
|
@@ -841,6 +841,36 @@ User avatar with fallback to initials or icon.
|
|
|
841
841
|
<!-- Shows "JD" initials -->
|
|
842
842
|
```
|
|
843
843
|
|
|
844
|
+
#### `Pill`
|
|
845
|
+
|
|
846
|
+
Inline rounded badge/tag/chip with intent + variant + size system. Polymorphic: renders as `<span>` (default), `<a>` (when `href` set), or `<button>` (when `onclick` set).
|
|
847
|
+
|
|
848
|
+
| Prop | Type | Default | Description |
|
|
849
|
+
| --------------- | ------------------------------------------------------------------ | -------- | ---------------------------------------------------- |
|
|
850
|
+
| `intent` | `"primary" \| "accent" \| "destructive" \| "warning" \| "success"` | - | Semantic color |
|
|
851
|
+
| `variant` | `"solid" \| "outline" \| "ghost" \| "soft" \| "link"` | `"soft"` | Visual treatment |
|
|
852
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Pill size |
|
|
853
|
+
| `roundedFull` | `boolean` | `true` | Fully rounded (9999px). `false` → element radius |
|
|
854
|
+
| `block` | `boolean` | `false` | Block-level flex (full width) |
|
|
855
|
+
| `dot` | `boolean` | `false` | Status dot before content |
|
|
856
|
+
| `dismissible` | `boolean` | `false` | Built-in X dismiss button |
|
|
857
|
+
| `ondismiss` | `(e: MouseEvent) => void` | - | Called when X clicked (stops propagation) |
|
|
858
|
+
| `active` | `boolean` | `false` | Selected state (filter-chip) |
|
|
859
|
+
| `muted` | `boolean` | `false` | Lower opacity |
|
|
860
|
+
| `href`, `target`| `string` | - | Render as `<a>` |
|
|
861
|
+
| `onclick` | `(e: MouseEvent) => void` | - | Render as `<button>` |
|
|
862
|
+
| `contentBefore` | `THC` | - | Content before children |
|
|
863
|
+
| `contentAfter` | `THC` | - | Content after children |
|
|
864
|
+
|
|
865
|
+
```svelte
|
|
866
|
+
<Pill intent="success" dot>Online</Pill>
|
|
867
|
+
<Pill intent="primary" dismissible ondismiss={() => removeTag()}>tag</Pill>
|
|
868
|
+
<Pill intent="accent" href="/profile">Profile</Pill>
|
|
869
|
+
<Pill intent="warning" variant="outline" active onclick={toggle}>Filter</Pill>
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
CSS tokens: `--stuic-pill-radius`, `--stuic-pill-font-family`, `--stuic-pill-font-weight`, `--stuic-pill-gap`, `--stuic-pill-dot-size`, `--stuic-pill-ring-{width,color}`, `--stuic-pill-{padding-x,padding-y,font-size,min-height}-{sm,md,lg}`.
|
|
873
|
+
|
|
844
874
|
#### `KbdShortcut`
|
|
845
875
|
|
|
846
876
|
Keyboard shortcut display.
|
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ CommandMenu, DropdownMenu, TabbedMenu, TypeaheadInput, KbdShortcut
|
|
|
164
164
|
|
|
165
165
|
### Display & Utility
|
|
166
166
|
|
|
167
|
-
Avatar, Book, BookResponsive, Card, Carousel, Circle, AnimatedElipsis, H, IconSwap, ImageCycler, Separator, ThemePreview, Tree, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview, AssetsPreviewInline, DataTable
|
|
167
|
+
Avatar, Pill, Book, BookResponsive, Card, Carousel, Circle, AnimatedElipsis, H, IconSwap, ImageCycler, Separator, ThemePreview, Tree, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview, AssetsPreviewInline, DataTable
|
|
168
168
|
|
|
169
169
|
### E-commerce
|
|
170
170
|
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
<script lang="ts">
|
|
28
|
+
import { untrack } from "svelte";
|
|
28
29
|
import {
|
|
29
30
|
iconBookOpen,
|
|
30
31
|
iconArrowRight as iconNext,
|
|
@@ -89,7 +90,7 @@
|
|
|
89
90
|
|
|
90
91
|
// ---- Manual mode override ----
|
|
91
92
|
|
|
92
|
-
let manualMode: "book" | "inline" | null = $state(initialMode ?? null);
|
|
93
|
+
let manualMode: "book" | "inline" | null = $state(untrack(() => initialMode ?? null));
|
|
93
94
|
|
|
94
95
|
// ---- Inline mode ----
|
|
95
96
|
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
:root {
|
|
8
8
|
--stuic-dismissible-message-padding-x: calc(var(--spacing) * 4);
|
|
9
9
|
--stuic-dismissible-message-padding-y: calc(var(--spacing) * 3);
|
|
10
|
+
--stuic-dismissible-x-padding: calc(var(--spacing) * 1);
|
|
11
|
+
--stuic-dismissible-x-bg-hover: rgb(0 0 0 / 0.1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
:root.dark {
|
|
15
|
+
--stuic-dismissible-x-bg-hover: rgb(255 255 255 / 0.05);
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
@layer components {
|
|
@@ -17,7 +23,10 @@
|
|
|
17
23
|
.stuic-dismissible-message {
|
|
18
24
|
display: flex;
|
|
19
25
|
align-items: center;
|
|
20
|
-
border-width: var(
|
|
26
|
+
border-width: var(
|
|
27
|
+
--stuic-dismissible-message-border-width,
|
|
28
|
+
var(--stuic-border-width)
|
|
29
|
+
);
|
|
21
30
|
border-style: solid;
|
|
22
31
|
border-radius: var(--stuic-dismissible-message-radius, var(--stuic-radius));
|
|
23
32
|
transition:
|
|
@@ -58,12 +67,14 @@
|
|
|
58
67
|
display: flex;
|
|
59
68
|
align-items: center;
|
|
60
69
|
justify-content: center;
|
|
61
|
-
padding:
|
|
70
|
+
padding: var(--stuic-dismissible-x-padding);
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
/* Dismiss button inherits message text color */
|
|
65
74
|
.stuic-dismissible-message > .dismiss .stuic-button {
|
|
66
75
|
color: var(--_text);
|
|
76
|
+
--_bg-hover: var(--stuic-dismissible-x-bg-hover);
|
|
77
|
+
--_bg-active: var(--stuic-dismissible-x-bg-hover);
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
/* =============================================================================
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
</script>
|
|
60
60
|
|
|
61
61
|
<script lang="ts">
|
|
62
|
-
import { tick } from "svelte";
|
|
62
|
+
import { tick, untrack } from "svelte";
|
|
63
63
|
import {
|
|
64
64
|
validate as validateAction,
|
|
65
65
|
type ValidationResult,
|
|
@@ -142,9 +142,8 @@
|
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
// Selected country object (initialize once from defaultCountry prop)
|
|
145
|
-
const _initCountry = defaultCountry;
|
|
146
145
|
let selectedCountry: Country | undefined = $state(
|
|
147
|
-
|
|
146
|
+
untrack(() => (defaultCountry ? ISO_MAP.get(defaultCountry.toUpperCase()) : undefined))
|
|
148
147
|
);
|
|
149
148
|
|
|
150
149
|
// Internal local number (for controlled input)
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
|
|
158
158
|
<form bind:this={el} class={_class} use:onSubmitValidityCheck {...rest}>
|
|
159
159
|
<!-- General error alert -->
|
|
160
|
-
<DismissibleMessage message={error} intent="destructive"
|
|
160
|
+
<DismissibleMessage message={error} intent="destructive" />
|
|
161
161
|
|
|
162
162
|
<!--
|
|
163
163
|
svelte-ignore binding_property_non_reactive:
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type {
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
HTMLAnchorAttributes,
|
|
5
|
+
HTMLButtonAttributes,
|
|
6
|
+
} from "svelte/elements";
|
|
7
|
+
import type { Snippet } from "svelte";
|
|
8
|
+
import type { IntentColorKey } from "../../utils/design-tokens.js";
|
|
9
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
10
|
+
|
|
11
|
+
export type PillVariant = "solid" | "outline" | "ghost" | "soft" | "link";
|
|
12
|
+
export type PillSize = "sm" | "md" | "lg";
|
|
13
|
+
|
|
14
|
+
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
15
|
+
/** Color intent (semantic meaning) */
|
|
16
|
+
intent?: IntentColorKey;
|
|
17
|
+
/** Visual variant (how colors are applied) */
|
|
18
|
+
variant?: PillVariant | string;
|
|
19
|
+
/** Size preset */
|
|
20
|
+
size?: PillSize | string;
|
|
21
|
+
/** Reduce emphasis (lower opacity) */
|
|
22
|
+
muted?: boolean;
|
|
23
|
+
/** Selected/active state — useful for filter-chip behavior */
|
|
24
|
+
active?: boolean;
|
|
25
|
+
/** Pill is fully rounded by default; set false to use element radius */
|
|
26
|
+
roundedFull?: boolean;
|
|
27
|
+
/** Render as block-level flex (full width). Inline-flex by default. */
|
|
28
|
+
block?: boolean;
|
|
29
|
+
/** Skip all default styling, use only custom classes */
|
|
30
|
+
unstyled?: boolean;
|
|
31
|
+
/** Additional CSS classes */
|
|
32
|
+
class?: string;
|
|
33
|
+
/** Render as anchor tag */
|
|
34
|
+
href?: string;
|
|
35
|
+
/** Link target (e.g. "_blank") — only relevant when href is set */
|
|
36
|
+
target?: string;
|
|
37
|
+
/** Render as button (when href not set) */
|
|
38
|
+
onclick?: (e: MouseEvent) => void;
|
|
39
|
+
/** Disabled (interactive variants only) */
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
/** Show built-in X dismiss control */
|
|
42
|
+
dismissible?: boolean;
|
|
43
|
+
/** Called when X is clicked. Stops propagation so parent onclick is unaffected. */
|
|
44
|
+
ondismiss?: (e: MouseEvent) => void;
|
|
45
|
+
/** Status dot rendered before content (uses current intent color) */
|
|
46
|
+
dot?: boolean;
|
|
47
|
+
/** Content rendered before children */
|
|
48
|
+
contentBefore?: THC;
|
|
49
|
+
/** Content rendered after children */
|
|
50
|
+
contentAfter?: THC;
|
|
51
|
+
/** Bindable element reference */
|
|
52
|
+
el?: HTMLElement;
|
|
53
|
+
/** Content snippet */
|
|
54
|
+
children?: Snippet;
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<script lang="ts">
|
|
59
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
60
|
+
import Thc, { isTHCNotEmpty } from "../Thc/Thc.svelte";
|
|
61
|
+
import { X } from "../X/index.js";
|
|
62
|
+
|
|
63
|
+
let {
|
|
64
|
+
class: classProp,
|
|
65
|
+
intent,
|
|
66
|
+
variant = "soft",
|
|
67
|
+
size = "md",
|
|
68
|
+
muted = false,
|
|
69
|
+
active = false,
|
|
70
|
+
roundedFull = true,
|
|
71
|
+
block = false,
|
|
72
|
+
unstyled = false,
|
|
73
|
+
href,
|
|
74
|
+
target,
|
|
75
|
+
onclick,
|
|
76
|
+
disabled,
|
|
77
|
+
dismissible = false,
|
|
78
|
+
ondismiss,
|
|
79
|
+
dot = false,
|
|
80
|
+
contentBefore,
|
|
81
|
+
contentAfter,
|
|
82
|
+
el = $bindable(),
|
|
83
|
+
children,
|
|
84
|
+
...rest
|
|
85
|
+
}: Props = $props();
|
|
86
|
+
|
|
87
|
+
let _class = $derived(unstyled ? classProp : twMerge("stuic-pill", classProp));
|
|
88
|
+
|
|
89
|
+
function handleDismiss(e: MouseEvent) {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
ondismiss?.(e);
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
{#snippet body()}
|
|
96
|
+
{#if dot}
|
|
97
|
+
<span class="stuic-pill-dot" aria-hidden="true"></span>
|
|
98
|
+
{/if}
|
|
99
|
+
{#if isTHCNotEmpty(contentBefore)}
|
|
100
|
+
<Thc thc={contentBefore as THC} />
|
|
101
|
+
{/if}
|
|
102
|
+
{@render children?.()}
|
|
103
|
+
{#if isTHCNotEmpty(contentAfter)}
|
|
104
|
+
<Thc thc={contentAfter as THC} />
|
|
105
|
+
{/if}
|
|
106
|
+
{/snippet}
|
|
107
|
+
|
|
108
|
+
{#snippet dismissBtn()}
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
class="stuic-pill-dismiss"
|
|
112
|
+
aria-label="Dismiss"
|
|
113
|
+
onclick={handleDismiss}
|
|
114
|
+
{disabled}
|
|
115
|
+
>
|
|
116
|
+
<X strokeWidth={2} />
|
|
117
|
+
</button>
|
|
118
|
+
{/snippet}
|
|
119
|
+
|
|
120
|
+
{#if dismissible}
|
|
121
|
+
<!-- Wrapper pattern: outer span carries pill styling; inner element is the
|
|
122
|
+
interactive area (when href/onclick); X dismiss is a sibling button. -->
|
|
123
|
+
<span
|
|
124
|
+
bind:this={el}
|
|
125
|
+
class={_class}
|
|
126
|
+
data-intent={!unstyled ? intent : undefined}
|
|
127
|
+
data-variant={!unstyled ? variant : undefined}
|
|
128
|
+
data-size={!unstyled ? size : undefined}
|
|
129
|
+
data-muted={!unstyled && muted ? "true" : undefined}
|
|
130
|
+
data-active={!unstyled && active ? "true" : undefined}
|
|
131
|
+
data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
|
|
132
|
+
data-block={!unstyled && block ? "true" : undefined}
|
|
133
|
+
data-with-dot={!unstyled && dot ? "true" : undefined}
|
|
134
|
+
data-dismissible="true"
|
|
135
|
+
{...rest}
|
|
136
|
+
>
|
|
137
|
+
{#if href}
|
|
138
|
+
<a {href} {target} class="stuic-pill-main">
|
|
139
|
+
{@render body()}
|
|
140
|
+
</a>
|
|
141
|
+
{:else if onclick}
|
|
142
|
+
<button type="button" class="stuic-pill-main" {onclick} {disabled}>
|
|
143
|
+
{@render body()}
|
|
144
|
+
</button>
|
|
145
|
+
{:else}
|
|
146
|
+
{@render body()}
|
|
147
|
+
{/if}
|
|
148
|
+
{@render dismissBtn()}
|
|
149
|
+
</span>
|
|
150
|
+
{:else if href}
|
|
151
|
+
<a
|
|
152
|
+
{href}
|
|
153
|
+
{target}
|
|
154
|
+
bind:this={el}
|
|
155
|
+
class={_class}
|
|
156
|
+
data-intent={!unstyled ? intent : undefined}
|
|
157
|
+
data-variant={!unstyled ? variant : undefined}
|
|
158
|
+
data-size={!unstyled ? size : undefined}
|
|
159
|
+
data-muted={!unstyled && muted ? "true" : undefined}
|
|
160
|
+
data-active={!unstyled && active ? "true" : undefined}
|
|
161
|
+
data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
|
|
162
|
+
data-block={!unstyled && block ? "true" : undefined}
|
|
163
|
+
data-with-dot={!unstyled && dot ? "true" : undefined}
|
|
164
|
+
data-interactive="true"
|
|
165
|
+
{...rest as HTMLAnchorAttributes}
|
|
166
|
+
>
|
|
167
|
+
{@render body()}
|
|
168
|
+
</a>
|
|
169
|
+
{:else if onclick}
|
|
170
|
+
<button
|
|
171
|
+
type="button"
|
|
172
|
+
bind:this={el}
|
|
173
|
+
class={_class}
|
|
174
|
+
data-intent={!unstyled ? intent : undefined}
|
|
175
|
+
data-variant={!unstyled ? variant : undefined}
|
|
176
|
+
data-size={!unstyled ? size : undefined}
|
|
177
|
+
data-muted={!unstyled && muted ? "true" : undefined}
|
|
178
|
+
data-active={!unstyled && active ? "true" : undefined}
|
|
179
|
+
data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
|
|
180
|
+
data-block={!unstyled && block ? "true" : undefined}
|
|
181
|
+
data-with-dot={!unstyled && dot ? "true" : undefined}
|
|
182
|
+
data-interactive="true"
|
|
183
|
+
{onclick}
|
|
184
|
+
{disabled}
|
|
185
|
+
{...rest as HTMLButtonAttributes}
|
|
186
|
+
>
|
|
187
|
+
{@render body()}
|
|
188
|
+
</button>
|
|
189
|
+
{:else}
|
|
190
|
+
<span
|
|
191
|
+
bind:this={el}
|
|
192
|
+
class={_class}
|
|
193
|
+
data-intent={!unstyled ? intent : undefined}
|
|
194
|
+
data-variant={!unstyled ? variant : undefined}
|
|
195
|
+
data-size={!unstyled ? size : undefined}
|
|
196
|
+
data-muted={!unstyled && muted ? "true" : undefined}
|
|
197
|
+
data-active={!unstyled && active ? "true" : undefined}
|
|
198
|
+
data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
|
|
199
|
+
data-block={!unstyled && block ? "true" : undefined}
|
|
200
|
+
data-with-dot={!unstyled && dot ? "true" : undefined}
|
|
201
|
+
{...rest}
|
|
202
|
+
>
|
|
203
|
+
{@render body()}
|
|
204
|
+
</span>
|
|
205
|
+
{/if}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { IntentColorKey } from "../../utils/design-tokens.js";
|
|
4
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
+
export type PillVariant = "solid" | "outline" | "ghost" | "soft" | "link";
|
|
6
|
+
export type PillSize = "sm" | "md" | "lg";
|
|
7
|
+
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
8
|
+
/** Color intent (semantic meaning) */
|
|
9
|
+
intent?: IntentColorKey;
|
|
10
|
+
/** Visual variant (how colors are applied) */
|
|
11
|
+
variant?: PillVariant | string;
|
|
12
|
+
/** Size preset */
|
|
13
|
+
size?: PillSize | string;
|
|
14
|
+
/** Reduce emphasis (lower opacity) */
|
|
15
|
+
muted?: boolean;
|
|
16
|
+
/** Selected/active state — useful for filter-chip behavior */
|
|
17
|
+
active?: boolean;
|
|
18
|
+
/** Pill is fully rounded by default; set false to use element radius */
|
|
19
|
+
roundedFull?: boolean;
|
|
20
|
+
/** Render as block-level flex (full width). Inline-flex by default. */
|
|
21
|
+
block?: boolean;
|
|
22
|
+
/** Skip all default styling, use only custom classes */
|
|
23
|
+
unstyled?: boolean;
|
|
24
|
+
/** Additional CSS classes */
|
|
25
|
+
class?: string;
|
|
26
|
+
/** Render as anchor tag */
|
|
27
|
+
href?: string;
|
|
28
|
+
/** Link target (e.g. "_blank") — only relevant when href is set */
|
|
29
|
+
target?: string;
|
|
30
|
+
/** Render as button (when href not set) */
|
|
31
|
+
onclick?: (e: MouseEvent) => void;
|
|
32
|
+
/** Disabled (interactive variants only) */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
/** Show built-in X dismiss control */
|
|
35
|
+
dismissible?: boolean;
|
|
36
|
+
/** Called when X is clicked. Stops propagation so parent onclick is unaffected. */
|
|
37
|
+
ondismiss?: (e: MouseEvent) => void;
|
|
38
|
+
/** Status dot rendered before content (uses current intent color) */
|
|
39
|
+
dot?: boolean;
|
|
40
|
+
/** Content rendered before children */
|
|
41
|
+
contentBefore?: THC;
|
|
42
|
+
/** Content rendered after children */
|
|
43
|
+
contentAfter?: THC;
|
|
44
|
+
/** Bindable element reference */
|
|
45
|
+
el?: HTMLElement;
|
|
46
|
+
/** Content snippet */
|
|
47
|
+
children?: Snippet;
|
|
48
|
+
}
|
|
49
|
+
declare const Pill: import("svelte").Component<Props, {}, "el">;
|
|
50
|
+
type Pill = ReturnType<typeof Pill>;
|
|
51
|
+
export default Pill;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Pill
|
|
2
|
+
|
|
3
|
+
A small rounded inline element for tags, badges, status indicators, and filter chips. Polymorphic (renders as `<span>`, `<a>`, or `<button>` depending on props), with optional dismiss button, status dot, and content slots.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
| --------------- | ------------------------------------------------------------------ | -------- | -------------------------------------------------------------------- |
|
|
9
|
+
| `intent` | `"primary" \| "accent" \| "destructive" \| "warning" \| "success"` | - | Semantic color intent |
|
|
10
|
+
| `variant` | `"solid" \| "outline" \| "ghost" \| "soft" \| "link"` | `"soft"` | Visual variant (how colors are applied) |
|
|
11
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Pill size |
|
|
12
|
+
| `muted` | `boolean` | `false` | Reduce emphasis (lower opacity) |
|
|
13
|
+
| `active` | `boolean` | `false` | Selected/active state (filter-chip behavior) |
|
|
14
|
+
| `roundedFull` | `boolean` | `true` | Fully rounded corners (9999px). Set `false` to use the element radius |
|
|
15
|
+
| `block` | `boolean` | `false` | Render as block-level flex (full width). `inline-flex` by default |
|
|
16
|
+
| `unstyled` | `boolean` | `false` | Skip all default styling |
|
|
17
|
+
| `href` | `string` | - | Render as `<a>` with this URL |
|
|
18
|
+
| `target` | `string` | - | Link target (only when `href` is set) |
|
|
19
|
+
| `onclick` | `(e: MouseEvent) => void` | - | Render as `<button>` with this handler (when no `href`) |
|
|
20
|
+
| `disabled` | `boolean` | - | Disabled state (interactive variants only) |
|
|
21
|
+
| `dismissible` | `boolean` | `false` | Show built-in X dismiss button |
|
|
22
|
+
| `ondismiss` | `(e: MouseEvent) => void` | - | Called when X is clicked. Stops propagation |
|
|
23
|
+
| `dot` | `boolean` | `false` | Status dot rendered before content |
|
|
24
|
+
| `contentBefore` | `THC` | - | Content rendered before children |
|
|
25
|
+
| `contentAfter` | `THC` | - | Content rendered after children |
|
|
26
|
+
| `el` | `HTMLElement` | - | Element reference (bindable) |
|
|
27
|
+
| `class` | `string` | - | Additional CSS classes |
|
|
28
|
+
|
|
29
|
+
## Element Resolution
|
|
30
|
+
|
|
31
|
+
| Condition | Element |
|
|
32
|
+
| --------------------------------- | ------------------------------------------------------ |
|
|
33
|
+
| Default | `<span>` |
|
|
34
|
+
| `href` set | `<a>` |
|
|
35
|
+
| `onclick` set (no `href`) | `<button>` |
|
|
36
|
+
| `dismissible` set | `<span>` wrapper containing main element + X sibling |
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### Intent x Variant
|
|
41
|
+
|
|
42
|
+
```svelte
|
|
43
|
+
<script lang="ts">
|
|
44
|
+
import { Pill } from "@marianmeres/stuic";
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<Pill intent="primary">primary</Pill>
|
|
48
|
+
<Pill intent="success" variant="solid">success</Pill>
|
|
49
|
+
<Pill intent="destructive" variant="outline">destructive</Pill>
|
|
50
|
+
<Pill intent="warning" variant="ghost">warning</Pill>
|
|
51
|
+
<Pill intent="accent" variant="link">accent</Pill>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Sizes
|
|
55
|
+
|
|
56
|
+
```svelte
|
|
57
|
+
<Pill size="sm">Small</Pill>
|
|
58
|
+
<Pill size="md">Medium</Pill>
|
|
59
|
+
<Pill size="lg">Large</Pill>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Polymorphic
|
|
63
|
+
|
|
64
|
+
```svelte
|
|
65
|
+
<!-- Plain inline marker (span) -->
|
|
66
|
+
<Pill intent="primary">tag</Pill>
|
|
67
|
+
|
|
68
|
+
<!-- Anchor link -->
|
|
69
|
+
<Pill intent="accent" href="/profile">Profile</Pill>
|
|
70
|
+
|
|
71
|
+
<!-- Button -->
|
|
72
|
+
<Pill intent="success" onclick={() => console.log("clicked")}>
|
|
73
|
+
Click me
|
|
74
|
+
</Pill>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Status Dot
|
|
78
|
+
|
|
79
|
+
```svelte
|
|
80
|
+
<Pill intent="success" dot>Online</Pill>
|
|
81
|
+
<Pill intent="warning" dot>Idle</Pill>
|
|
82
|
+
<Pill intent="destructive" dot>Offline</Pill>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Dismissible
|
|
86
|
+
|
|
87
|
+
```svelte
|
|
88
|
+
<script lang="ts">
|
|
89
|
+
let tags = $state(["svelte", "tailwind", "stuic"]);
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
{#each tags as tag (tag)}
|
|
93
|
+
<Pill
|
|
94
|
+
intent="primary"
|
|
95
|
+
dismissible
|
|
96
|
+
ondismiss={() => (tags = tags.filter((x) => x !== tag))}
|
|
97
|
+
>
|
|
98
|
+
{tag}
|
|
99
|
+
</Pill>
|
|
100
|
+
{/each}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`dismissible` works alongside `onclick` and `href` — the X button stops propagation so the parent click/navigation only fires for clicks outside the X.
|
|
104
|
+
|
|
105
|
+
### Filter Chips (active state)
|
|
106
|
+
|
|
107
|
+
```svelte
|
|
108
|
+
<script lang="ts">
|
|
109
|
+
let filters = $state(new Set<string>());
|
|
110
|
+
function toggle(f: string) {
|
|
111
|
+
filters.has(f) ? filters.delete(f) : filters.add(f);
|
|
112
|
+
filters = new Set(filters);
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
{#each ["new", "popular", "in-stock"] as opt}
|
|
117
|
+
<Pill
|
|
118
|
+
intent="primary"
|
|
119
|
+
variant="outline"
|
|
120
|
+
active={filters.has(opt)}
|
|
121
|
+
onclick={() => toggle(opt)}
|
|
122
|
+
>
|
|
123
|
+
{opt}
|
|
124
|
+
</Pill>
|
|
125
|
+
{/each}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Icons via contentBefore / contentAfter
|
|
129
|
+
|
|
130
|
+
```svelte
|
|
131
|
+
<Pill intent="success" contentBefore={{ html: iconCheck() }}>Verified</Pill>
|
|
132
|
+
<Pill intent="primary" contentAfter={{ html: iconArrowRight() }}>Next</Pill>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`contentBefore` and `contentAfter` accept any `THC` value (string, `{ html }`, `{ text }`, component, snippet).
|
|
136
|
+
|
|
137
|
+
### Block (full width)
|
|
138
|
+
|
|
139
|
+
```svelte
|
|
140
|
+
<Pill intent="primary" block>Block-level pill</Pill>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Custom Styling
|
|
144
|
+
|
|
145
|
+
```svelte
|
|
146
|
+
<!-- Override radius inline -->
|
|
147
|
+
<Pill intent="primary" style="--stuic-pill-radius: 4px;">Squared</Pill>
|
|
148
|
+
|
|
149
|
+
<!-- Disable rounded-full default for element-radius -->
|
|
150
|
+
<Pill intent="primary" roundedFull={false}>Element radius</Pill>
|
|
151
|
+
|
|
152
|
+
<!-- Unstyled for full control -->
|
|
153
|
+
<Pill unstyled class="bg-gradient-to-r from-purple-500 to-pink-500 text-white px-3 py-1 rounded-full">
|
|
154
|
+
Custom
|
|
155
|
+
</Pill>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## CSS Variables
|
|
159
|
+
|
|
160
|
+
### Component Tokens
|
|
161
|
+
|
|
162
|
+
| Variable | Default | Description |
|
|
163
|
+
| ----------------------------- | ---------------------- | --------------------- |
|
|
164
|
+
| `--stuic-pill-radius` | `--stuic-radius` | Border radius (overridden to `9999px` by `roundedFull`) |
|
|
165
|
+
| `--stuic-pill-font-family` | `--font-sans` | Font family |
|
|
166
|
+
| `--stuic-pill-font-weight` | `--font-weight-medium` | Font weight |
|
|
167
|
+
| `--stuic-pill-transition` | `--stuic-transition` | Transition duration |
|
|
168
|
+
| `--stuic-pill-border-width` | `--stuic-border-width` | Border width |
|
|
169
|
+
| `--stuic-pill-ring-width` | `2px` | Focus ring width |
|
|
170
|
+
| `--stuic-pill-ring-color` | `--stuic-color-ring` | Focus ring color |
|
|
171
|
+
| `--stuic-pill-gap` | `0.375rem` | Gap between dot/before/children/after/dismiss |
|
|
172
|
+
| `--stuic-pill-dot-size` | `0.5rem` | Status dot diameter |
|
|
173
|
+
|
|
174
|
+
### Size Tokens
|
|
175
|
+
|
|
176
|
+
Each size (sm, md, lg) has corresponding tokens:
|
|
177
|
+
|
|
178
|
+
- `--stuic-pill-padding-x-{size}`
|
|
179
|
+
- `--stuic-pill-padding-y-{size}`
|
|
180
|
+
- `--stuic-pill-font-size-{size}`
|
|
181
|
+
- `--stuic-pill-min-height-{size}`
|
|
182
|
+
|
|
183
|
+
Dismissible pills override `padding-y` to `0` (the X button defines the height).
|
|
184
|
+
|
|
185
|
+
### Intent Color Tokens
|
|
186
|
+
|
|
187
|
+
Pill reuses the global intent palette:
|
|
188
|
+
|
|
189
|
+
```css
|
|
190
|
+
:root {
|
|
191
|
+
--stuic-color-primary: ...;
|
|
192
|
+
--stuic-color-primary-hover: ...;
|
|
193
|
+
--stuic-color-primary-foreground: ...;
|
|
194
|
+
/* + accent, destructive, warning, success */
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Data Attributes
|
|
199
|
+
|
|
200
|
+
The component uses data attributes for styling:
|
|
201
|
+
|
|
202
|
+
- `data-intent` - intent value
|
|
203
|
+
- `data-variant` - variant value
|
|
204
|
+
- `data-size` - size value
|
|
205
|
+
- `data-muted` - present when `muted`
|
|
206
|
+
- `data-active` - present when `active`
|
|
207
|
+
- `data-rounded-full` - present when `roundedFull`
|
|
208
|
+
- `data-block` - present when `block`
|
|
209
|
+
- `data-with-dot` - present when `dot`
|
|
210
|
+
- `data-dismissible` - present when `dismissible`
|
|
211
|
+
- `data-interactive` - present on `<a>` / `<button>` non-dismissible variants
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
PILL COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-pill-radius: 4px; }
|
|
4
|
+
Override locally: <Pill style="--stuic-pill-radius: 4px;">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
7
|
+
/* prettier-ignore */
|
|
8
|
+
:root {
|
|
9
|
+
/* Component-level customization tokens */
|
|
10
|
+
--stuic-pill-font-family: var(--font-sans);
|
|
11
|
+
--stuic-pill-font-weight: var(--font-weight-medium);
|
|
12
|
+
|
|
13
|
+
/* Focus ring (interactive variants) */
|
|
14
|
+
--stuic-pill-ring-width: 2px;
|
|
15
|
+
--stuic-pill-ring-offset: 0px;
|
|
16
|
+
--stuic-pill-ring-color: var(--stuic-color-ring);
|
|
17
|
+
|
|
18
|
+
/* Layout */
|
|
19
|
+
--stuic-pill-gap: 0.375rem;
|
|
20
|
+
|
|
21
|
+
/* Status dot */
|
|
22
|
+
--stuic-pill-dot-size: 0.5rem;
|
|
23
|
+
|
|
24
|
+
/* Size: sm */
|
|
25
|
+
--stuic-pill-padding-x-sm: 0.5rem;
|
|
26
|
+
--stuic-pill-padding-y-sm: 0.125rem;
|
|
27
|
+
--stuic-pill-font-size-sm: 0.75rem;
|
|
28
|
+
--stuic-pill-min-height-sm: 1.25rem;
|
|
29
|
+
|
|
30
|
+
/* Size: md */
|
|
31
|
+
--stuic-pill-padding-x-md: 0.625rem;
|
|
32
|
+
--stuic-pill-padding-y-md: 0.1875rem;
|
|
33
|
+
--stuic-pill-font-size-md: 0.875rem;
|
|
34
|
+
--stuic-pill-min-height-md: 1.5rem;
|
|
35
|
+
|
|
36
|
+
/* Size: lg */
|
|
37
|
+
--stuic-pill-padding-x-lg: 0.875rem;
|
|
38
|
+
--stuic-pill-padding-y-lg: 0.3125rem;
|
|
39
|
+
--stuic-pill-font-size-lg: 1rem;
|
|
40
|
+
--stuic-pill-min-height-lg: 1.875rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@layer components {
|
|
44
|
+
/* ============================================================================
|
|
45
|
+
BASE STYLES
|
|
46
|
+
============================================================================ */
|
|
47
|
+
|
|
48
|
+
.stuic-pill {
|
|
49
|
+
/* Layout */
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
gap: var(--stuic-pill-gap);
|
|
54
|
+
vertical-align: middle;
|
|
55
|
+
|
|
56
|
+
/* Typography */
|
|
57
|
+
font-family: var(--stuic-pill-font-family);
|
|
58
|
+
font-weight: var(--stuic-pill-font-weight);
|
|
59
|
+
line-height: 1;
|
|
60
|
+
text-align: center;
|
|
61
|
+
text-decoration: none;
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
|
|
64
|
+
/* Box model */
|
|
65
|
+
border-width: var(--stuic-pill-border-width, var(--stuic-border-width));
|
|
66
|
+
border-style: solid;
|
|
67
|
+
border-radius: var(--stuic-pill-radius, var(--stuic-radius));
|
|
68
|
+
|
|
69
|
+
/* Interaction */
|
|
70
|
+
user-select: none;
|
|
71
|
+
-webkit-tap-highlight-color: transparent;
|
|
72
|
+
touch-action: manipulation;
|
|
73
|
+
transition:
|
|
74
|
+
background var(--stuic-pill-transition, var(--stuic-transition)),
|
|
75
|
+
color var(--stuic-pill-transition, var(--stuic-transition)),
|
|
76
|
+
border-color var(--stuic-pill-transition, var(--stuic-transition)),
|
|
77
|
+
opacity var(--stuic-pill-transition, var(--stuic-transition));
|
|
78
|
+
|
|
79
|
+
/* Colors - use internal vars set by intent/variant */
|
|
80
|
+
background: var(--_bg);
|
|
81
|
+
color: var(--_text);
|
|
82
|
+
border-color: var(--_border);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Non-interactive default cursor */
|
|
86
|
+
span.stuic-pill {
|
|
87
|
+
cursor: default;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Interactive elements */
|
|
91
|
+
a.stuic-pill,
|
|
92
|
+
button.stuic-pill {
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Focus styles (interactive variants) */
|
|
97
|
+
a.stuic-pill:focus-visible,
|
|
98
|
+
button.stuic-pill:focus-visible {
|
|
99
|
+
outline: var(--stuic-pill-ring-width) solid
|
|
100
|
+
var(--_ring, var(--stuic-pill-ring-color));
|
|
101
|
+
outline-offset: var(--stuic-pill-ring-offset);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Hover state (interactive variants) */
|
|
105
|
+
a.stuic-pill:hover:not(:disabled),
|
|
106
|
+
button.stuic-pill:hover:not(:disabled) {
|
|
107
|
+
background: var(--_bg-hover);
|
|
108
|
+
color: var(--_text-hover);
|
|
109
|
+
border-color: var(--_border-hover);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Active state (interactive variants) */
|
|
113
|
+
a.stuic-pill:active:not(:disabled),
|
|
114
|
+
button.stuic-pill:active:not(:disabled) {
|
|
115
|
+
background: var(--_bg-active);
|
|
116
|
+
color: var(--_text-active);
|
|
117
|
+
border-color: var(--_border-active);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Disabled state */
|
|
121
|
+
.stuic-pill:disabled,
|
|
122
|
+
.stuic-pill[aria-disabled="true"] {
|
|
123
|
+
opacity: 0.5;
|
|
124
|
+
cursor: not-allowed;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ============================================================================
|
|
128
|
+
SIZE VARIANTS
|
|
129
|
+
============================================================================ */
|
|
130
|
+
|
|
131
|
+
.stuic-pill[data-size="sm"] {
|
|
132
|
+
padding: var(--stuic-pill-padding-y-sm) var(--stuic-pill-padding-x-sm);
|
|
133
|
+
font-size: var(--stuic-pill-font-size-sm);
|
|
134
|
+
min-height: var(--stuic-pill-min-height-sm);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.stuic-pill[data-size="md"] {
|
|
138
|
+
padding: var(--stuic-pill-padding-y-md) var(--stuic-pill-padding-x-md);
|
|
139
|
+
font-size: var(--stuic-pill-font-size-md);
|
|
140
|
+
min-height: var(--stuic-pill-min-height-md);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.stuic-pill[data-size="lg"] {
|
|
144
|
+
padding: var(--stuic-pill-padding-y-lg) var(--stuic-pill-padding-x-lg);
|
|
145
|
+
font-size: var(--stuic-pill-font-size-lg);
|
|
146
|
+
min-height: var(--stuic-pill-min-height-lg);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Dismissible pills: drop vertical padding — the X button already provides
|
|
150
|
+
visual height, extra padding-y just makes them oversized. */
|
|
151
|
+
.stuic-pill[data-dismissible="true"][data-size="sm"],
|
|
152
|
+
.stuic-pill[data-dismissible="true"][data-size="md"],
|
|
153
|
+
.stuic-pill[data-dismissible="true"][data-size="lg"] {
|
|
154
|
+
padding-top: 0;
|
|
155
|
+
padding-bottom: 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* ============================================================================
|
|
159
|
+
INTENT COLOR MAPPING
|
|
160
|
+
============================================================================ */
|
|
161
|
+
|
|
162
|
+
.stuic-pill:not([data-intent]) {
|
|
163
|
+
--_color: var(--stuic-color-muted);
|
|
164
|
+
--_color-hover: var(--stuic-color-muted-hover);
|
|
165
|
+
--_color-active: var(--stuic-color-muted-active);
|
|
166
|
+
--_fg: var(--stuic-color-foreground);
|
|
167
|
+
--_fg-hover: var(--stuic-color-foreground-hover);
|
|
168
|
+
--_fg-active: var(--stuic-color-foreground-active);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.stuic-pill[data-intent="primary"] {
|
|
172
|
+
--_color: var(--stuic-color-primary);
|
|
173
|
+
--_color-hover: var(--stuic-color-primary-hover);
|
|
174
|
+
--_color-active: var(--stuic-color-primary-active);
|
|
175
|
+
--_fg: var(--stuic-color-primary-foreground);
|
|
176
|
+
--_fg-hover: var(--stuic-color-primary-foreground-hover);
|
|
177
|
+
--_fg-active: var(--stuic-color-primary-foreground-active);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.stuic-pill[data-intent="accent"] {
|
|
181
|
+
--_color: var(--stuic-color-accent);
|
|
182
|
+
--_color-hover: var(--stuic-color-accent-hover);
|
|
183
|
+
--_color-active: var(--stuic-color-accent-active);
|
|
184
|
+
--_fg: var(--stuic-color-accent-foreground);
|
|
185
|
+
--_fg-hover: var(--stuic-color-accent-foreground-hover);
|
|
186
|
+
--_fg-active: var(--stuic-color-accent-foreground-active);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.stuic-pill[data-intent="destructive"] {
|
|
190
|
+
--_color: var(--stuic-color-destructive);
|
|
191
|
+
--_color-hover: var(--stuic-color-destructive-hover);
|
|
192
|
+
--_color-active: var(--stuic-color-destructive-active);
|
|
193
|
+
--_fg: var(--stuic-color-destructive-foreground);
|
|
194
|
+
--_fg-hover: var(--stuic-color-destructive-foreground-hover);
|
|
195
|
+
--_fg-active: var(--stuic-color-destructive-foreground-active);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.stuic-pill[data-intent="warning"] {
|
|
199
|
+
--_color: var(--stuic-color-warning);
|
|
200
|
+
--_color-hover: var(--stuic-color-warning-hover);
|
|
201
|
+
--_color-active: var(--stuic-color-warning-active);
|
|
202
|
+
--_fg: var(--stuic-color-warning-foreground);
|
|
203
|
+
--_fg-hover: var(--stuic-color-warning-foreground-hover);
|
|
204
|
+
--_fg-active: var(--stuic-color-warning-foreground-active);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.stuic-pill[data-intent="success"] {
|
|
208
|
+
--_color: var(--stuic-color-success);
|
|
209
|
+
--_color-hover: var(--stuic-color-success-hover);
|
|
210
|
+
--_color-active: var(--stuic-color-success-active);
|
|
211
|
+
--_fg: var(--stuic-color-success-foreground);
|
|
212
|
+
--_fg-hover: var(--stuic-color-success-foreground-hover);
|
|
213
|
+
--_fg-active: var(--stuic-color-success-foreground-active);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* ============================================================================
|
|
217
|
+
VARIANT STYLES
|
|
218
|
+
============================================================================ */
|
|
219
|
+
|
|
220
|
+
/* Solid: filled background with contrasting text */
|
|
221
|
+
.stuic-pill[data-variant="solid"] {
|
|
222
|
+
--_bg: var(--_color);
|
|
223
|
+
--_bg-hover: var(--_color-hover);
|
|
224
|
+
--_bg-active: var(--_color-active);
|
|
225
|
+
--_text: var(--_fg);
|
|
226
|
+
--_text-hover: var(--_fg-hover);
|
|
227
|
+
--_text-active: var(--_fg-active);
|
|
228
|
+
--_border: var(--_color);
|
|
229
|
+
--_border-hover: var(--_color-hover);
|
|
230
|
+
--_border-active: var(--_color-active);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* Outline: transparent background with colored border and text */
|
|
234
|
+
.stuic-pill[data-variant="outline"] {
|
|
235
|
+
--_bg: transparent;
|
|
236
|
+
--_bg-hover: color-mix(in srgb, var(--_color) 10%, var(--stuic-color-background));
|
|
237
|
+
--_bg-active: color-mix(in srgb, var(--_color) 20%, var(--stuic-color-background));
|
|
238
|
+
--_text: var(--_color);
|
|
239
|
+
--_text-hover: var(--_color-hover);
|
|
240
|
+
--_text-active: var(--_color-active);
|
|
241
|
+
--_border: var(--_color);
|
|
242
|
+
--_border-hover: var(--_color-hover);
|
|
243
|
+
--_border-active: var(--_color-active);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Ghost: transparent background, colored text, subtle hover state */
|
|
247
|
+
.stuic-pill[data-variant="ghost"] {
|
|
248
|
+
--_bg: transparent;
|
|
249
|
+
--_bg-hover: color-mix(in srgb, var(--_color) 10%, var(--stuic-color-background));
|
|
250
|
+
--_bg-active: color-mix(in srgb, var(--_color) 20%, var(--stuic-color-background));
|
|
251
|
+
--_text: var(--_color);
|
|
252
|
+
--_text-hover: var(--_color-hover);
|
|
253
|
+
--_text-active: var(--_color-active);
|
|
254
|
+
--_border: transparent;
|
|
255
|
+
--_border-hover: transparent;
|
|
256
|
+
--_border-active: transparent;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* Soft (default): muted background tint with colored text */
|
|
260
|
+
.stuic-pill[data-variant="soft"],
|
|
261
|
+
.stuic-pill:not([data-variant]) {
|
|
262
|
+
--_bg: color-mix(in srgb, var(--_color) 15%, var(--stuic-color-background));
|
|
263
|
+
--_bg-hover: color-mix(in srgb, var(--_color) 25%, var(--stuic-color-background));
|
|
264
|
+
--_bg-active: color-mix(in srgb, var(--_color) 35%, var(--stuic-color-background));
|
|
265
|
+
--_text: var(--_color);
|
|
266
|
+
--_text-hover: var(--_color-hover);
|
|
267
|
+
--_text-active: var(--_color-active);
|
|
268
|
+
--_border: transparent;
|
|
269
|
+
--_border-hover: transparent;
|
|
270
|
+
--_border-active: transparent;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Link: appears as inline text link, minimal styling */
|
|
274
|
+
.stuic-pill[data-variant="link"] {
|
|
275
|
+
--_bg: transparent;
|
|
276
|
+
--_bg-hover: transparent;
|
|
277
|
+
--_bg-active: transparent;
|
|
278
|
+
--_text: var(--_color);
|
|
279
|
+
--_text-hover: var(--_color-hover);
|
|
280
|
+
--_text-active: var(--_color-active);
|
|
281
|
+
--_border: transparent;
|
|
282
|
+
--_border-hover: transparent;
|
|
283
|
+
--_border-active: transparent;
|
|
284
|
+
text-decoration: underline;
|
|
285
|
+
text-underline-offset: 2px;
|
|
286
|
+
min-height: auto;
|
|
287
|
+
padding: 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* ============================================================================
|
|
291
|
+
DEFAULT INTENT OVERRIDES
|
|
292
|
+
The default intent uses neutral role tokens — adjust per variant for legibility.
|
|
293
|
+
============================================================================ */
|
|
294
|
+
|
|
295
|
+
.stuic-pill:not([data-intent])[data-variant="solid"] {
|
|
296
|
+
--_text: var(--stuic-color-foreground);
|
|
297
|
+
--_text-hover: var(--stuic-color-foreground-hover);
|
|
298
|
+
--_text-active: var(--stuic-color-foreground-active);
|
|
299
|
+
--_border: var(--stuic-color-border-hover);
|
|
300
|
+
--_border-hover: var(--stuic-color-border-active);
|
|
301
|
+
--_border-active: var(--stuic-color-border-active);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.stuic-pill:not([data-intent])[data-variant="outline"] {
|
|
305
|
+
--_text: var(--stuic-color-foreground);
|
|
306
|
+
--_text-hover: var(--stuic-color-foreground-hover);
|
|
307
|
+
--_text-active: var(--stuic-color-foreground-active);
|
|
308
|
+
--_border: var(--stuic-color-border-hover);
|
|
309
|
+
--_border-hover: var(--stuic-color-border-hover);
|
|
310
|
+
--_border-active: var(--stuic-color-border-active);
|
|
311
|
+
--_bg-hover: var(--stuic-color-muted-hover);
|
|
312
|
+
--_bg-active: var(--stuic-color-muted-active);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.stuic-pill:not([data-intent])[data-variant="ghost"] {
|
|
316
|
+
--_text: var(--stuic-color-foreground);
|
|
317
|
+
--_text-hover: var(--stuic-color-foreground-hover);
|
|
318
|
+
--_text-active: var(--stuic-color-foreground-active);
|
|
319
|
+
--_bg-hover: var(--stuic-color-muted-hover);
|
|
320
|
+
--_bg-active: var(--stuic-color-muted-active);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.stuic-pill:not([data-intent])[data-variant="soft"],
|
|
324
|
+
.stuic-pill:not([data-intent]):not([data-variant]) {
|
|
325
|
+
--_bg: var(--stuic-color-muted);
|
|
326
|
+
--_bg-hover: var(--stuic-color-muted-hover);
|
|
327
|
+
--_bg-active: var(--stuic-color-muted-active);
|
|
328
|
+
--_text: var(--stuic-color-foreground);
|
|
329
|
+
--_text-hover: var(--stuic-color-foreground-hover);
|
|
330
|
+
--_text-active: var(--stuic-color-foreground-active);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.stuic-pill:not([data-intent])[data-variant="link"] {
|
|
334
|
+
--_text: var(--stuic-color-muted-foreground);
|
|
335
|
+
--_text-hover: var(--stuic-color-foreground-hover);
|
|
336
|
+
--_text-active: var(--stuic-color-foreground-active);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* ============================================================================
|
|
340
|
+
MUTED MODIFIER
|
|
341
|
+
============================================================================ */
|
|
342
|
+
|
|
343
|
+
.stuic-pill[data-muted="true"] {
|
|
344
|
+
opacity: 0.7;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
a.stuic-pill[data-muted="true"]:hover:not(:disabled),
|
|
348
|
+
button.stuic-pill[data-muted="true"]:hover:not(:disabled) {
|
|
349
|
+
opacity: 0.85;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* ============================================================================
|
|
353
|
+
ACTIVE (selected) MODIFIER
|
|
354
|
+
Slight emphasis — useful for filter-chip toggleable behavior.
|
|
355
|
+
============================================================================ */
|
|
356
|
+
|
|
357
|
+
.stuic-pill[data-active="true"][data-variant="outline"],
|
|
358
|
+
.stuic-pill[data-active="true"][data-variant="ghost"],
|
|
359
|
+
.stuic-pill[data-active="true"][data-variant="soft"] {
|
|
360
|
+
--_bg: color-mix(in srgb, var(--_color) 25%, var(--stuic-color-background));
|
|
361
|
+
--_border: var(--_color);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.stuic-pill[data-active="true"][data-variant="solid"] {
|
|
365
|
+
--_bg: var(--_color-active);
|
|
366
|
+
--_border: var(--_color-active);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* ============================================================================
|
|
370
|
+
ROUNDED FULL (default)
|
|
371
|
+
============================================================================ */
|
|
372
|
+
|
|
373
|
+
.stuic-pill[data-rounded-full="true"] {
|
|
374
|
+
--stuic-pill-radius: 9999px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* ============================================================================
|
|
378
|
+
BLOCK MODIFIER
|
|
379
|
+
Block-level flex (full width) instead of the default inline-flex.
|
|
380
|
+
============================================================================ */
|
|
381
|
+
|
|
382
|
+
.stuic-pill[data-block="true"] {
|
|
383
|
+
display: flex;
|
|
384
|
+
width: 100%;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* ============================================================================
|
|
388
|
+
STATUS DOT
|
|
389
|
+
============================================================================ */
|
|
390
|
+
|
|
391
|
+
.stuic-pill-dot {
|
|
392
|
+
display: inline-block;
|
|
393
|
+
width: var(--stuic-pill-dot-size);
|
|
394
|
+
height: var(--stuic-pill-dot-size);
|
|
395
|
+
border-radius: 9999px;
|
|
396
|
+
background: var(--_color);
|
|
397
|
+
flex-shrink: 0;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* On solid variants the bg already IS the intent color — use foreground instead. */
|
|
401
|
+
.stuic-pill[data-variant="solid"] .stuic-pill-dot {
|
|
402
|
+
background: var(--_text);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* ============================================================================
|
|
406
|
+
DISMISSIBLE WRAPPER
|
|
407
|
+
When dismissible, the outer span is the styled pill; inner main + dismiss
|
|
408
|
+
button are siblings. Strip default styling from the inner interactive area.
|
|
409
|
+
============================================================================ */
|
|
410
|
+
|
|
411
|
+
.stuic-pill[data-dismissible="true"] {
|
|
412
|
+
gap: var(--stuic-pill-gap);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.stuic-pill-main {
|
|
416
|
+
display: inline-flex;
|
|
417
|
+
align-items: center;
|
|
418
|
+
justify-content: center;
|
|
419
|
+
gap: var(--stuic-pill-gap);
|
|
420
|
+
|
|
421
|
+
/* Reset native button/anchor defaults */
|
|
422
|
+
appearance: none;
|
|
423
|
+
background: transparent;
|
|
424
|
+
border: 0;
|
|
425
|
+
padding: 0;
|
|
426
|
+
margin: 0;
|
|
427
|
+
font: inherit;
|
|
428
|
+
color: inherit;
|
|
429
|
+
text-decoration: none;
|
|
430
|
+
cursor: pointer;
|
|
431
|
+
line-height: 1;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.stuic-pill-main:focus-visible {
|
|
435
|
+
outline: var(--stuic-pill-ring-width) solid
|
|
436
|
+
var(--_ring, var(--stuic-pill-ring-color));
|
|
437
|
+
outline-offset: 2px;
|
|
438
|
+
border-radius: 2px;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.stuic-pill-main:disabled {
|
|
442
|
+
cursor: not-allowed;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* Dismiss X button */
|
|
446
|
+
.stuic-pill-dismiss {
|
|
447
|
+
display: inline-flex;
|
|
448
|
+
align-items: center;
|
|
449
|
+
justify-content: center;
|
|
450
|
+
appearance: none;
|
|
451
|
+
background: transparent;
|
|
452
|
+
border: 0;
|
|
453
|
+
padding: 0;
|
|
454
|
+
margin: 0;
|
|
455
|
+
color: inherit;
|
|
456
|
+
cursor: pointer;
|
|
457
|
+
opacity: 0.7;
|
|
458
|
+
transition: opacity var(--stuic-pill-transition, var(--stuic-transition));
|
|
459
|
+
flex-shrink: 0;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.stuic-pill-dismiss:hover:not(:disabled) {
|
|
463
|
+
opacity: 1;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.stuic-pill-dismiss:focus-visible {
|
|
467
|
+
outline: var(--stuic-pill-ring-width) solid
|
|
468
|
+
var(--_ring, var(--stuic-pill-ring-color));
|
|
469
|
+
outline-offset: 1px;
|
|
470
|
+
border-radius: 9999px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.stuic-pill-dismiss:disabled {
|
|
474
|
+
opacity: 0.4;
|
|
475
|
+
cursor: not-allowed;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.stuic-pill-dismiss svg {
|
|
479
|
+
width: 1em;
|
|
480
|
+
height: 1em;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* Tighten dismiss icon size per pill size */
|
|
484
|
+
.stuic-pill[data-size="sm"] .stuic-pill-dismiss svg {
|
|
485
|
+
width: 0.875em;
|
|
486
|
+
height: 0.875em;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Pill, type Props as PillProps, type PillVariant, type PillSize, } from "./Pill.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Pill, } from "./Pill.svelte";
|
|
@@ -195,7 +195,6 @@
|
|
|
195
195
|
onButtonClick={(i) => {
|
|
196
196
|
billingPeriod = i === 0 ? "monthly" : "annual";
|
|
197
197
|
}}
|
|
198
|
-
{unstyled}
|
|
199
198
|
class={classToggleProp}
|
|
200
199
|
style="
|
|
201
200
|
width: auto;
|
|
@@ -229,7 +228,6 @@
|
|
|
229
228
|
class={tierClass}
|
|
230
229
|
data-highlighted={!unstyled && tier.highlighted ? "" : undefined}
|
|
231
230
|
data-disabled={!unstyled && tier.disabled ? "" : undefined}
|
|
232
|
-
aria-disabled={tier.disabled ? "true" : undefined}
|
|
233
231
|
role="listitem"
|
|
234
232
|
>
|
|
235
233
|
{#if tier.highlighted && tier.highlightedBadge}
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
|
|
194
194
|
<form bind:this={el} class={_class} use:onSubmitValidityCheck {...rest}>
|
|
195
195
|
<!-- General error alert -->
|
|
196
|
-
<DismissibleMessage message={error} intent="destructive"
|
|
196
|
+
<DismissibleMessage message={error} intent="destructive" />
|
|
197
197
|
|
|
198
198
|
<!-- Top-position extra fields -->
|
|
199
199
|
{#each topFields as cfg (cfg.name)}
|
package/dist/index.css
CHANGED
|
@@ -84,6 +84,7 @@ In practice:
|
|
|
84
84
|
@import "./components/Nav/index.css";
|
|
85
85
|
@import "./components/Notifications/index.css";
|
|
86
86
|
@import "./components/OtpInput/index.css";
|
|
87
|
+
@import "./components/Pill/index.css";
|
|
87
88
|
@import "./components/PricingTable/index.css";
|
|
88
89
|
@import "./components/Progress/index.css";
|
|
89
90
|
@import "./components/Separator/index.css";
|
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ export * from "./components/ModalDialog/index.js";
|
|
|
60
60
|
export * from "./components/Nav/index.js";
|
|
61
61
|
export * from "./components/Notifications/index.js";
|
|
62
62
|
export * from "./components/OtpInput/index.js";
|
|
63
|
+
export * from "./components/Pill/index.js";
|
|
63
64
|
export * from "./components/PricingTable/index.js";
|
|
64
65
|
export * from "./components/Progress/index.js";
|
|
65
66
|
export * from "./components/Separator/index.js";
|
package/dist/index.js
CHANGED
|
@@ -61,6 +61,7 @@ export * from "./components/ModalDialog/index.js";
|
|
|
61
61
|
export * from "./components/Nav/index.js";
|
|
62
62
|
export * from "./components/Notifications/index.js";
|
|
63
63
|
export * from "./components/OtpInput/index.js";
|
|
64
|
+
export * from "./components/Pill/index.js";
|
|
64
65
|
export * from "./components/PricingTable/index.js";
|
|
65
66
|
export * from "./components/Progress/index.js";
|
|
66
67
|
export * from "./components/Separator/index.js";
|
package/dist/mcp.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
56 Svelte 5 component directories with consistent API patterns. All use runes-based reactivity.
|
|
6
6
|
|
|
7
7
|
## Component Categories
|
|
8
8
|
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
| Component | Purpose |
|
|
73
73
|
| --------------- | ------------------------------------------------------------------- |
|
|
74
74
|
| Avatar | User avatars with fallback |
|
|
75
|
+
| Pill | Inline rounded badge/tag/chip (intent + variant + size, dismissible, dot, polymorphic span/a/button) |
|
|
75
76
|
| KbdShortcut | Keyboard shortcut hints |
|
|
76
77
|
| Carousel | Image/content slider with snap, keyboard nav, wheel scroll, arrows |
|
|
77
78
|
| ListItemButton | List item with actions |
|