@casinogate/ui 1.5.1 → 1.5.3
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/assets/css/root.css +20 -30
- package/dist/components/field/components/field.label.svelte +8 -0
- package/dist/components/field/components/field.root.svelte +9 -2
- package/dist/components/field/components/field.svelte.d.ts +2 -0
- package/dist/components/field/components/field.svelte.js +2 -0
- package/dist/components/field/field.stories.svelte +8 -3
- package/dist/components/field/styles.d.ts +3 -3
- package/dist/components/field/styles.js +6 -1
- package/dist/components/field/types.d.ts +5 -0
- package/dist/components/icons/asterisk.svelte +12 -0
- package/dist/components/icons/asterisk.svelte.d.ts +3 -0
- package/dist/components/icons/exports.d.ts +1 -0
- package/dist/components/icons/exports.js +1 -0
- package/dist/components/select/components/select.content.svelte +19 -1
- package/dist/components/select/components/select.content.svelte.d.ts +2 -2
- package/dist/components/select/components/select.trigger.svelte +1 -1
- package/dist/components/select/exports.d.ts +1 -0
- package/dist/components/select/exports.js +1 -0
- package/dist/components/select/index.d.ts +1 -1
- package/dist/components/select/select.async.svelte +204 -0
- package/dist/components/select/select.async.svelte.d.ts +4 -0
- package/dist/components/select/select.stories.svelte +48 -1
- package/dist/components/select/select.svelte +18 -30
- package/dist/components/select/styles.js +9 -5
- package/dist/components/select/types.d.ts +18 -1
- package/dist/components/select/utils/get-item-key.d.ts +2 -0
- package/dist/components/select/utils/get-item-key.js +7 -0
- package/dist/components/select/utils/get-label-from-value.d.ts +2 -0
- package/dist/components/select/utils/get-label-from-value.js +23 -0
- package/dist/components/select/utils/index.d.ts +2 -0
- package/dist/components/select/utils/index.js +2 -0
- package/package.json +1 -1
package/dist/assets/css/root.css
CHANGED
|
@@ -312,6 +312,9 @@
|
|
|
312
312
|
.cgui\:h-\(--bits-select-anchor-height\) {
|
|
313
313
|
height: var(--bits-select-anchor-height);
|
|
314
314
|
}
|
|
315
|
+
.cgui\:h-\(--cg-ui-max-content-height\) {
|
|
316
|
+
height: var(--cg-ui-max-content-height);
|
|
317
|
+
}
|
|
315
318
|
.cgui\:h-3 {
|
|
316
319
|
height: calc(var(--cgui-spacing) * 3);
|
|
317
320
|
}
|
|
@@ -357,6 +360,9 @@
|
|
|
357
360
|
.cgui\:h-20 {
|
|
358
361
|
height: calc(var(--cgui-spacing) * 20);
|
|
359
362
|
}
|
|
363
|
+
.cgui\:h-30 {
|
|
364
|
+
height: calc(var(--cgui-spacing) * 30);
|
|
365
|
+
}
|
|
360
366
|
.cgui\:h-34 {
|
|
361
367
|
height: calc(var(--cgui-spacing) * 34);
|
|
362
368
|
}
|
|
@@ -543,6 +549,9 @@
|
|
|
543
549
|
.cgui\:justify-end {
|
|
544
550
|
justify-content: flex-end;
|
|
545
551
|
}
|
|
552
|
+
.cgui\:gap-0\.5 {
|
|
553
|
+
gap: calc(var(--cgui-spacing) * 0.5);
|
|
554
|
+
}
|
|
546
555
|
.cgui\:gap-1 {
|
|
547
556
|
gap: calc(var(--cgui-spacing) * 1);
|
|
548
557
|
}
|
|
@@ -1044,6 +1053,9 @@
|
|
|
1044
1053
|
.cgui\:scrollbar-thumb-rounded-md {
|
|
1045
1054
|
--scrollbar-thumb-radius: calc(var(--cg-ui-number-md) * 1px);
|
|
1046
1055
|
}
|
|
1056
|
+
.cgui\:scrollbar-thumb-stroke-default {
|
|
1057
|
+
--scrollbar-thumb: var(--cg-ui-palette-neutral-40);
|
|
1058
|
+
}
|
|
1047
1059
|
.cgui\:scrollbar-thumb-surface-regular {
|
|
1048
1060
|
--scrollbar-thumb: var(--cg-ui-palette-neutral-50);
|
|
1049
1061
|
}
|
|
@@ -1062,6 +1074,9 @@
|
|
|
1062
1074
|
.cgui\:scrollbar-track-surface-lightest {
|
|
1063
1075
|
--scrollbar-track: var(--cg-ui-palette-neutral-10);
|
|
1064
1076
|
}
|
|
1077
|
+
.cgui\:scrollbar-track-transparent {
|
|
1078
|
+
--scrollbar-track: transparent;
|
|
1079
|
+
}
|
|
1065
1080
|
.cgui\:group-has-\[\[data-slot\=\"header\"\]\]\/appShell\:h-\[calc\(100\%-var\(--app-shell-header-height\)\)\] {
|
|
1066
1081
|
&:is(:where(.cgui\:group\/appShell):has(*:is([data-slot="header"])) *) {
|
|
1067
1082
|
height: calc(100% - var(--app-shell-header-height));
|
|
@@ -1336,21 +1351,6 @@
|
|
|
1336
1351
|
opacity: 50%;
|
|
1337
1352
|
}
|
|
1338
1353
|
}
|
|
1339
|
-
.cgui\:data-\[end-chevron\]\:right-2\.5 {
|
|
1340
|
-
&[data-end-chevron] {
|
|
1341
|
-
right: calc(var(--cgui-spacing) * 2.5);
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
.cgui\:data-\[end-chevron\]\:right-3 {
|
|
1345
|
-
&[data-end-chevron] {
|
|
1346
|
-
right: calc(var(--cgui-spacing) * 3);
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
.cgui\:data-\[end-chevron\]\:right-4 {
|
|
1350
|
-
&[data-end-chevron] {
|
|
1351
|
-
right: calc(var(--cgui-spacing) * 4);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
1354
|
.cgui\:data-\[end-chevron\]\:pr-7 {
|
|
1355
1355
|
&[data-end-chevron] {
|
|
1356
1356
|
padding-right: calc(var(--cgui-spacing) * 7);
|
|
@@ -1371,6 +1371,11 @@
|
|
|
1371
1371
|
padding-right: calc(var(--cgui-spacing) * 14);
|
|
1372
1372
|
}
|
|
1373
1373
|
}
|
|
1374
|
+
.cgui\:data-\[highlighted\]\:bg-surface-lightest {
|
|
1375
|
+
&[data-highlighted] {
|
|
1376
|
+
background-color: var(--cg-ui-palette-neutral-10);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1374
1379
|
.cgui\:data-\[orientation\=horizontal\]\:h-px {
|
|
1375
1380
|
&[data-orientation="horizontal"] {
|
|
1376
1381
|
height: 1px;
|
|
@@ -1475,21 +1480,6 @@
|
|
|
1475
1480
|
left: calc(var(--cgui-spacing) * 2);
|
|
1476
1481
|
}
|
|
1477
1482
|
}
|
|
1478
|
-
.cgui\:data-\[start-chevron\]\:left-2\.5 {
|
|
1479
|
-
&[data-start-chevron] {
|
|
1480
|
-
left: calc(var(--cgui-spacing) * 2.5);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
.cgui\:data-\[start-chevron\]\:left-3 {
|
|
1484
|
-
&[data-start-chevron] {
|
|
1485
|
-
left: calc(var(--cgui-spacing) * 3);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
.cgui\:data-\[start-chevron\]\:left-4 {
|
|
1489
|
-
&[data-start-chevron] {
|
|
1490
|
-
left: calc(var(--cgui-spacing) * 4);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
1483
|
.cgui\:data-\[start-chevron\]\:pl-8 {
|
|
1494
1484
|
&[data-start-chevron] {
|
|
1495
1485
|
padding-left: calc(var(--cgui-spacing) * 8);
|
|
@@ -39,5 +39,13 @@
|
|
|
39
39
|
{:else}
|
|
40
40
|
<label {...mergedProps}>
|
|
41
41
|
{@render children?.()}
|
|
42
|
+
|
|
43
|
+
{#if labelState.root.opts.required.current}
|
|
44
|
+
<span
|
|
45
|
+
class=" cgui:inline-flex cgui:items-center cgui:justify-center cgui:text-caption cgui:font-medium cgui:text-fg-error cgui:shrink-0 cgui:align-middle"
|
|
46
|
+
>
|
|
47
|
+
*
|
|
48
|
+
</span>
|
|
49
|
+
{/if}
|
|
42
50
|
</label>
|
|
43
51
|
{/if}
|
|
@@ -37,12 +37,19 @@
|
|
|
37
37
|
FieldStylesContext.set(box.with(() => variants));
|
|
38
38
|
|
|
39
39
|
const mergedProps = $derived(mergeProps(restProps, rootState.props, { class: cn(variants.root(), className) }));
|
|
40
|
+
|
|
41
|
+
const attrStates = $derived({
|
|
42
|
+
invalid: rootState.opts.invalid.current,
|
|
43
|
+
disabled: rootState.opts.disabled.current,
|
|
44
|
+
readOnly: rootState.opts.readOnly.current,
|
|
45
|
+
required: rootState.opts.required.current,
|
|
46
|
+
});
|
|
40
47
|
</script>
|
|
41
48
|
|
|
42
49
|
{#if child}
|
|
43
|
-
{@render child({ props: mergedProps })}
|
|
50
|
+
{@render child({ props: mergedProps, ...attrStates })}
|
|
44
51
|
{:else}
|
|
45
52
|
<div {...mergedProps}>
|
|
46
|
-
{@render children?.()}
|
|
53
|
+
{@render children?.(attrStates)}
|
|
47
54
|
</div>
|
|
48
55
|
{/if}
|
|
@@ -122,5 +122,7 @@ export class FieldControlState {
|
|
|
122
122
|
props = $derived.by(() => ({
|
|
123
123
|
id: this.opts.id.current,
|
|
124
124
|
'aria-invalid': this.root.opts.invalid.current,
|
|
125
|
+
disabled: this.root.opts.disabled.current,
|
|
126
|
+
readOnly: this.root.opts.readOnly.current,
|
|
125
127
|
}));
|
|
126
128
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const parameters: Parameters = {
|
|
9
9
|
controls: {
|
|
10
|
-
include: [],
|
|
10
|
+
include: ['invalid', 'disabled', 'required', 'readOnly'],
|
|
11
11
|
},
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
|
|
20
20
|
type Args = ComponentProps<typeof FieldPrimitive.Root>;
|
|
21
21
|
|
|
22
|
-
const args: Args = {
|
|
22
|
+
const args: Args = {
|
|
23
|
+
invalid: false,
|
|
24
|
+
disabled: false,
|
|
25
|
+
required: false,
|
|
26
|
+
readOnly: false,
|
|
27
|
+
};
|
|
23
28
|
</script>
|
|
24
29
|
|
|
25
30
|
<Story name="Basic" {args} {parameters}>
|
|
@@ -34,7 +39,7 @@
|
|
|
34
39
|
|
|
35
40
|
<Story name="Primitive" {args} {parameters}>
|
|
36
41
|
{#snippet template(args: Args)}
|
|
37
|
-
<FieldPrimitive.Root {...args} invalid>
|
|
42
|
+
<FieldPrimitive.Root {...args} invalid required>
|
|
38
43
|
<FieldPrimitive.Label>Label</FieldPrimitive.Label>
|
|
39
44
|
|
|
40
45
|
<FieldPrimitive.Control>
|
|
@@ -21,7 +21,7 @@ export declare const fieldStyles: import("tailwind-variants").TVReturnType<{
|
|
|
21
21
|
};
|
|
22
22
|
} | {}, {
|
|
23
23
|
root: string;
|
|
24
|
-
label: string;
|
|
24
|
+
label: string[];
|
|
25
25
|
error: string;
|
|
26
26
|
description: string;
|
|
27
27
|
}, undefined, {
|
|
@@ -35,12 +35,12 @@ export declare const fieldStyles: import("tailwind-variants").TVReturnType<{
|
|
|
35
35
|
};
|
|
36
36
|
} | {}, {
|
|
37
37
|
root: string;
|
|
38
|
-
label: string;
|
|
38
|
+
label: string[];
|
|
39
39
|
error: string;
|
|
40
40
|
description: string;
|
|
41
41
|
}, import("tailwind-variants").TVReturnType<unknown, {
|
|
42
42
|
root: string;
|
|
43
|
-
label: string;
|
|
43
|
+
label: string[];
|
|
44
44
|
error: string;
|
|
45
45
|
description: string;
|
|
46
46
|
}, undefined, unknown, unknown, undefined>>;
|
|
@@ -4,7 +4,12 @@ import { Context } from 'runed';
|
|
|
4
4
|
export const fieldStyles = tv({
|
|
5
5
|
slots: {
|
|
6
6
|
root: 'cgui:flex cgui:flex-col cgui:gap-1',
|
|
7
|
-
label:
|
|
7
|
+
label: [
|
|
8
|
+
'cgui:relative',
|
|
9
|
+
'cgui:w-fit',
|
|
10
|
+
'cgui:inline-flex cgui:items-center cgui:gap-0.5',
|
|
11
|
+
'cgui:text-fg-medium cgui:text-caption cgui:font-medium',
|
|
12
|
+
],
|
|
8
13
|
error: 'cgui:text-caption cgui:text-fg-error',
|
|
9
14
|
description: 'cgui:text-caption cgui:text-fg-medium',
|
|
10
15
|
},
|
|
@@ -6,6 +6,11 @@ type FieldRootPropsWithoutHTML = WithElementRef<WithChild<{
|
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
readOnly?: boolean;
|
|
8
8
|
required?: boolean;
|
|
9
|
+
}, {
|
|
10
|
+
invalid: boolean;
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
readOnly: boolean;
|
|
13
|
+
required: boolean;
|
|
9
14
|
}>>;
|
|
10
15
|
export type FieldRootProps = FieldRootPropsWithoutHTML & Without<PrimitiveDivAttributes, FieldRootPropsWithoutHTML>;
|
|
11
16
|
export type FieldControlProps = {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { IconProps } from './types.js';
|
|
3
|
+
|
|
4
|
+
let { width = 24, height = 24, color = 'currentColor', ...props }: IconProps = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<svg xmlns="http://www.w3.org/2000/svg" {width} {height} viewBox="0 0 24 24" {...props}>
|
|
8
|
+
<path
|
|
9
|
+
fill={color}
|
|
10
|
+
d="M21 13h-6.6l4.7 4.7l-1.4 1.4l-4.7-4.7V21h-2v-6.7L6.3 19l-1.4-1.4L9.4 13H3v-2h6.6L4.9 6.3l1.4-1.4L11 9.6V3h2v6.4l4.6-4.6L19 6.3L14.3 11H21z"
|
|
11
|
+
/>
|
|
12
|
+
</svg>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../../../internal/utils/common.js';
|
|
3
3
|
import { Select as SelectPrimitive } from 'bits-ui';
|
|
4
|
+
import type { Attachment } from 'svelte/attachments';
|
|
4
5
|
import { SelectStylesContext } from '../styles.js';
|
|
5
6
|
import type { SelectContentProps } from '../types.js';
|
|
6
7
|
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
children,
|
|
12
13
|
side = 'bottom',
|
|
13
14
|
forceMount = false,
|
|
15
|
+
maxContentHeight,
|
|
14
16
|
...restProps
|
|
15
17
|
}: SelectContentProps = $props();
|
|
16
18
|
|
|
@@ -24,8 +26,24 @@
|
|
|
24
26
|
side,
|
|
25
27
|
...restProps,
|
|
26
28
|
});
|
|
29
|
+
|
|
30
|
+
const contentStyle = $derived.by(() => {
|
|
31
|
+
return {
|
|
32
|
+
...(maxContentHeight ? { '--cg-ui-max-content-height': maxContentHeight } : {}),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const contentMount: Attachment<HTMLDivElement> = (el) => {
|
|
37
|
+
$effect.pre(() => {
|
|
38
|
+
if (!el) return;
|
|
39
|
+
|
|
40
|
+
Object.entries(contentStyle).forEach(([key, value]) => {
|
|
41
|
+
el.style.setProperty(key, value);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
};
|
|
27
45
|
</script>
|
|
28
46
|
|
|
29
|
-
<SelectPrimitive.Content bind:ref {...attrs}>
|
|
47
|
+
<SelectPrimitive.Content bind:ref {...attrs} {@attach contentMount}>
|
|
30
48
|
{@render children?.()}
|
|
31
49
|
</SelectPrimitive.Content>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
declare const Select: import("svelte").Component<
|
|
1
|
+
import type { SelectContentProps } from '../types.js';
|
|
2
|
+
declare const Select: import("svelte").Component<SelectContentProps, {}, "ref">;
|
|
3
3
|
type Select = ReturnType<typeof Select>;
|
|
4
4
|
export default Select;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * as SelectPrimitive from './exports-primitive.js';
|
|
2
2
|
export * as Select from './exports.js';
|
|
3
|
-
export type { SelectContentProps, SelectGroupHeadingProps, SelectGroupProps, SelectItem, SelectItemData, SelectItemGroup, SelectItemProps, SelectRootProps, SelectTriggerProps, SelectViewportProps, } from './types.js';
|
|
3
|
+
export type { SelectAsyncCallback, SelectAsyncCallbackParams, SelectAsyncCallbackResult, SelectAsyncProps, SelectContentProps, SelectGroupHeadingProps, SelectGroupProps, SelectItem, SelectItemData, SelectItemGroup, SelectItemProps, SelectRootProps, SelectTriggerProps, SelectViewportProps, } from './types.js';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Spinner } from '../spinner/index.js';
|
|
3
|
+
import Content from './components/select.content.svelte';
|
|
4
|
+
import GroupHeading from './components/select.group-heading.svelte';
|
|
5
|
+
import Group from './components/select.group.svelte';
|
|
6
|
+
import Item from './components/select.item.svelte';
|
|
7
|
+
import Portal from './components/select.portal.svelte';
|
|
8
|
+
import Root from './components/select.root.svelte';
|
|
9
|
+
import Trigger from './components/select.trigger.svelte';
|
|
10
|
+
import Viewport from './components/select.viewport.svelte';
|
|
11
|
+
import type { SelectAsyncProps, SelectData, SelectItem, SelectItemGroup } from './types.js';
|
|
12
|
+
import { getItemKey, getLabelFromValue } from './utils/index.js';
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
value = $bindable(''),
|
|
16
|
+
open = $bindable(false),
|
|
17
|
+
empty = 'No results found',
|
|
18
|
+
maxContentHeight,
|
|
19
|
+
item,
|
|
20
|
+
trigger,
|
|
21
|
+
side,
|
|
22
|
+
sideOffset,
|
|
23
|
+
align,
|
|
24
|
+
alignOffset,
|
|
25
|
+
avoidCollisions,
|
|
26
|
+
collisionPadding,
|
|
27
|
+
customAnchor,
|
|
28
|
+
placeholder: placeholderProp,
|
|
29
|
+
loading,
|
|
30
|
+
pageSize = 10,
|
|
31
|
+
callback,
|
|
32
|
+
...restProps
|
|
33
|
+
}: SelectAsyncProps = $props();
|
|
34
|
+
|
|
35
|
+
let data = $state<SelectData>([]);
|
|
36
|
+
let isLoading = $state(false);
|
|
37
|
+
let hasMore = $state(true);
|
|
38
|
+
let currentPage = $state(1);
|
|
39
|
+
|
|
40
|
+
let error = $state<string | null>(null);
|
|
41
|
+
|
|
42
|
+
const placeholder = $derived.by(() => {
|
|
43
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
44
|
+
const label = getLabelFromValue(value, data);
|
|
45
|
+
return label ?? value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
49
|
+
const labels = value.map((v) => getLabelFromValue(v, data) ?? v);
|
|
50
|
+
return labels.join(', ');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (placeholderProp) return placeholderProp;
|
|
54
|
+
|
|
55
|
+
return '';
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const fetchData = async (page: number, append = false) => {
|
|
59
|
+
isLoading = true;
|
|
60
|
+
error = null;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const result = await callback({ page, pageSize });
|
|
64
|
+
|
|
65
|
+
if (append) {
|
|
66
|
+
data = [...data, ...(result.data as any)];
|
|
67
|
+
} else {
|
|
68
|
+
data = result.data;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
currentPage = page;
|
|
72
|
+
hasMore = result.hasMore;
|
|
73
|
+
isLoading = false;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
error = err instanceof Error ? err.message : 'Failed to fetch data';
|
|
76
|
+
} finally {
|
|
77
|
+
isLoading = false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
$effect(() => {
|
|
82
|
+
if (open && data.length === 0 && !isLoading) {
|
|
83
|
+
fetchData(currentPage);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
$effect(() => {
|
|
88
|
+
return () => {
|
|
89
|
+
currentPage = 1;
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const loadMore = () => {
|
|
94
|
+
if (!isLoading && hasMore) {
|
|
95
|
+
const nextPage = currentPage + 1;
|
|
96
|
+
fetchData(nextPage, true);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleScroll = (event: Event) => {
|
|
101
|
+
const viewport = event.target as HTMLElement;
|
|
102
|
+
const scrollBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
103
|
+
|
|
104
|
+
if (scrollBottom < 100 && hasMore && !isLoading) {
|
|
105
|
+
loadMore();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const hasResults = $derived.by(() => {
|
|
110
|
+
return data.length > 0;
|
|
111
|
+
});
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
{#snippet itemsString(item: string)}
|
|
115
|
+
<Item value={item} label={item} />
|
|
116
|
+
{/snippet}
|
|
117
|
+
|
|
118
|
+
{#snippet itemBasic(item: SelectItem)}
|
|
119
|
+
<Item value={item.value} label={item.label} />
|
|
120
|
+
{/snippet}
|
|
121
|
+
|
|
122
|
+
{#snippet itemGroup(group: SelectItemGroup)}
|
|
123
|
+
<Group>
|
|
124
|
+
<GroupHeading>
|
|
125
|
+
{group.group}
|
|
126
|
+
</GroupHeading>
|
|
127
|
+
|
|
128
|
+
{#each group.items as item (getItemKey(item))}
|
|
129
|
+
{#if typeof item === 'string'}
|
|
130
|
+
{@render itemsString(item)}
|
|
131
|
+
{:else}
|
|
132
|
+
{@render itemBasic(item)}
|
|
133
|
+
{/if}
|
|
134
|
+
{/each}
|
|
135
|
+
</Group>
|
|
136
|
+
{/snippet}
|
|
137
|
+
|
|
138
|
+
<Root bind:value={value as never} bind:open {...restProps as any}>
|
|
139
|
+
{#if trigger}
|
|
140
|
+
<Trigger>
|
|
141
|
+
{#snippet child({ props })}
|
|
142
|
+
{@render trigger?.({ props, label: placeholder })}
|
|
143
|
+
{/snippet}
|
|
144
|
+
</Trigger>
|
|
145
|
+
{:else}
|
|
146
|
+
<Trigger>
|
|
147
|
+
{placeholder}
|
|
148
|
+
</Trigger>
|
|
149
|
+
{/if}
|
|
150
|
+
|
|
151
|
+
<Portal>
|
|
152
|
+
<Content
|
|
153
|
+
{maxContentHeight}
|
|
154
|
+
{side}
|
|
155
|
+
{sideOffset}
|
|
156
|
+
{align}
|
|
157
|
+
{alignOffset}
|
|
158
|
+
{avoidCollisions}
|
|
159
|
+
{collisionPadding}
|
|
160
|
+
{customAnchor}
|
|
161
|
+
>
|
|
162
|
+
<Viewport onscroll={handleScroll}>
|
|
163
|
+
{#if error}
|
|
164
|
+
<div class="cgui:p-4 cgui:text-center cgui:text-body-2 cgui:text-fg-darkest cgui:text-destructive">
|
|
165
|
+
{error}
|
|
166
|
+
</div>
|
|
167
|
+
{:else if isLoading && !hasResults}
|
|
168
|
+
{#if loading}
|
|
169
|
+
{@render loading?.()}
|
|
170
|
+
{:else}
|
|
171
|
+
<div class="cgui:p-4 cgui:flex cgui:items-center cgui:h-full cgui:justify-center cgui:text-fg-darkest">
|
|
172
|
+
<Spinner />
|
|
173
|
+
</div>
|
|
174
|
+
{/if}
|
|
175
|
+
{:else if hasResults}
|
|
176
|
+
{#each data as item (getItemKey(item))}
|
|
177
|
+
{#if typeof item === 'string'}
|
|
178
|
+
{@render itemsString(item)}
|
|
179
|
+
{:else if 'group' in item}
|
|
180
|
+
{@render itemGroup(item)}
|
|
181
|
+
{:else}
|
|
182
|
+
{@render itemBasic(item)}
|
|
183
|
+
{/if}
|
|
184
|
+
{/each}
|
|
185
|
+
{#if isLoading}
|
|
186
|
+
<div
|
|
187
|
+
class="cgui:p-2 cgui:flex cgui:items-center cgui:justify-center cgui:text-center cgui:text-body-2 cgui:text-fg-darkest"
|
|
188
|
+
>
|
|
189
|
+
<Spinner />
|
|
190
|
+
</div>
|
|
191
|
+
{/if}
|
|
192
|
+
{:else if typeof empty === 'string'}
|
|
193
|
+
<div
|
|
194
|
+
class="cgui:p-4 cgui:flex cgui:items-center cgui:h-full cgui:justify-center cgui:text-center cgui:text-body-2 cgui:text-fg-regular"
|
|
195
|
+
>
|
|
196
|
+
{empty}
|
|
197
|
+
</div>
|
|
198
|
+
{:else}
|
|
199
|
+
{@render empty?.()}
|
|
200
|
+
{/if}
|
|
201
|
+
</Viewport>
|
|
202
|
+
</Content>
|
|
203
|
+
</Portal>
|
|
204
|
+
</Root>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { Parameters } from '@storybook/sveltekit';
|
|
4
4
|
import type { ComponentProps } from 'svelte';
|
|
5
5
|
import { Select, SelectPrimitive } from './index.js';
|
|
6
|
+
import type { SelectAsyncCallback } from './types.js';
|
|
6
7
|
|
|
7
8
|
const parameters: Parameters = {
|
|
8
9
|
controls: {
|
|
@@ -104,6 +105,38 @@
|
|
|
104
105
|
[] as Array<{ label: string; items: typeof groupedItems }>
|
|
105
106
|
)
|
|
106
107
|
);
|
|
108
|
+
|
|
109
|
+
type CatData = {
|
|
110
|
+
id: string;
|
|
111
|
+
url: string;
|
|
112
|
+
width: number;
|
|
113
|
+
height: number;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const fetchCatData: SelectAsyncCallback = async (params) => {
|
|
117
|
+
const { page, pageSize } = params;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const res = await fetch(`https://api.thecatapi.com/v1/images/search?limit=${pageSize}&page=${page}`, {
|
|
121
|
+
headers: {
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
'x-api-key': 'live_V1XvjJ84eY5LPP29tbKPJXDiRFPg0WgkDjRtCsocndgnNrVtiUDP25W9rp3QuwbX',
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const data = (await res.json()) as CatData[];
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
data: data.map((item) => ({
|
|
131
|
+
value: item.id,
|
|
132
|
+
label: item.id,
|
|
133
|
+
})),
|
|
134
|
+
hasMore: data.length > 0,
|
|
135
|
+
};
|
|
136
|
+
} catch (error) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
107
140
|
</script>
|
|
108
141
|
|
|
109
142
|
<Story name="Basic" {args} {parameters}>
|
|
@@ -131,7 +164,7 @@
|
|
|
131
164
|
<SelectPrimitive.Trigger>{selectedGroupedLabel}</SelectPrimitive.Trigger>
|
|
132
165
|
|
|
133
166
|
<SelectPrimitive.Portal>
|
|
134
|
-
<SelectPrimitive.Content>
|
|
167
|
+
<SelectPrimitive.Content class="cgui:h-30">
|
|
135
168
|
<SelectPrimitive.Viewport>
|
|
136
169
|
<SelectPrimitive.Group>
|
|
137
170
|
{#each organizedGroups as group}
|
|
@@ -191,6 +224,7 @@
|
|
|
191
224
|
<Select.Root
|
|
192
225
|
{...args}
|
|
193
226
|
type="multiple"
|
|
227
|
+
maxContentHeight="150px"
|
|
194
228
|
data={[
|
|
195
229
|
{
|
|
196
230
|
group: 'Group 1',
|
|
@@ -214,3 +248,16 @@
|
|
|
214
248
|
</div>
|
|
215
249
|
{/snippet}
|
|
216
250
|
</Story>
|
|
251
|
+
|
|
252
|
+
<Story name="Async" {args} {parameters}>
|
|
253
|
+
{#snippet template(args: Args)}
|
|
254
|
+
<Select.Async
|
|
255
|
+
{...args}
|
|
256
|
+
type="single"
|
|
257
|
+
side="top"
|
|
258
|
+
maxContentHeight="150px"
|
|
259
|
+
callback={fetchCatData}
|
|
260
|
+
placeholder="Select Item"
|
|
261
|
+
/>
|
|
262
|
+
{/snippet}
|
|
263
|
+
</Story>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { SelectItem,
|
|
2
|
+
import type { SelectItem, SelectItemGroup, SelectProps } from './types.js';
|
|
3
|
+
import { getItemKey, getLabelFromValue } from './utils/index.js';
|
|
3
4
|
|
|
4
5
|
import Content from './components/select.content.svelte';
|
|
5
6
|
import GroupHeading from './components/select.group-heading.svelte';
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
|
|
17
18
|
empty = 'No results found',
|
|
18
19
|
|
|
20
|
+
maxContentHeight,
|
|
19
21
|
data,
|
|
20
22
|
item,
|
|
21
23
|
trigger,
|
|
@@ -32,33 +34,14 @@
|
|
|
32
34
|
...restProps
|
|
33
35
|
}: SelectProps = $props();
|
|
34
36
|
|
|
35
|
-
const getLabelFromValue = (searchValue: string): string | null => {
|
|
36
|
-
for (const item of data) {
|
|
37
|
-
if (typeof item === 'string') {
|
|
38
|
-
if (item === searchValue) return item;
|
|
39
|
-
} else if ('group' in item) {
|
|
40
|
-
for (const groupItem of item.items) {
|
|
41
|
-
if (typeof groupItem === 'string') {
|
|
42
|
-
if (groupItem === searchValue) return groupItem;
|
|
43
|
-
} else if (groupItem.value === searchValue) {
|
|
44
|
-
return groupItem.label;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
} else if (item.value === searchValue) {
|
|
48
|
-
return item.label;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
37
|
const placeholder = $derived.by(() => {
|
|
55
38
|
if (typeof value === 'string' && value.trim() !== '') {
|
|
56
|
-
const label = getLabelFromValue(value);
|
|
39
|
+
const label = getLabelFromValue(value, data);
|
|
57
40
|
return label ?? value;
|
|
58
41
|
}
|
|
59
42
|
|
|
60
43
|
if (Array.isArray(value) && value.length > 0) {
|
|
61
|
-
const labels = value.map((v) => getLabelFromValue(v) ?? v);
|
|
44
|
+
const labels = value.map((v) => getLabelFromValue(v, data) ?? v);
|
|
62
45
|
return labels.join(', ');
|
|
63
46
|
}
|
|
64
47
|
|
|
@@ -67,12 +50,6 @@
|
|
|
67
50
|
return '';
|
|
68
51
|
});
|
|
69
52
|
|
|
70
|
-
const getItemKey = (item: SelectItemData) => {
|
|
71
|
-
if (typeof item === 'string') return item;
|
|
72
|
-
if ('group' in item) return item.group;
|
|
73
|
-
return item.value;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
53
|
const hasResults = $derived.by(() => {
|
|
77
54
|
return data.length > 0;
|
|
78
55
|
});
|
|
@@ -116,7 +93,16 @@
|
|
|
116
93
|
{/if}
|
|
117
94
|
|
|
118
95
|
<Portal>
|
|
119
|
-
<Content
|
|
96
|
+
<Content
|
|
97
|
+
{maxContentHeight}
|
|
98
|
+
{side}
|
|
99
|
+
{sideOffset}
|
|
100
|
+
{align}
|
|
101
|
+
{alignOffset}
|
|
102
|
+
{avoidCollisions}
|
|
103
|
+
{collisionPadding}
|
|
104
|
+
{customAnchor}
|
|
105
|
+
>
|
|
120
106
|
<Viewport>
|
|
121
107
|
{#if hasResults}
|
|
122
108
|
{#each data as item (getItemKey(item))}
|
|
@@ -129,7 +115,9 @@
|
|
|
129
115
|
{/if}
|
|
130
116
|
{/each}
|
|
131
117
|
{:else if typeof empty === 'string'}
|
|
132
|
-
<div
|
|
118
|
+
<div
|
|
119
|
+
class="cgui:p-4 cgui:flex cgui:items-center cgui:h-full cgui:justify-center cgui:text-center cgui:text-body-2 cgui:text-fg-regular"
|
|
120
|
+
>
|
|
133
121
|
{empty}
|
|
134
122
|
</div>
|
|
135
123
|
{:else}
|
|
@@ -4,23 +4,28 @@ import { keyWithPrefix } from './../../internal/utils/common.js';
|
|
|
4
4
|
export const selectVariants = tv({
|
|
5
5
|
slots: {
|
|
6
6
|
content: [
|
|
7
|
+
'cgui:scrollbar-thin',
|
|
7
8
|
'cgui:relative cgui:overflow-y-auto cgui:overflow-x-hidden',
|
|
8
9
|
'cgui:shadow-select cgui:bg-surface-white',
|
|
9
10
|
'cgui:rounded-sm',
|
|
10
11
|
'cgui:z-(--cg-ui-z-index-select)',
|
|
11
|
-
'cgui:max-h-(--bits-select-content-available-height) cgui:origin-(--bits-select-content-transform-origin)',
|
|
12
|
+
'cgui:max-h-(--bits-select-content-available-height) cgui:origin-(--bits-select-content-transform-origin) cgui:h-(--cg-ui-max-content-height)',
|
|
12
13
|
'cgui:data-[state=open]:animate-in cgui:data-[state=closed]:animate-out cgui:data-[state=closed]:fade-out-0 cgui:data-[state=open]:fade-in-0 cgui:data-[state=closed]:zoom-out-95 cgui:data-[state=open]:zoom-in-95 cgui:data-[side=bottom]:slide-in-from-top-2 cgui:data-[side=left]:slide-in-from-right-2 cgui:data-[side=right]:slide-in-from-left-2 cgui:data-[side=top]:slide-in-from-bottom-2 cgui:data-[side=bottom]:translate-y-1 cgui:data-[side=left]:-translate-x-1 cgui:data-[side=right]:translate-x-1 cgui:data-[side=top]:-translate-y-1',
|
|
13
14
|
],
|
|
14
15
|
item: [
|
|
15
16
|
'cgui:relative cgui:w-full',
|
|
16
17
|
'cgui:flex cgui:items-center cgui:justify-between cgui:gap-2',
|
|
17
18
|
'cgui:outline-hidden cgui:cursor-default cgui:select-none',
|
|
19
|
+
'cgui:transition-all cgui:duration-250 cgui:ease-in-out',
|
|
20
|
+
'cgui:rounded-xs',
|
|
18
21
|
'cgui:p-2',
|
|
19
|
-
'cgui:text-body cgui:text-fg-
|
|
22
|
+
'cgui:text-body cgui:text-fg-medium',
|
|
20
23
|
'cgui:data-[disabled]:pointer-events-none cgui:data-[disabled]:opacity-50',
|
|
21
24
|
'cgui:[&_svg]:shrink-0 cgui:[&_svg]:pointer-events-none',
|
|
25
|
+
'cgui:data-[highlighted]:bg-surface-lightest',
|
|
22
26
|
],
|
|
23
27
|
viewport: [
|
|
28
|
+
'cgui:scrollbar-track-transparent cgui:scrollbar-thumb-stroke-default cgui:scrollbar-thumb-rounded-full',
|
|
24
29
|
'cgui:h-(--bits-select-anchor-height) cgui:min-w-(--bits-select-anchor-width) cgui:w-full cgui:scroll-my-1 cgui:p-1',
|
|
25
30
|
],
|
|
26
31
|
group: [],
|
|
@@ -28,6 +33,7 @@ export const selectVariants = tv({
|
|
|
28
33
|
trigger: [
|
|
29
34
|
'cgui:group/select-trigger',
|
|
30
35
|
'cgui:relative cgui:flex cgui:items-center cgui:justify-between cgui:flex-wrap',
|
|
36
|
+
'cgui:text-body',
|
|
31
37
|
'cgui:transition-all cgui:duration-250 cgui:ease-in-out',
|
|
32
38
|
],
|
|
33
39
|
},
|
|
@@ -54,7 +60,6 @@ export const selectVariants = tv({
|
|
|
54
60
|
'cgui:px-2.5 cgui:py-1.5',
|
|
55
61
|
'cgui:data-[start-chevron]:pl-8 cgui:data-[end-chevron]:pr-8',
|
|
56
62
|
],
|
|
57
|
-
// chevron: ['cgui:data-[start-chevron]:left-2.5 cgui:data-[end-chevron]:right-2.5'],
|
|
58
63
|
},
|
|
59
64
|
md: {
|
|
60
65
|
trigger: [
|
|
@@ -63,15 +68,14 @@ export const selectVariants = tv({
|
|
|
63
68
|
'cgui:data-[start-chevron]:pl-10 cgui:data-[end-chevron]:pr-10',
|
|
64
69
|
],
|
|
65
70
|
chevron: [],
|
|
66
|
-
// chevron: ['cgui:data-[start-chevron]:left-3 cgui:data-[end-chevron]:right-3'],
|
|
67
71
|
},
|
|
68
72
|
lg: {
|
|
69
73
|
trigger: [
|
|
70
74
|
'cgui:min-h-11',
|
|
75
|
+
'cgui:text-heading-2',
|
|
71
76
|
'cgui:px-4 cgui:py-2.5',
|
|
72
77
|
'cgui:data-[start-chevron]:pl-14 cgui:data-[end-chevron]:pr-14',
|
|
73
78
|
],
|
|
74
|
-
// chevron: ['cgui:data-[start-chevron]:left-4 cgui:data-[end-chevron]:right-4'],
|
|
75
79
|
},
|
|
76
80
|
},
|
|
77
81
|
rounded: {
|
|
@@ -6,7 +6,9 @@ export type SelectRootProps = SelectRootPropsPrimitive & SelectVariantsProps;
|
|
|
6
6
|
export type SelectTriggerProps = SelectTriggerPropsPrimitive & {
|
|
7
7
|
hasChevron?: boolean;
|
|
8
8
|
};
|
|
9
|
-
export type SelectContentProps = WithoutChild<SelectContentPropsPrimitive
|
|
9
|
+
export type SelectContentProps = WithoutChild<SelectContentPropsPrimitive> & {
|
|
10
|
+
maxContentHeight?: string;
|
|
11
|
+
};
|
|
10
12
|
export type SelectViewportProps = SelectViewportPropsPrimitive;
|
|
11
13
|
export type SelectItemProps = WithoutChild<SelectItemPropsPrimitive>;
|
|
12
14
|
export type SelectGroupProps = SelectGroupPropsPrimitive;
|
|
@@ -37,6 +39,7 @@ export type SelectProps = SelectRootProps & {
|
|
|
37
39
|
label: string;
|
|
38
40
|
}]>;
|
|
39
41
|
empty?: Snippet | string;
|
|
42
|
+
maxContentHeight?: SelectContentProps['maxContentHeight'];
|
|
40
43
|
side?: SelectContentProps['side'];
|
|
41
44
|
sideOffset?: SelectContentProps['sideOffset'];
|
|
42
45
|
align?: SelectContentProps['align'];
|
|
@@ -45,3 +48,17 @@ export type SelectProps = SelectRootProps & {
|
|
|
45
48
|
collisionPadding?: SelectContentProps['collisionPadding'];
|
|
46
49
|
customAnchor?: SelectContentProps['customAnchor'];
|
|
47
50
|
};
|
|
51
|
+
export type SelectAsyncCallbackParams = {
|
|
52
|
+
page: number;
|
|
53
|
+
pageSize: number;
|
|
54
|
+
};
|
|
55
|
+
export type SelectAsyncCallbackResult = {
|
|
56
|
+
data: SelectData;
|
|
57
|
+
hasMore: boolean;
|
|
58
|
+
};
|
|
59
|
+
export type SelectAsyncCallback = (params: SelectAsyncCallbackParams) => Promise<SelectAsyncCallbackResult>;
|
|
60
|
+
export type SelectAsyncProps = Omit<SelectProps, 'data'> & {
|
|
61
|
+
loading?: Snippet;
|
|
62
|
+
pageSize?: number;
|
|
63
|
+
callback: SelectAsyncCallback;
|
|
64
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const getLabelFromValue = (searchValue, data) => {
|
|
2
|
+
for (const item of data) {
|
|
3
|
+
if (typeof item === 'string') {
|
|
4
|
+
if (item === searchValue)
|
|
5
|
+
return item;
|
|
6
|
+
}
|
|
7
|
+
else if ('group' in item) {
|
|
8
|
+
for (const groupItem of item.items) {
|
|
9
|
+
if (typeof groupItem === 'string') {
|
|
10
|
+
if (groupItem === searchValue)
|
|
11
|
+
return groupItem;
|
|
12
|
+
}
|
|
13
|
+
else if (groupItem.value === searchValue) {
|
|
14
|
+
return groupItem.label;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else if (item.value === searchValue) {
|
|
19
|
+
return item.label;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
};
|