@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.
Files changed (32) hide show
  1. package/dist/assets/css/root.css +20 -30
  2. package/dist/components/field/components/field.label.svelte +8 -0
  3. package/dist/components/field/components/field.root.svelte +9 -2
  4. package/dist/components/field/components/field.svelte.d.ts +2 -0
  5. package/dist/components/field/components/field.svelte.js +2 -0
  6. package/dist/components/field/field.stories.svelte +8 -3
  7. package/dist/components/field/styles.d.ts +3 -3
  8. package/dist/components/field/styles.js +6 -1
  9. package/dist/components/field/types.d.ts +5 -0
  10. package/dist/components/icons/asterisk.svelte +12 -0
  11. package/dist/components/icons/asterisk.svelte.d.ts +3 -0
  12. package/dist/components/icons/exports.d.ts +1 -0
  13. package/dist/components/icons/exports.js +1 -0
  14. package/dist/components/select/components/select.content.svelte +19 -1
  15. package/dist/components/select/components/select.content.svelte.d.ts +2 -2
  16. package/dist/components/select/components/select.trigger.svelte +1 -1
  17. package/dist/components/select/exports.d.ts +1 -0
  18. package/dist/components/select/exports.js +1 -0
  19. package/dist/components/select/index.d.ts +1 -1
  20. package/dist/components/select/select.async.svelte +204 -0
  21. package/dist/components/select/select.async.svelte.d.ts +4 -0
  22. package/dist/components/select/select.stories.svelte +48 -1
  23. package/dist/components/select/select.svelte +18 -30
  24. package/dist/components/select/styles.js +9 -5
  25. package/dist/components/select/types.d.ts +18 -1
  26. package/dist/components/select/utils/get-item-key.d.ts +2 -0
  27. package/dist/components/select/utils/get-item-key.js +7 -0
  28. package/dist/components/select/utils/get-label-from-value.d.ts +2 -0
  29. package/dist/components/select/utils/get-label-from-value.js +23 -0
  30. package/dist/components/select/utils/index.d.ts +2 -0
  31. package/dist/components/select/utils/index.js +2 -0
  32. package/package.json +1 -1
@@ -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}
@@ -78,6 +78,8 @@ export declare class FieldControlState {
78
78
  readonly props: {
79
79
  readonly id: string;
80
80
  readonly 'aria-invalid': boolean;
81
+ readonly disabled: boolean;
82
+ readonly readOnly: boolean;
81
83
  };
82
84
  }
83
85
  export {};
@@ -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: 'cgui:text-fg-medium cgui:text-caption cgui:font-medium',
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>
@@ -0,0 +1,3 @@
1
+ declare const Asterisk: import("svelte").Component<import("../../internal/types/html-attributes.js").PrimitiveSVGAttributes, {}, "">;
2
+ type Asterisk = ReturnType<typeof Asterisk>;
3
+ export default Asterisk;
@@ -1,3 +1,4 @@
1
+ export { default as Asterisk } from './asterisk.svelte';
1
2
  export { default as CaretDown } from './caret-down.svelte';
2
3
  export { default as CaretUp } from './caret-up.svelte';
3
4
  export { default as Check } from './check.svelte';
@@ -1,3 +1,4 @@
1
+ export { default as Asterisk } from './asterisk.svelte';
1
2
  export { default as CaretDown } from './caret-down.svelte';
2
3
  export { default as CaretUp } from './caret-up.svelte';
3
4
  export { default as Check } from './check.svelte';
@@ -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 { Select as SelectPrimitive } from 'bits-ui';
2
- declare const Select: import("svelte").Component<Omit<SelectPrimitive.ContentProps, "child">, {}, "ref">;
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;
@@ -37,7 +37,7 @@
37
37
  'cgui:group-data-[state=open]/select-trigger:-rotate-180'
38
38
  )}
39
39
  >
40
- <Icon.ChevronDown width={20} height={20} />
40
+ <Icon.ChevronDown width={18} height={18} />
41
41
  </span>
42
42
  {/if}
43
43
  </SelectPrimitive.Trigger>
@@ -1 +1,2 @@
1
+ export { default as Async } from './select.async.svelte';
1
2
  export { default as Root } from './select.svelte';
@@ -1 +1,2 @@
1
+ export { default as Async } from './select.async.svelte';
1
2
  export { default as Root } from './select.svelte';
@@ -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>
@@ -0,0 +1,4 @@
1
+ import type { SelectAsyncProps } from './types.js';
2
+ declare const Select: import("svelte").Component<SelectAsyncProps, {}, "value" | "open">;
3
+ type Select = ReturnType<typeof Select>;
4
+ export default Select;
@@ -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, SelectItemData, SelectItemGroup, SelectProps } from './types.js';
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 class="cgui:p-4 cgui:text-center cgui:text-body-2 cgui:text-fg-darkest">
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-dark',
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,2 @@
1
+ import type { SelectItemData } from '../types.js';
2
+ export declare const getItemKey: (item: SelectItemData) => string;
@@ -0,0 +1,7 @@
1
+ export const getItemKey = (item) => {
2
+ if (typeof item === 'string')
3
+ return item;
4
+ if ('group' in item)
5
+ return item.group;
6
+ return item.value;
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { SelectData } from '../types.js';
2
+ export declare const getLabelFromValue: (searchValue: string, data: SelectData) => string | null;
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+ export { getItemKey } from './get-item-key.js';
2
+ export { getLabelFromValue } from './get-label-from-value.js';
@@ -0,0 +1,2 @@
1
+ export { getItemKey } from './get-item-key.js';
2
+ export { getLabelFromValue } from './get-label-from-value.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@casinogate/ui",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "svelte": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",