@finsweet/webflow-apps-utils 1.0.30 → 1.0.32
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/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/progress-bar/ProgressBar.stories.svelte +185 -0
- package/dist/ui/components/progress-bar/ProgressBar.stories.svelte.d.ts +27 -0
- package/dist/ui/components/progress-bar/ProgressBar.svelte +290 -0
- package/dist/ui/components/progress-bar/ProgressBar.svelte.d.ts +4 -0
- package/dist/ui/components/progress-bar/index.d.ts +2 -0
- package/dist/ui/components/progress-bar/index.js +2 -0
- package/dist/ui/components/progress-bar/types.d.ts +63 -0
- package/dist/ui/components/progress-bar/types.js +1 -0
- package/dist/ui/components/select/Select.stories.d.ts +16 -0
- package/dist/ui/components/select/Select.stories.js +173 -0
- package/dist/ui/components/select/Select.svelte +166 -118
- package/dist/ui/components/select/types.d.ts +13 -0
- package/dist/ui/components/tooltip/Tooltip.svelte +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import { fn } from 'storybook/test';
|
|
4
|
+
|
|
5
|
+
import ProgressBar from './ProgressBar.svelte';
|
|
6
|
+
|
|
7
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
|
|
8
|
+
const { Story } = defineMeta({
|
|
9
|
+
title: 'UI/ProgressBar',
|
|
10
|
+
component: ProgressBar,
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
argTypes: {
|
|
13
|
+
value: {
|
|
14
|
+
control: { type: 'range', min: 0, max: 100, step: 1 }
|
|
15
|
+
},
|
|
16
|
+
max: {
|
|
17
|
+
control: { type: 'number' }
|
|
18
|
+
},
|
|
19
|
+
variant: {
|
|
20
|
+
control: { type: 'select' },
|
|
21
|
+
options: ['default', 'success', 'warning', 'error']
|
|
22
|
+
},
|
|
23
|
+
easing: {
|
|
24
|
+
control: { type: 'select' },
|
|
25
|
+
options: ['linear', 'cubicIn', 'cubicOut', 'cubicInOut', 'quartOut']
|
|
26
|
+
},
|
|
27
|
+
animated: {
|
|
28
|
+
control: { type: 'boolean' }
|
|
29
|
+
},
|
|
30
|
+
duration: {
|
|
31
|
+
control: { type: 'range', min: 100, max: 2000, step: 100 }
|
|
32
|
+
},
|
|
33
|
+
showPercentage: {
|
|
34
|
+
control: { type: 'boolean' }
|
|
35
|
+
},
|
|
36
|
+
showStatus: {
|
|
37
|
+
control: { type: 'boolean' }
|
|
38
|
+
},
|
|
39
|
+
showSpinner: {
|
|
40
|
+
control: { type: 'boolean' }
|
|
41
|
+
},
|
|
42
|
+
completed: {
|
|
43
|
+
control: { type: 'boolean' }
|
|
44
|
+
},
|
|
45
|
+
height: {
|
|
46
|
+
control: { type: 'range', min: 2, max: 20, step: 1 }
|
|
47
|
+
},
|
|
48
|
+
statusText: {
|
|
49
|
+
control: { type: 'text' }
|
|
50
|
+
},
|
|
51
|
+
onComplete: { action: 'completed' }
|
|
52
|
+
},
|
|
53
|
+
args: {
|
|
54
|
+
onComplete: fn()
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<Story
|
|
60
|
+
name="Default"
|
|
61
|
+
args={{
|
|
62
|
+
value: 45,
|
|
63
|
+
showPercentage: true
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
<Story
|
|
68
|
+
name="With Status"
|
|
69
|
+
args={{
|
|
70
|
+
value: 60,
|
|
71
|
+
showStatus: true,
|
|
72
|
+
showPercentage: true,
|
|
73
|
+
statusText: 'Processing files...'
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<Story
|
|
78
|
+
name="With Spinner"
|
|
79
|
+
args={{
|
|
80
|
+
value: 35,
|
|
81
|
+
showStatus: true,
|
|
82
|
+
showPercentage: true,
|
|
83
|
+
showSpinner: true,
|
|
84
|
+
statusText: 'Uploading data...'
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<Story
|
|
89
|
+
name="Completed State"
|
|
90
|
+
args={{
|
|
91
|
+
value: 100,
|
|
92
|
+
completed: true,
|
|
93
|
+
showStatus: true,
|
|
94
|
+
showPercentage: true,
|
|
95
|
+
statusText: 'Processing completed'
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<Story
|
|
100
|
+
name="Success Variant"
|
|
101
|
+
args={{
|
|
102
|
+
value: 80,
|
|
103
|
+
variant: 'success',
|
|
104
|
+
showStatus: true,
|
|
105
|
+
showPercentage: true,
|
|
106
|
+
statusText: 'Successfully processing...'
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
|
|
110
|
+
<Story
|
|
111
|
+
name="Warning Variant"
|
|
112
|
+
args={{
|
|
113
|
+
value: 65,
|
|
114
|
+
variant: 'warning',
|
|
115
|
+
showStatus: true,
|
|
116
|
+
showPercentage: true,
|
|
117
|
+
statusText: 'Warning: Slow connection detected'
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
<Story
|
|
122
|
+
name="Error Variant"
|
|
123
|
+
args={{
|
|
124
|
+
value: 25,
|
|
125
|
+
variant: 'error',
|
|
126
|
+
showStatus: true,
|
|
127
|
+
showPercentage: true,
|
|
128
|
+
statusText: 'Error: Connection failed'
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
<Story
|
|
133
|
+
name="No Animation"
|
|
134
|
+
args={{
|
|
135
|
+
value: 70,
|
|
136
|
+
animated: false,
|
|
137
|
+
showPercentage: true,
|
|
138
|
+
showStatus: true,
|
|
139
|
+
statusText: 'Instant progress'
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
<Story
|
|
144
|
+
name="Slow Animation"
|
|
145
|
+
args={{
|
|
146
|
+
value: 85,
|
|
147
|
+
duration: 2000,
|
|
148
|
+
easing: 'quartOut',
|
|
149
|
+
showPercentage: true,
|
|
150
|
+
showStatus: true,
|
|
151
|
+
statusText: 'Slow, smooth progress...'
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<Story
|
|
156
|
+
name="Custom Height"
|
|
157
|
+
args={{
|
|
158
|
+
value: 55,
|
|
159
|
+
height: 8,
|
|
160
|
+
showPercentage: true,
|
|
161
|
+
showStatus: true,
|
|
162
|
+
statusText: 'Thick progress bar'
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
<Story
|
|
167
|
+
name="Minimal"
|
|
168
|
+
args={{
|
|
169
|
+
value: 40,
|
|
170
|
+
showPercentage: false,
|
|
171
|
+
showStatus: false
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
|
|
175
|
+
<Story
|
|
176
|
+
name="Scan Progress Simulation"
|
|
177
|
+
args={{
|
|
178
|
+
value: 73,
|
|
179
|
+
showStatus: true,
|
|
180
|
+
showPercentage: true,
|
|
181
|
+
showSpinner: true,
|
|
182
|
+
statusText: 'Scanning: 73/100 pages',
|
|
183
|
+
height: 4
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default ProgressBar;
|
|
2
|
+
type ProgressBar = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const ProgressBar: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
import ProgressBar from './ProgressBar.svelte';
|
|
15
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
16
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
17
|
+
$$bindings?: Bindings;
|
|
18
|
+
} & Exports;
|
|
19
|
+
(internal: unknown, props: {
|
|
20
|
+
$$events?: Events;
|
|
21
|
+
$$slots?: Slots;
|
|
22
|
+
}): Exports & {
|
|
23
|
+
$set?: any;
|
|
24
|
+
$on?: any;
|
|
25
|
+
};
|
|
26
|
+
z_$$bindings?: Bindings;
|
|
27
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cubicIn, cubicInOut, cubicOut, linear, quartOut } from 'svelte/easing';
|
|
3
|
+
import { tweened } from 'svelte/motion';
|
|
4
|
+
|
|
5
|
+
import { CheckCircleOutlinedIcon, WarningTriangleOutlineIcon } from '../../icons';
|
|
6
|
+
import Loader from '../Loader.svelte';
|
|
7
|
+
import { Text } from '../text';
|
|
8
|
+
import type { ProgressBarEasing, ProgressBarProps, ProgressBarVariant } from './types.js';
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
value = 0,
|
|
12
|
+
max = 100,
|
|
13
|
+
animated = true,
|
|
14
|
+
duration = 400,
|
|
15
|
+
easing = 'cubicOut',
|
|
16
|
+
showPercentage = true,
|
|
17
|
+
showStatus = false,
|
|
18
|
+
statusText = '',
|
|
19
|
+
showSpinner = false,
|
|
20
|
+
variant = 'default',
|
|
21
|
+
height = 4,
|
|
22
|
+
completed = false,
|
|
23
|
+
class: className = '',
|
|
24
|
+
onComplete,
|
|
25
|
+
...restProps
|
|
26
|
+
}: ProgressBarProps = $props();
|
|
27
|
+
|
|
28
|
+
const easingFunctions = {
|
|
29
|
+
linear,
|
|
30
|
+
cubicIn,
|
|
31
|
+
cubicOut,
|
|
32
|
+
cubicInOut,
|
|
33
|
+
quartOut
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let currentProgress = $state(0);
|
|
37
|
+
let isCompleted = $state(false);
|
|
38
|
+
let completedValue = $state<number | null>(null);
|
|
39
|
+
|
|
40
|
+
let percentage = $derived(
|
|
41
|
+
Math.min(100, Math.round((Math.max(0, value) / Math.max(1, max)) * 100))
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
let tweenedProgress = $state(0);
|
|
45
|
+
const progress = tweened(0, {
|
|
46
|
+
duration,
|
|
47
|
+
easing: easingFunctions[easing]
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
$effect(() => {
|
|
51
|
+
const unsubscribe = progress.subscribe((val) => {
|
|
52
|
+
tweenedProgress = val;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return unsubscribe;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let displayWidth = $derived(
|
|
59
|
+
isCompleted && completedValue !== null ? completedValue : tweenedProgress
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
let displayPercentage = $derived(
|
|
63
|
+
isCompleted && completedValue !== null
|
|
64
|
+
? Math.round(completedValue)
|
|
65
|
+
: Math.round(tweenedProgress)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
$effect(() => {
|
|
69
|
+
const currentPercentage = percentage;
|
|
70
|
+
if (!completed) {
|
|
71
|
+
currentProgress = currentPercentage;
|
|
72
|
+
if (animated) {
|
|
73
|
+
progress.set(currentPercentage, {
|
|
74
|
+
duration,
|
|
75
|
+
easing: easingFunctions[easing]
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
progress.set(currentPercentage, { duration: 0 });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
$effect(() => {
|
|
84
|
+
if (completed && !isCompleted) {
|
|
85
|
+
completedValue = tweenedProgress;
|
|
86
|
+
isCompleted = true;
|
|
87
|
+
|
|
88
|
+
onComplete?.();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!completed && isCompleted) {
|
|
92
|
+
isCompleted = false;
|
|
93
|
+
completedValue = null;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
let containerClasses = $derived(() => {
|
|
98
|
+
const classes = ['progress-container'];
|
|
99
|
+
if (className) classes.push(className);
|
|
100
|
+
return classes.join(' ');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
let progressClasses = $derived(() => {
|
|
104
|
+
const classes = ['progress-fill'];
|
|
105
|
+
if (isCompleted) classes.push('completed');
|
|
106
|
+
if (variant !== 'default') classes.push(`progress-fill--${variant}`);
|
|
107
|
+
return classes.join(' ');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let displayStatusText = $derived(() => {
|
|
111
|
+
if (isCompleted) {
|
|
112
|
+
return 'Completed successfully';
|
|
113
|
+
}
|
|
114
|
+
return statusText || `${displayPercentage}%`;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
let StatusIcon = $derived(() => {
|
|
118
|
+
if (isCompleted) {
|
|
119
|
+
return CheckCircleOutlinedIcon;
|
|
120
|
+
}
|
|
121
|
+
return WarningTriangleOutlineIcon;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
let statusColor = $derived(() => {
|
|
125
|
+
if (isCompleted) return 'var(--greenText)';
|
|
126
|
+
|
|
127
|
+
switch (variant) {
|
|
128
|
+
case 'success':
|
|
129
|
+
return 'var(--greenText)';
|
|
130
|
+
case 'warning':
|
|
131
|
+
return 'var(--yellowText)';
|
|
132
|
+
case 'error':
|
|
133
|
+
return 'var(--redText)';
|
|
134
|
+
default:
|
|
135
|
+
return 'var(--text1)';
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
let progressFillColor = $derived(() => {
|
|
140
|
+
if (isCompleted) return 'var(--greenText)';
|
|
141
|
+
|
|
142
|
+
switch (variant) {
|
|
143
|
+
case 'success':
|
|
144
|
+
return 'var(--greenText)';
|
|
145
|
+
case 'warning':
|
|
146
|
+
return 'var(--yellowText)';
|
|
147
|
+
case 'error':
|
|
148
|
+
return 'var(--redText)';
|
|
149
|
+
default:
|
|
150
|
+
return 'var(--actionPrimaryBackground)';
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<div
|
|
156
|
+
class={containerClasses()}
|
|
157
|
+
role="progressbar"
|
|
158
|
+
aria-valuenow={displayPercentage}
|
|
159
|
+
aria-valuemin="0"
|
|
160
|
+
aria-valuemax="100"
|
|
161
|
+
aria-label={displayStatusText()}
|
|
162
|
+
aria-live="polite"
|
|
163
|
+
{...restProps}
|
|
164
|
+
>
|
|
165
|
+
{#if showStatus || showPercentage || showSpinner}
|
|
166
|
+
<div class="progress-header">
|
|
167
|
+
<div class="status-info">
|
|
168
|
+
{#if showSpinner && !isCompleted}
|
|
169
|
+
<div class="progress-spinner">
|
|
170
|
+
<Loader size={12} margin="0" />
|
|
171
|
+
</div>
|
|
172
|
+
{/if}
|
|
173
|
+
|
|
174
|
+
<div class="status-icon" class:success={isCompleted}>
|
|
175
|
+
<StatusIcon />
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{#if showStatus}
|
|
179
|
+
<Text
|
|
180
|
+
label={displayStatusText()}
|
|
181
|
+
fontColor={statusColor()}
|
|
182
|
+
fontSize="normal"
|
|
183
|
+
class="progress-status"
|
|
184
|
+
/>
|
|
185
|
+
{/if}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div class="progress-details">
|
|
189
|
+
{#if showPercentage}
|
|
190
|
+
<Text
|
|
191
|
+
label="{displayPercentage}%"
|
|
192
|
+
fontColor={statusColor()}
|
|
193
|
+
fontSize="normal"
|
|
194
|
+
class="progress-percentage"
|
|
195
|
+
/>
|
|
196
|
+
{/if}
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
{/if}
|
|
200
|
+
|
|
201
|
+
<div class="progress-track" style="height: {height}px;">
|
|
202
|
+
<div
|
|
203
|
+
class={progressClasses()}
|
|
204
|
+
style="width: {displayWidth}%; height: {height}px; background-color: {progressFillColor()};"
|
|
205
|
+
></div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<style>
|
|
210
|
+
.progress-container {
|
|
211
|
+
display: flex;
|
|
212
|
+
flex-direction: column;
|
|
213
|
+
gap: 8px;
|
|
214
|
+
width: 100%;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.progress-header {
|
|
218
|
+
display: flex;
|
|
219
|
+
justify-content: space-between;
|
|
220
|
+
align-items: center;
|
|
221
|
+
gap: 4px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.status-info {
|
|
225
|
+
display: flex;
|
|
226
|
+
align-items: center;
|
|
227
|
+
gap: 4px;
|
|
228
|
+
flex: 1;
|
|
229
|
+
min-width: 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.status-icon :global(svg) {
|
|
233
|
+
width: 20px;
|
|
234
|
+
height: 20px;
|
|
235
|
+
color: var(--text1);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.status-icon.success :global(svg) {
|
|
239
|
+
color: var(--greenText);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.progress-details {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
gap: 8px;
|
|
246
|
+
flex-shrink: 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.progress-spinner {
|
|
250
|
+
display: flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
justify-content: center;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.progress-track {
|
|
256
|
+
width: 100%;
|
|
257
|
+
background-color: var(--black);
|
|
258
|
+
border-radius: 2px;
|
|
259
|
+
overflow: hidden;
|
|
260
|
+
position: relative;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.progress-fill {
|
|
264
|
+
height: 100%;
|
|
265
|
+
border-radius: 2px;
|
|
266
|
+
transition: width 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
267
|
+
min-width: 1px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.progress-fill.completed {
|
|
271
|
+
background-color: var(--greenText) !important;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.progress-fill--success {
|
|
275
|
+
background-color: var(--greenText);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.progress-fill--warning {
|
|
279
|
+
background-color: var(--yellowText);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.progress-fill--error {
|
|
283
|
+
background-color: var(--redText);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Ensure smooth transitions */
|
|
287
|
+
.progress-fill {
|
|
288
|
+
will-change: width;
|
|
289
|
+
}
|
|
290
|
+
</style>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type ProgressBarVariant = 'default' | 'success' | 'warning' | 'error';
|
|
2
|
+
export type ProgressBarEasing = 'linear' | 'cubicIn' | 'cubicOut' | 'cubicInOut' | 'quartOut';
|
|
3
|
+
export interface ProgressBarProps {
|
|
4
|
+
/**
|
|
5
|
+
* Current progress value (0-100)
|
|
6
|
+
*/
|
|
7
|
+
value: number;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum value (default: 100)
|
|
10
|
+
*/
|
|
11
|
+
max?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Enable smooth animations (default: true)
|
|
14
|
+
*/
|
|
15
|
+
animated?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Animation duration in milliseconds (default: 400)
|
|
18
|
+
*/
|
|
19
|
+
duration?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Easing function (default: 'cubicOut')
|
|
22
|
+
*/
|
|
23
|
+
easing?: ProgressBarEasing;
|
|
24
|
+
/**
|
|
25
|
+
* Show percentage text (default: true)
|
|
26
|
+
*/
|
|
27
|
+
showPercentage?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Show status text (default: false)
|
|
30
|
+
*/
|
|
31
|
+
showStatus?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Custom status text
|
|
34
|
+
*/
|
|
35
|
+
statusText?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Show loading spinner during progress (default: false)
|
|
38
|
+
*/
|
|
39
|
+
showSpinner?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Color scheme
|
|
42
|
+
*/
|
|
43
|
+
variant?: ProgressBarVariant;
|
|
44
|
+
/**
|
|
45
|
+
* Height in pixels (default: 4)
|
|
46
|
+
*/
|
|
47
|
+
height?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Completed state - locks progress at final value
|
|
50
|
+
*/
|
|
51
|
+
completed?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Custom CSS class
|
|
54
|
+
*/
|
|
55
|
+
class?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Callback when progress completes
|
|
58
|
+
*/
|
|
59
|
+
onComplete?: () => void;
|
|
60
|
+
}
|
|
61
|
+
export interface ProgressBarEvents {
|
|
62
|
+
complete: CustomEvent<void>;
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -68,6 +68,14 @@ declare const meta: {
|
|
|
68
68
|
control: string;
|
|
69
69
|
description: string;
|
|
70
70
|
};
|
|
71
|
+
alert: {
|
|
72
|
+
control: string;
|
|
73
|
+
description: string;
|
|
74
|
+
};
|
|
75
|
+
invalid: {
|
|
76
|
+
control: string;
|
|
77
|
+
description: string;
|
|
78
|
+
};
|
|
71
79
|
};
|
|
72
80
|
};
|
|
73
81
|
export default meta;
|
|
@@ -92,3 +100,11 @@ export declare const SingleOption: Story;
|
|
|
92
100
|
export declare const LongOptions: Story;
|
|
93
101
|
export declare const AccessibilityTest: Story;
|
|
94
102
|
export declare const ManyOptions: Story;
|
|
103
|
+
export declare const WithErrorAlert: Story;
|
|
104
|
+
export declare const WithWarningAlert: Story;
|
|
105
|
+
export declare const WithSuccessAlert: Story;
|
|
106
|
+
export declare const WithInfoAlert: Story;
|
|
107
|
+
export declare const InvalidState: Story;
|
|
108
|
+
export declare const InvalidWithAlert: Story;
|
|
109
|
+
export declare const ValidationStates: Story;
|
|
110
|
+
export declare const FormValidationExample: Story;
|
|
@@ -114,6 +114,14 @@ const meta = {
|
|
|
114
114
|
className: {
|
|
115
115
|
control: 'text',
|
|
116
116
|
description: 'Additional CSS classes'
|
|
117
|
+
},
|
|
118
|
+
alert: {
|
|
119
|
+
control: 'object',
|
|
120
|
+
description: 'Alert configuration for validation messages'
|
|
121
|
+
},
|
|
122
|
+
invalid: {
|
|
123
|
+
control: 'boolean',
|
|
124
|
+
description: 'Whether the select is in an invalid state'
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
};
|
|
@@ -394,3 +402,168 @@ export const ManyOptions = {
|
|
|
394
402
|
}
|
|
395
403
|
}
|
|
396
404
|
};
|
|
405
|
+
// Validation and error states
|
|
406
|
+
export const WithErrorAlert = {
|
|
407
|
+
args: {
|
|
408
|
+
options: basicOptions,
|
|
409
|
+
defaultText: 'Select a required option',
|
|
410
|
+
alert: {
|
|
411
|
+
type: 'error',
|
|
412
|
+
message: 'This field is required. Please select an option.'
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
parameters: {
|
|
416
|
+
docs: {
|
|
417
|
+
description: {
|
|
418
|
+
story: 'Shows an error alert with a validation message. Hover over the select to see the tooltip.'
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
export const WithWarningAlert = {
|
|
424
|
+
args: {
|
|
425
|
+
options: basicOptions,
|
|
426
|
+
selected: 'option3',
|
|
427
|
+
defaultText: 'Select an option',
|
|
428
|
+
alert: {
|
|
429
|
+
type: 'warning',
|
|
430
|
+
message: 'This option may affect performance. Consider choosing a different option.'
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
parameters: {
|
|
434
|
+
docs: {
|
|
435
|
+
description: {
|
|
436
|
+
story: 'Warning alert to inform users about potential issues with their selection.'
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
export const WithSuccessAlert = {
|
|
442
|
+
args: {
|
|
443
|
+
options: basicOptions,
|
|
444
|
+
selected: 'option1',
|
|
445
|
+
defaultText: 'Select an option',
|
|
446
|
+
alert: {
|
|
447
|
+
type: 'success',
|
|
448
|
+
message: 'Great choice! This option is optimized for your use case.'
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
parameters: {
|
|
452
|
+
docs: {
|
|
453
|
+
description: {
|
|
454
|
+
story: 'Success alert to confirm a good selection or provide positive feedback.'
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
export const WithInfoAlert = {
|
|
460
|
+
args: {
|
|
461
|
+
options: basicOptions,
|
|
462
|
+
defaultText: 'Select configuration',
|
|
463
|
+
alert: {
|
|
464
|
+
type: 'info',
|
|
465
|
+
message: 'Tip: You can change this setting later in your preferences.'
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
parameters: {
|
|
469
|
+
docs: {
|
|
470
|
+
description: {
|
|
471
|
+
story: 'Info alert to provide additional context or helpful information.'
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
export const InvalidState = {
|
|
477
|
+
args: {
|
|
478
|
+
options: basicOptions,
|
|
479
|
+
defaultText: 'Invalid selection',
|
|
480
|
+
invalid: true
|
|
481
|
+
},
|
|
482
|
+
parameters: {
|
|
483
|
+
docs: {
|
|
484
|
+
description: {
|
|
485
|
+
story: 'Shows the select in an invalid state with red outline styling.'
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
export const InvalidWithAlert = {
|
|
491
|
+
args: {
|
|
492
|
+
options: basicOptions,
|
|
493
|
+
defaultText: 'Select a value',
|
|
494
|
+
invalid: true,
|
|
495
|
+
alert: {
|
|
496
|
+
type: 'error',
|
|
497
|
+
message: 'Please select a valid option to continue.'
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
parameters: {
|
|
501
|
+
docs: {
|
|
502
|
+
description: {
|
|
503
|
+
story: 'Combines invalid state styling with an error alert message for comprehensive validation feedback.'
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
export const ValidationStates = {
|
|
509
|
+
args: {
|
|
510
|
+
options: [
|
|
511
|
+
{ label: 'Option A', value: 'a' },
|
|
512
|
+
{ label: 'Option B', value: 'b' },
|
|
513
|
+
{ label: 'Option C', value: 'c' }
|
|
514
|
+
],
|
|
515
|
+
defaultText: 'Validation example'
|
|
516
|
+
},
|
|
517
|
+
parameters: {
|
|
518
|
+
docs: {
|
|
519
|
+
description: {
|
|
520
|
+
story: 'Basic validation state. See other validation stories (WithErrorAlert, WithWarningAlert, etc.) for different alert types and invalid states.'
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
// Form validation example
|
|
526
|
+
export const FormValidationExample = {
|
|
527
|
+
render: (args) => ({
|
|
528
|
+
Component: Select,
|
|
529
|
+
props: {
|
|
530
|
+
...args,
|
|
531
|
+
onchange: (event) => {
|
|
532
|
+
// Simulate form validation
|
|
533
|
+
const value = event.value;
|
|
534
|
+
if (!value) {
|
|
535
|
+
// Update to show required field error
|
|
536
|
+
console.log('Validation: Field is required');
|
|
537
|
+
}
|
|
538
|
+
else if (value === 'option3') {
|
|
539
|
+
// Show warning for specific option
|
|
540
|
+
console.log('Validation: Warning for option 3');
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
// Valid selection
|
|
544
|
+
console.log('Validation: Valid selection');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}),
|
|
549
|
+
args: {
|
|
550
|
+
options: [
|
|
551
|
+
{ label: 'Valid Option 1', value: 'option1' },
|
|
552
|
+
{ label: 'Valid Option 2', value: 'option2' },
|
|
553
|
+
{ label: 'Problematic Option', value: 'option3' },
|
|
554
|
+
{ label: 'Another Valid Option', value: 'option4' }
|
|
555
|
+
],
|
|
556
|
+
defaultText: 'Choose wisely...',
|
|
557
|
+
alert: {
|
|
558
|
+
type: 'info',
|
|
559
|
+
message: 'Select an option to see validation feedback'
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
parameters: {
|
|
563
|
+
docs: {
|
|
564
|
+
description: {
|
|
565
|
+
story: 'Interactive example showing how validation states might change based on user selection in a real form.'
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
};
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { CheckIcon, ChevronIcon, UndoIcon } from '../../icons';
|
|
14
14
|
|
|
15
|
+
import { Tooltip } from '..';
|
|
15
16
|
import { Text } from '../text';
|
|
16
17
|
import type { DropdownInstance, SelectInstanceManager, SelectProps } from './types.js';
|
|
17
18
|
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
preventNoSelection = false,
|
|
34
35
|
disabled = false,
|
|
35
36
|
placement = 'bottom',
|
|
37
|
+
alert = null,
|
|
38
|
+
invalid = false,
|
|
36
39
|
className = '',
|
|
37
40
|
onchange,
|
|
38
41
|
children
|
|
@@ -85,6 +88,9 @@
|
|
|
85
88
|
}
|
|
86
89
|
});
|
|
87
90
|
|
|
91
|
+
// Computed states
|
|
92
|
+
let hasAlert = $derived(alert?.message);
|
|
93
|
+
|
|
88
94
|
// Computed styles based on state
|
|
89
95
|
const dropdownStyles = $derived(() => {
|
|
90
96
|
const base = {
|
|
@@ -101,17 +107,17 @@
|
|
|
101
107
|
};
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
if (hasError) {
|
|
110
|
+
if (hasError || hasAlert || invalid) {
|
|
105
111
|
return {
|
|
106
112
|
...base,
|
|
107
|
-
|
|
113
|
+
outline: '1px solid var(--redBorder)'
|
|
108
114
|
};
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
if (isFocused) {
|
|
112
118
|
return {
|
|
113
119
|
...base,
|
|
114
|
-
|
|
120
|
+
outline: '1px solid var(--blueBorder)'
|
|
115
121
|
};
|
|
116
122
|
}
|
|
117
123
|
|
|
@@ -393,6 +399,23 @@
|
|
|
393
399
|
debouncedFilterOptions(searchValue, options);
|
|
394
400
|
};
|
|
395
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Gets the tooltip background color based on alert type
|
|
404
|
+
*/
|
|
405
|
+
const getTooltipColor = (alertType: string) => {
|
|
406
|
+
switch (alertType) {
|
|
407
|
+
case 'error':
|
|
408
|
+
return 'var(--redBackground, #ff4d4d)';
|
|
409
|
+
case 'warning':
|
|
410
|
+
return 'var(--orangeBackground, #ff9933)';
|
|
411
|
+
case 'success':
|
|
412
|
+
return 'var(--greenBackground, #00cc66)';
|
|
413
|
+
case 'info':
|
|
414
|
+
default:
|
|
415
|
+
return 'var(--blueBackground, #4d9fff)';
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
396
419
|
// Lifecycle cleanup
|
|
397
420
|
$effect(() => {
|
|
398
421
|
return () => {
|
|
@@ -401,134 +424,159 @@
|
|
|
401
424
|
});
|
|
402
425
|
</script>
|
|
403
426
|
|
|
404
|
-
|
|
405
|
-
class="dropdown-wrapper {className}"
|
|
406
|
-
bind:this={dropdownWrapper}
|
|
407
|
-
style="{hide ? 'display:none;' : ''} width: {width};"
|
|
408
|
-
>
|
|
427
|
+
{#snippet selectWrapper()}
|
|
409
428
|
<div
|
|
410
|
-
class="dropdown"
|
|
411
|
-
|
|
412
|
-
{
|
|
413
|
-
style="width:{width}; {Object.entries(dropdownStyles())
|
|
414
|
-
.map(([key, value]) => `${key.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${value}`)
|
|
415
|
-
.join('; ')}"
|
|
416
|
-
aria-disabled={disabled}
|
|
417
|
-
tabindex={disabled || isOpen ? -1 : 0}
|
|
418
|
-
role="button"
|
|
419
|
-
aria-haspopup="listbox"
|
|
420
|
-
aria-labelledby={id}
|
|
421
|
-
bind:this={target}
|
|
422
|
-
onmouseenter={() => (isHovered = true)}
|
|
423
|
-
onmouseleave={() => (isHovered = false)}
|
|
424
|
-
onfocus={() => (isFocused = true)}
|
|
425
|
-
onblur={() => (isFocused = false)}
|
|
429
|
+
class="dropdown-wrapper {className}"
|
|
430
|
+
bind:this={dropdownWrapper}
|
|
431
|
+
style="{hide ? 'display:none;' : ''} width: {width};"
|
|
426
432
|
>
|
|
427
|
-
<div class="dropdown-header" aria-disabled={disabled}>
|
|
428
|
-
<div class="label">
|
|
429
|
-
{selected ? selectedLabel || defaultText : defaultText}
|
|
430
|
-
</div>
|
|
431
|
-
<div class="arrow" style="transform:rotate({isOpen ? '270deg' : '90deg'})">
|
|
432
|
-
<ChevronIcon />
|
|
433
|
-
</div>
|
|
434
|
-
</div>
|
|
435
|
-
|
|
436
433
|
<div
|
|
434
|
+
class="dropdown"
|
|
435
|
+
class:disabled
|
|
436
|
+
{id}
|
|
437
|
+
style="width:{width}; {Object.entries(dropdownStyles())
|
|
438
|
+
.map(([key, value]) => `${key.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${value}`)
|
|
439
|
+
.join('; ')}"
|
|
440
|
+
aria-disabled={disabled}
|
|
437
441
|
tabindex={disabled || isOpen ? -1 : 0}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
bind:this={dropdownItems}
|
|
442
|
+
role="button"
|
|
443
|
+
aria-haspopup="listbox"
|
|
444
|
+
aria-labelledby={id}
|
|
445
|
+
bind:this={target}
|
|
446
|
+
onmouseenter={() => (isHovered = true)}
|
|
447
|
+
onmouseleave={() => (isHovered = false)}
|
|
448
|
+
onfocus={() => (isFocused = true)}
|
|
449
|
+
onblur={() => (isFocused = false)}
|
|
447
450
|
>
|
|
448
|
-
{
|
|
449
|
-
<div class="
|
|
450
|
-
|
|
451
|
-
<Text label={selectedLabel} fontSize="normal" fontColor="var(--text1)" />
|
|
452
|
-
</div>
|
|
451
|
+
<div class="dropdown-header" aria-disabled={disabled}>
|
|
452
|
+
<div class="label">
|
|
453
|
+
{selected ? selectedLabel || defaultText : defaultText}
|
|
453
454
|
</div>
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
455
|
+
<div class="arrow" style="transform:rotate({isOpen ? '270deg' : '90deg'})">
|
|
456
|
+
<ChevronIcon />
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<div
|
|
461
|
+
tabindex={disabled || isOpen ? -1 : 0}
|
|
462
|
+
class="dropdown-list"
|
|
463
|
+
role="listbox"
|
|
464
|
+
style="width:{dropdownWidth}; max-height:{dropdownHeight};"
|
|
465
|
+
onkeydown={(e) => {
|
|
466
|
+
e.stopPropagation();
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
handleKeyDown(e);
|
|
469
|
+
}}
|
|
470
|
+
bind:this={dropdownItems}
|
|
471
|
+
>
|
|
472
|
+
{#if selectedLabel}
|
|
473
|
+
<div class="selected">
|
|
474
|
+
<div class="label">
|
|
475
|
+
<Text label={selectedLabel} fontSize="normal" fontColor="var(--text1)" />
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
{/if}
|
|
479
|
+
|
|
480
|
+
{#if enableSearch}
|
|
481
|
+
<div class="search-container">
|
|
482
|
+
<input
|
|
483
|
+
type="text"
|
|
484
|
+
placeholder="Search"
|
|
485
|
+
oninput={(e) => {
|
|
486
|
+
e.stopPropagation();
|
|
487
|
+
e.preventDefault();
|
|
488
|
+
handleSearch(e);
|
|
489
|
+
}}
|
|
490
|
+
onkeydown={(e) => e.stopPropagation()}
|
|
491
|
+
/>
|
|
492
|
+
</div>
|
|
493
|
+
{/if}
|
|
494
|
+
|
|
495
|
+
{#each optionsStore?.length > 0 ? optionsStore : options as { label, value, className = null, description = null, labelIcon = null, descriptionTitle = null, isDisabled = false }, index (index)}
|
|
496
|
+
{@const indexId = index + 1}
|
|
497
|
+
{@const itemId = ref ? ref.replace(' ', '-') : 'dropdown'}
|
|
498
|
+
<button
|
|
499
|
+
aria-posinset={indexId}
|
|
500
|
+
aria-selected={value === selected && selected?.trim() !== '' ? 'true' : 'false'}
|
|
501
|
+
id={`${itemId}-list-${indexId}-${id}`}
|
|
502
|
+
data-value={value}
|
|
503
|
+
class="dropdown-item {isDisabled ? 'disabled' : ''} {className}"
|
|
504
|
+
role="option"
|
|
505
|
+
onclick={(e) => {
|
|
506
|
+
e.stopPropagation();
|
|
507
|
+
if (isDisabled) return;
|
|
508
|
+
handleSelect(value, label, e.currentTarget);
|
|
509
|
+
}}
|
|
510
|
+
onkeydown={(e) => {
|
|
462
511
|
e.stopPropagation();
|
|
463
512
|
e.preventDefault();
|
|
464
|
-
handleSearch(e);
|
|
465
513
|
}}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
{#if value === selected && selected?.trim() !== ''}
|
|
497
|
-
<CheckIcon />
|
|
498
|
-
{/if}
|
|
499
|
-
</div>
|
|
500
|
-
<div class="label">
|
|
501
|
-
{#if description || descriptionTitle || labelIcon}
|
|
502
|
-
<div class="label-content">
|
|
503
|
-
<div class="label-name">
|
|
504
|
-
<Text {label} />
|
|
505
|
-
{#if labelIcon}
|
|
506
|
-
{@const IconComponent = labelIcon}
|
|
507
|
-
<IconComponent />
|
|
508
|
-
{/if}
|
|
509
|
-
</div>
|
|
510
|
-
<div class="label-description-title">
|
|
511
|
-
<Text
|
|
512
|
-
label={descriptionTitle || ''}
|
|
513
|
-
fontColor="var(--greenText)"
|
|
514
|
-
fontSize="10px"
|
|
515
|
-
/>
|
|
514
|
+
onmouseenter={handleMouseEnter}
|
|
515
|
+
aria-hidden={!isOpen}
|
|
516
|
+
tabindex={value === selected ? 0 : -1}
|
|
517
|
+
style={description ? 'align-items:start;' : ''}
|
|
518
|
+
>
|
|
519
|
+
<div class="icon" aria-label={label}>
|
|
520
|
+
{#if value === selected && selected?.trim() !== ''}
|
|
521
|
+
<CheckIcon />
|
|
522
|
+
{/if}
|
|
523
|
+
</div>
|
|
524
|
+
<div class="label">
|
|
525
|
+
{#if description || descriptionTitle || labelIcon}
|
|
526
|
+
<div class="label-content">
|
|
527
|
+
<div class="label-name">
|
|
528
|
+
<Text {label} />
|
|
529
|
+
{#if labelIcon}
|
|
530
|
+
{@const IconComponent = labelIcon}
|
|
531
|
+
<IconComponent />
|
|
532
|
+
{/if}
|
|
533
|
+
</div>
|
|
534
|
+
<div class="label-description-title">
|
|
535
|
+
<Text
|
|
536
|
+
label={descriptionTitle || ''}
|
|
537
|
+
fontColor="var(--greenText)"
|
|
538
|
+
fontSize="10px"
|
|
539
|
+
/>
|
|
540
|
+
</div>
|
|
541
|
+
<div class="label-description">
|
|
542
|
+
<Text label={description || ''} fontColor="var(--text2)" fontSize="10px" />
|
|
543
|
+
</div>
|
|
516
544
|
</div>
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
</div>
|
|
525
|
-
</button>
|
|
526
|
-
{/each}
|
|
545
|
+
{:else}
|
|
546
|
+
<Text {label} fontSize="normal" />
|
|
547
|
+
{/if}
|
|
548
|
+
</div>
|
|
549
|
+
</button>
|
|
550
|
+
{/each}
|
|
551
|
+
</div>
|
|
527
552
|
</div>
|
|
528
553
|
</div>
|
|
529
|
-
|
|
554
|
+
{/snippet}
|
|
555
|
+
|
|
556
|
+
<Tooltip
|
|
557
|
+
message={hasAlert ? alert?.message || '' : ''}
|
|
558
|
+
placement="top"
|
|
559
|
+
listener="hover"
|
|
560
|
+
listenerout="hover"
|
|
561
|
+
showArrow={true}
|
|
562
|
+
hidden={!hasAlert}
|
|
563
|
+
disabled={!hasAlert || !alert?.message}
|
|
564
|
+
fontColor="var(--actionPrimaryText)"
|
|
565
|
+
width="max-content"
|
|
566
|
+
padding="6px"
|
|
567
|
+
bgColor={getTooltipColor(alert?.type || 'info')}
|
|
568
|
+
class="select-tooltip"
|
|
569
|
+
>
|
|
570
|
+
{#snippet target()}
|
|
571
|
+
{@render selectWrapper()}
|
|
572
|
+
{/snippet}
|
|
573
|
+
</Tooltip>
|
|
530
574
|
|
|
531
575
|
<style>
|
|
576
|
+
:global(.select-tooltip) {
|
|
577
|
+
padding: 0;
|
|
578
|
+
}
|
|
579
|
+
|
|
532
580
|
.dropdown-item.disabled {
|
|
533
581
|
opacity: 0.75;
|
|
534
582
|
cursor: not-allowed;
|
|
@@ -619,7 +667,7 @@
|
|
|
619
667
|
}
|
|
620
668
|
|
|
621
669
|
.dropdown-list :global(.dropdown-item.hover-state) {
|
|
622
|
-
|
|
670
|
+
outline: 1px solid var(--blueBorder);
|
|
623
671
|
}
|
|
624
672
|
|
|
625
673
|
.dropdown-list {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Placement } from '@floating-ui/dom';
|
|
2
2
|
import type { Component, Snippet } from 'svelte';
|
|
3
|
+
export type AlertType = 'error' | 'success' | 'info' | 'warning';
|
|
4
|
+
export interface AlertConfig {
|
|
5
|
+
type: AlertType;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
3
8
|
export type SelectValue = string & {
|
|
4
9
|
readonly brand: unique symbol;
|
|
5
10
|
};
|
|
@@ -37,6 +42,14 @@ export interface SelectProps {
|
|
|
37
42
|
preventNoSelection?: boolean;
|
|
38
43
|
disabled?: boolean;
|
|
39
44
|
placement?: Placement;
|
|
45
|
+
/**
|
|
46
|
+
* Alert configuration for showing validation messages
|
|
47
|
+
*/
|
|
48
|
+
alert?: AlertConfig | null;
|
|
49
|
+
/**
|
|
50
|
+
* If true, the select will be invalid
|
|
51
|
+
*/
|
|
52
|
+
invalid?: boolean;
|
|
40
53
|
className?: string;
|
|
41
54
|
onchange?: SelectChangeHandler;
|
|
42
55
|
children?: Snippet;
|
|
@@ -345,11 +345,11 @@
|
|
|
345
345
|
{#if tooltip}
|
|
346
346
|
{@render tooltip()}
|
|
347
347
|
{:else if message && raw}
|
|
348
|
-
<div class="message" style="color:{fontColor};">
|
|
348
|
+
<div class="message" style="color:{fontColor}; background-color: {bgColor};">
|
|
349
349
|
{@html message}
|
|
350
350
|
</div>
|
|
351
351
|
{:else if message}
|
|
352
|
-
<div class="message">
|
|
352
|
+
<div class="message" style="color:{fontColor}; background-color: {bgColor};">
|
|
353
353
|
<Text label={formattedMessage} fontSize="11px" fontWeight="500" {fontColor} />
|
|
354
354
|
</div>
|
|
355
355
|
{/if}
|