@marianmeres/stuic 2.34.0 → 2.38.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/components/Collapsible/Collapsible.svelte +128 -0
- package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -0
- package/dist/components/Collapsible/README.md +81 -0
- package/dist/components/Collapsible/index.d.ts +1 -0
- package/dist/components/Collapsible/index.js +1 -0
- package/dist/components/Input/FieldCheckbox.svelte +17 -2
- package/dist/components/Input/_internal/InputWrap.svelte +7 -31
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
4
|
+
import { tooltip } from "../../actions/index.js";
|
|
5
|
+
import { isPlainObject } from "../../utils/is-plain-object.js";
|
|
6
|
+
import { replaceMap } from "../../utils/replace-map.js";
|
|
7
|
+
import type { TranslateFn } from "../../types.js";
|
|
8
|
+
|
|
9
|
+
export interface Props {
|
|
10
|
+
/** Content to display */
|
|
11
|
+
children: Snippet;
|
|
12
|
+
/** Number of lines to show when collapsed (default: 1) */
|
|
13
|
+
lines?: number;
|
|
14
|
+
/** Expanded state (bindable) */
|
|
15
|
+
expanded?: boolean;
|
|
16
|
+
/** Collapsed indicator character (default: "↓") */
|
|
17
|
+
collapsedIndicator?: string;
|
|
18
|
+
/** Expanded indicator character (default: "↑") */
|
|
19
|
+
expandedIndicator?: string;
|
|
20
|
+
/** Container class */
|
|
21
|
+
class?: string;
|
|
22
|
+
/** Content wrapper class */
|
|
23
|
+
classContent?: string;
|
|
24
|
+
/** Toggle button class */
|
|
25
|
+
classToggle?: string;
|
|
26
|
+
/** Opacity class for toggle button (default: "opacity-70") */
|
|
27
|
+
toggleOpacity?: string;
|
|
28
|
+
/** Bind reference to container element */
|
|
29
|
+
el?: HTMLDivElement;
|
|
30
|
+
/** Optional translate function */
|
|
31
|
+
t?: TranslateFn;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// i18n ready
|
|
35
|
+
function t_default(
|
|
36
|
+
k: string,
|
|
37
|
+
values: false | null | undefined | Record<string, string | number> = null,
|
|
38
|
+
fallback: string | boolean = "",
|
|
39
|
+
i18nSpanWrap: boolean = true
|
|
40
|
+
) {
|
|
41
|
+
const m: Record<string, string> = {
|
|
42
|
+
more: "Show more...",
|
|
43
|
+
less: "Show less...",
|
|
44
|
+
};
|
|
45
|
+
let out = m[k] ?? fallback ?? k;
|
|
46
|
+
|
|
47
|
+
return isPlainObject(values) ? replaceMap(out, values as any) : out;
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<script lang="ts">
|
|
52
|
+
let {
|
|
53
|
+
children,
|
|
54
|
+
lines = 1,
|
|
55
|
+
expanded = $bindable(false),
|
|
56
|
+
collapsedIndicator = "↓",
|
|
57
|
+
expandedIndicator = "↑",
|
|
58
|
+
class: classProp,
|
|
59
|
+
classContent,
|
|
60
|
+
classToggle,
|
|
61
|
+
toggleOpacity = "opacity-75",
|
|
62
|
+
el = $bindable(),
|
|
63
|
+
t = t_default,
|
|
64
|
+
}: Props = $props();
|
|
65
|
+
|
|
66
|
+
let contentEl: HTMLDivElement | undefined;
|
|
67
|
+
let containerWidth = $state(0);
|
|
68
|
+
let needsCollapse = $state(false);
|
|
69
|
+
|
|
70
|
+
$effect(() => {
|
|
71
|
+
// Only measure when collapsed (line-clamp applied) to detect if truncation is needed
|
|
72
|
+
// containerWidth dependency ensures re-measurement on resize
|
|
73
|
+
if (contentEl && !expanded && containerWidth) {
|
|
74
|
+
needsCollapse = contentEl.scrollHeight > contentEl.clientHeight;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// normalize, range validation
|
|
79
|
+
let _lines = $derived.by(() => {
|
|
80
|
+
const l = Math.abs(lines);
|
|
81
|
+
return l > 10 ? 10 : l;
|
|
82
|
+
});
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<div
|
|
86
|
+
bind:this={el}
|
|
87
|
+
bind:clientWidth={containerWidth}
|
|
88
|
+
class={twMerge("stuic-collapsible", classProp)}
|
|
89
|
+
>
|
|
90
|
+
<div class="flex items-end">
|
|
91
|
+
<div
|
|
92
|
+
bind:this={contentEl}
|
|
93
|
+
class={twMerge("flex-1", !expanded && `line-clamp-${_lines}`, classContent)}
|
|
94
|
+
>
|
|
95
|
+
{@render children()}
|
|
96
|
+
</div>
|
|
97
|
+
{#if needsCollapse}
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
class={twMerge(
|
|
101
|
+
toggleOpacity,
|
|
102
|
+
"hover:opacity-100 cursor-pointer px-2 py-1 -my-1 -mr-2",
|
|
103
|
+
classToggle
|
|
104
|
+
)}
|
|
105
|
+
onclick={() => (expanded = !expanded)}
|
|
106
|
+
use:tooltip={() => ({
|
|
107
|
+
content: expanded ? t("less") : t("more"),
|
|
108
|
+
})}
|
|
109
|
+
>
|
|
110
|
+
{expanded ? expandedIndicator : collapsedIndicator}
|
|
111
|
+
</button>
|
|
112
|
+
{/if}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!--
|
|
117
|
+
DO NOT REMOVE: Food for TW compiler
|
|
118
|
+
line-clamp-1
|
|
119
|
+
line-clamp-2
|
|
120
|
+
line-clamp-3
|
|
121
|
+
line-clamp-4
|
|
122
|
+
line-clamp-5
|
|
123
|
+
line-clamp-6
|
|
124
|
+
line-clamp-7
|
|
125
|
+
line-clamp-8
|
|
126
|
+
line-clamp-9
|
|
127
|
+
line-clamp-10
|
|
128
|
+
-->
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { TranslateFn } from "../../types.js";
|
|
3
|
+
export interface Props {
|
|
4
|
+
/** Content to display */
|
|
5
|
+
children: Snippet;
|
|
6
|
+
/** Number of lines to show when collapsed (default: 1) */
|
|
7
|
+
lines?: number;
|
|
8
|
+
/** Expanded state (bindable) */
|
|
9
|
+
expanded?: boolean;
|
|
10
|
+
/** Collapsed indicator character (default: "↓") */
|
|
11
|
+
collapsedIndicator?: string;
|
|
12
|
+
/** Expanded indicator character (default: "↑") */
|
|
13
|
+
expandedIndicator?: string;
|
|
14
|
+
/** Container class */
|
|
15
|
+
class?: string;
|
|
16
|
+
/** Content wrapper class */
|
|
17
|
+
classContent?: string;
|
|
18
|
+
/** Toggle button class */
|
|
19
|
+
classToggle?: string;
|
|
20
|
+
/** Opacity class for toggle button (default: "opacity-70") */
|
|
21
|
+
toggleOpacity?: string;
|
|
22
|
+
/** Bind reference to container element */
|
|
23
|
+
el?: HTMLDivElement;
|
|
24
|
+
/** Optional translate function */
|
|
25
|
+
t?: TranslateFn;
|
|
26
|
+
}
|
|
27
|
+
declare const Collapsible: import("svelte").Component<Props, {}, "el" | "expanded">;
|
|
28
|
+
type Collapsible = ReturnType<typeof Collapsible>;
|
|
29
|
+
export default Collapsible;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Collapsible
|
|
2
|
+
|
|
3
|
+
A component that truncates content to a specified number of lines with an expand/collapse toggle. Automatically detects if truncation is needed and only shows the toggle when content overflows.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
| -------------------- | --------------- | ------------- | ------------------------------------------------ |
|
|
9
|
+
| `children` | `Snippet` | - | Content to display |
|
|
10
|
+
| `lines` | `number` | `1` | Number of lines to show when collapsed |
|
|
11
|
+
| `expanded` | `boolean` | `false` | Expanded state (bindable) |
|
|
12
|
+
| `collapsedIndicator` | `string` | `"↓"` | Character/text shown when collapsed |
|
|
13
|
+
| `expandedIndicator` | `string` | `"↑"` | Character/text shown when expanded |
|
|
14
|
+
| `class` | `string` | - | Container element class |
|
|
15
|
+
| `classContent` | `string` | - | Content wrapper class |
|
|
16
|
+
| `classToggle` | `string` | - | Toggle button class |
|
|
17
|
+
| `toggleOpacity` | `string` | `"opacity-70"`| Opacity class for toggle button |
|
|
18
|
+
| `el` | `HTMLDivElement`| - | Bind reference to container element |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Usage
|
|
23
|
+
|
|
24
|
+
```svelte
|
|
25
|
+
<script>
|
|
26
|
+
import { Collapsible } from 'stuic';
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<Collapsible>
|
|
30
|
+
This is a long text that will be truncated to one line with an ellipsis
|
|
31
|
+
and a toggle button to expand it.
|
|
32
|
+
</Collapsible>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Multiple Lines
|
|
36
|
+
|
|
37
|
+
```svelte
|
|
38
|
+
<Collapsible lines={3}>
|
|
39
|
+
This content will be truncated to 3 lines before showing the expand toggle.
|
|
40
|
+
Add more content here to see the effect.
|
|
41
|
+
</Collapsible>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Custom Indicators
|
|
45
|
+
|
|
46
|
+
```svelte
|
|
47
|
+
<Collapsible collapsedIndicator="▼" expandedIndicator="▲">
|
|
48
|
+
Content with custom expand/collapse indicators.
|
|
49
|
+
</Collapsible>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Controlled State
|
|
53
|
+
|
|
54
|
+
```svelte
|
|
55
|
+
<script>
|
|
56
|
+
import { Collapsible } from 'stuic';
|
|
57
|
+
|
|
58
|
+
let expanded = $state(false);
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<button onclick={() => expanded = !expanded}>
|
|
62
|
+
Toggle externally
|
|
63
|
+
</button>
|
|
64
|
+
|
|
65
|
+
<Collapsible bind:expanded>
|
|
66
|
+
This collapsible can be controlled from outside.
|
|
67
|
+
</Collapsible>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Custom Styling
|
|
71
|
+
|
|
72
|
+
```svelte
|
|
73
|
+
<Collapsible
|
|
74
|
+
class="bg-gray-100 p-4 rounded"
|
|
75
|
+
classContent="text-sm text-gray-600"
|
|
76
|
+
classToggle="text-blue-500 font-bold"
|
|
77
|
+
toggleOpacity="opacity-100"
|
|
78
|
+
>
|
|
79
|
+
Styled collapsible content.
|
|
80
|
+
</Collapsible>
|
|
81
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Collapsible, type Props as CollapsibleProps, } from "./Collapsible.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Collapsible, } from "./Collapsible.svelte";
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
import { getId } from "../../utils/get-id.js";
|
|
37
37
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
38
38
|
import { Thc, isTHCNotEmpty } from "../Thc/index.js";
|
|
39
|
+
import { Collapsible } from "../Collapsible/index.js";
|
|
39
40
|
type THC = import("../Thc/index.js").THC;
|
|
40
41
|
|
|
41
42
|
let {
|
|
@@ -183,7 +184,21 @@
|
|
|
183
184
|
</div>
|
|
184
185
|
{/if}
|
|
185
186
|
{#if description}
|
|
186
|
-
<
|
|
187
|
+
<Collapsible>
|
|
188
|
+
<div
|
|
189
|
+
id={idDesc}
|
|
190
|
+
class={twMerge(
|
|
191
|
+
"desc-box",
|
|
192
|
+
_classCommon,
|
|
193
|
+
"text-sm opacity-50 cursor-pointer font-normal",
|
|
194
|
+
disabled && "cursor-not-allowed",
|
|
195
|
+
classDescBox
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
198
|
+
{@render snippetOrThc({ id, value: description })}
|
|
199
|
+
</div>
|
|
200
|
+
</Collapsible>
|
|
201
|
+
<!-- <div
|
|
187
202
|
id={idDesc}
|
|
188
203
|
class={twMerge(
|
|
189
204
|
"desc-box",
|
|
@@ -194,7 +209,7 @@
|
|
|
194
209
|
)}
|
|
195
210
|
>
|
|
196
211
|
{@render snippetOrThc({ id, value: description })}
|
|
197
|
-
</div>
|
|
212
|
+
</div> -->
|
|
198
213
|
{/if}
|
|
199
214
|
</div>
|
|
200
215
|
</label>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { slide } from "svelte/transition";
|
|
4
4
|
import type { ValidationResult } from "../../../actions/validate.svelte.js";
|
|
5
5
|
import { twMerge } from "../../../utils/tw-merge.js";
|
|
6
|
+
import { Collapsible } from "../../Collapsible/index.js";
|
|
6
7
|
import Thc, { isTHCNotEmpty, type THC } from "../../Thc/Thc.svelte";
|
|
7
8
|
|
|
8
9
|
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
@@ -78,9 +79,6 @@
|
|
|
78
79
|
let invalid = $derived(validation && !validation?.valid);
|
|
79
80
|
|
|
80
81
|
let width = $state<number>(0);
|
|
81
|
-
let descExpanded = $state(descriptionDefaultExpanded);
|
|
82
|
-
let descContentEl: HTMLDivElement | undefined;
|
|
83
|
-
let descNeedsCollapse = $state(false);
|
|
84
82
|
|
|
85
83
|
$effect(() => {
|
|
86
84
|
// a non-zero breakpoint has priority
|
|
@@ -89,14 +87,6 @@
|
|
|
89
87
|
}
|
|
90
88
|
});
|
|
91
89
|
|
|
92
|
-
$effect(() => {
|
|
93
|
-
// only measure when collapsed (line-clamp applied) to detect if truncation is needed
|
|
94
|
-
// width dependency ensures re-measurement on resize
|
|
95
|
-
if (descContentEl && descriptionCollapsible && !descExpanded && width) {
|
|
96
|
-
descNeedsCollapse = descContentEl.scrollHeight > descContentEl.clientHeight;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
90
|
let _classCommon = $derived(
|
|
101
91
|
[invalid && "invalid", disabled && "disabled", required && "required", size]
|
|
102
92
|
.filter(Boolean)
|
|
@@ -232,26 +222,12 @@
|
|
|
232
222
|
)}
|
|
233
223
|
>
|
|
234
224
|
{#if descriptionCollapsible}
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
</div>
|
|
242
|
-
{#if descNeedsCollapse}
|
|
243
|
-
<button
|
|
244
|
-
type="button"
|
|
245
|
-
class={twMerge(
|
|
246
|
-
"opacity-70 hover:opacity-100 cursor-pointer px-2 py-1 -my-1 -mr-2",
|
|
247
|
-
classDescBoxToggle
|
|
248
|
-
)}
|
|
249
|
-
onclick={() => (descExpanded = !descExpanded)}
|
|
250
|
-
>
|
|
251
|
-
{descExpanded ? "↑" : "↓"}
|
|
252
|
-
</button>
|
|
253
|
-
{/if}
|
|
254
|
-
</div>
|
|
225
|
+
<Collapsible
|
|
226
|
+
expanded={descriptionDefaultExpanded}
|
|
227
|
+
classToggle={classDescBoxToggle}
|
|
228
|
+
>
|
|
229
|
+
{@render snippetOrThc({ id, value: description })}
|
|
230
|
+
</Collapsible>
|
|
255
231
|
{:else}
|
|
256
232
|
{@render snippetOrThc({ id, value: description })}
|
|
257
233
|
{/if}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export * from "./components/AvatarInitials/index.js";
|
|
|
28
28
|
export * from "./components/Backdrop/index.js";
|
|
29
29
|
export * from "./components/Button/index.js";
|
|
30
30
|
export * from "./components/ButtonGroupRadio/index.js";
|
|
31
|
+
export * from "./components/Collapsible/index.js";
|
|
31
32
|
export * from "./components/ColorScheme/index.js";
|
|
32
33
|
export * from "./components/CommandMenu/index.js";
|
|
33
34
|
export * from "./components/DismissibleMessage/index.js";
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./components/AvatarInitials/index.js";
|
|
|
29
29
|
export * from "./components/Backdrop/index.js";
|
|
30
30
|
export * from "./components/Button/index.js";
|
|
31
31
|
export * from "./components/ButtonGroupRadio/index.js";
|
|
32
|
+
export * from "./components/Collapsible/index.js";
|
|
32
33
|
export * from "./components/ColorScheme/index.js";
|
|
33
34
|
export * from "./components/CommandMenu/index.js";
|
|
34
35
|
export * from "./components/DismissibleMessage/index.js";
|