@aspect-ops/exon-ui 0.1.0 → 0.2.1
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/README.md +341 -16
- package/dist/components/Accordion/Accordion.svelte +2 -2
- package/dist/components/Accordion/AccordionItem.svelte +2 -2
- package/dist/components/Chatbot/ChatMessage.svelte +143 -0
- package/dist/components/Chatbot/ChatMessage.svelte.d.ts +8 -0
- package/dist/components/Chatbot/Chatbot.svelte +640 -0
- package/dist/components/Chatbot/Chatbot.svelte.d.ts +22 -0
- package/dist/components/Chatbot/index.d.ts +3 -0
- package/dist/components/Chatbot/index.js +2 -0
- package/dist/components/Chatbot/types.d.ts +48 -0
- package/dist/components/Chatbot/types.js +2 -0
- package/dist/components/ContactForm/ContactForm.svelte +564 -0
- package/dist/components/ContactForm/ContactForm.svelte.d.ts +44 -0
- package/dist/components/ContactForm/index.d.ts +1 -0
- package/dist/components/ContactForm/index.js +1 -0
- package/dist/components/DoughnutChart/DoughnutChart.svelte +372 -0
- package/dist/components/DoughnutChart/DoughnutChart.svelte.d.ts +25 -0
- package/dist/components/DoughnutChart/index.d.ts +1 -0
- package/dist/components/DoughnutChart/index.js +1 -0
- package/dist/components/FAB/FAB.svelte +5 -1
- package/dist/components/FAB/FABGroup.svelte +10 -2
- package/dist/components/FileUpload/FileUpload.svelte +12 -12
- package/dist/components/Mermaid/Mermaid.svelte +206 -0
- package/dist/components/Mermaid/Mermaid.svelte.d.ts +28 -0
- package/dist/components/Mermaid/index.d.ts +1 -0
- package/dist/components/Mermaid/index.js +1 -0
- package/dist/components/Mermaid/mermaid.d.ts +21 -0
- package/dist/components/NumberInput/NumberInput.svelte +293 -0
- package/dist/components/NumberInput/NumberInput.svelte.d.ts +16 -0
- package/dist/components/NumberInput/index.d.ts +1 -0
- package/dist/components/NumberInput/index.js +1 -0
- package/dist/components/Pagination/Pagination.svelte +243 -0
- package/dist/components/Pagination/Pagination.svelte.d.ts +10 -0
- package/dist/components/Pagination/index.d.ts +1 -0
- package/dist/components/Pagination/index.js +1 -0
- package/dist/components/Popover/PopoverTrigger.svelte +1 -3
- package/dist/components/ToggleGroup/ToggleGroup.svelte +91 -0
- package/dist/components/ToggleGroup/ToggleGroup.svelte.d.ts +13 -0
- package/dist/components/ToggleGroup/ToggleGroupItem.svelte +158 -0
- package/dist/components/ToggleGroup/ToggleGroupItem.svelte.d.ts +9 -0
- package/dist/components/ToggleGroup/index.d.ts +3 -0
- package/dist/components/ToggleGroup/index.js +2 -0
- package/dist/components/ViewCounter/ViewCounter.svelte +157 -0
- package/dist/components/ViewCounter/ViewCounter.svelte.d.ts +17 -0
- package/dist/components/ViewCounter/index.d.ts +1 -0
- package/dist/components/ViewCounter/index.js +1 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +12 -0
- package/dist/styles/tokens.css +2 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/input.d.ts +35 -0
- package/package.json +2 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
|
|
5
|
+
interface ThemeVariables {
|
|
6
|
+
primaryColor?: string;
|
|
7
|
+
primaryTextColor?: string;
|
|
8
|
+
primaryBorderColor?: string;
|
|
9
|
+
lineColor?: string;
|
|
10
|
+
secondaryColor?: string;
|
|
11
|
+
tertiaryColor?: string;
|
|
12
|
+
background?: string;
|
|
13
|
+
mainBkg?: string;
|
|
14
|
+
nodeBorder?: string;
|
|
15
|
+
clusterBkg?: string;
|
|
16
|
+
titleColor?: string;
|
|
17
|
+
edgeLabelBackground?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
/** Mermaid diagram code. If not provided, uses slot content */
|
|
22
|
+
code?: string;
|
|
23
|
+
/** Custom theme variables */
|
|
24
|
+
theme?: ThemeVariables;
|
|
25
|
+
/** Custom class */
|
|
26
|
+
class?: string;
|
|
27
|
+
/** Slot content for diagram code */
|
|
28
|
+
children?: Snippet;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Default ExonPro theme
|
|
32
|
+
const defaultTheme: ThemeVariables = {
|
|
33
|
+
primaryColor: '#4654A3',
|
|
34
|
+
primaryTextColor: '#270949',
|
|
35
|
+
primaryBorderColor: '#4654A3',
|
|
36
|
+
lineColor: '#270949',
|
|
37
|
+
secondaryColor: '#FF3131',
|
|
38
|
+
tertiaryColor: '#f3f4f6'
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let { code, theme = defaultTheme, class: className = '', children }: Props = $props();
|
|
42
|
+
|
|
43
|
+
let containerRef: HTMLDivElement | undefined = $state();
|
|
44
|
+
let slotRef: HTMLDivElement | undefined = $state();
|
|
45
|
+
let isLoading = $state(true);
|
|
46
|
+
let hasError = $state(false);
|
|
47
|
+
let errorMessage = $state('');
|
|
48
|
+
let renderedSvg = $state('');
|
|
49
|
+
|
|
50
|
+
// Get diagram code from prop or slot
|
|
51
|
+
function getDiagramCode(): string {
|
|
52
|
+
if (code) return code;
|
|
53
|
+
if (slotRef) {
|
|
54
|
+
return slotRef.textContent?.trim() || '';
|
|
55
|
+
}
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onMount(async () => {
|
|
60
|
+
try {
|
|
61
|
+
// Dynamically import mermaid (type assertion for dynamic import)
|
|
62
|
+
const mermaidModule = await import('mermaid');
|
|
63
|
+
const mermaid = mermaidModule.default;
|
|
64
|
+
|
|
65
|
+
// Initialize mermaid with theme
|
|
66
|
+
mermaid.initialize({
|
|
67
|
+
startOnLoad: false,
|
|
68
|
+
theme: 'base',
|
|
69
|
+
themeVariables: { ...defaultTheme, ...theme },
|
|
70
|
+
securityLevel: 'loose',
|
|
71
|
+
fontFamily: 'inherit'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const diagramCode = getDiagramCode();
|
|
75
|
+
if (!diagramCode) {
|
|
76
|
+
hasError = true;
|
|
77
|
+
errorMessage = 'No diagram code provided';
|
|
78
|
+
isLoading = false;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Generate unique ID
|
|
83
|
+
const id = `mermaid-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
84
|
+
|
|
85
|
+
// Render the diagram
|
|
86
|
+
const { svg } = await mermaid.render(id, diagramCode);
|
|
87
|
+
renderedSvg = svg;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
hasError = true;
|
|
90
|
+
errorMessage = error instanceof Error ? error.message : 'Failed to render diagram';
|
|
91
|
+
} finally {
|
|
92
|
+
isLoading = false;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<div class="mermaid-container {className}" bind:this={containerRef}>
|
|
98
|
+
<!-- Hidden slot for extracting diagram code -->
|
|
99
|
+
<div bind:this={slotRef} style="display: none;">
|
|
100
|
+
{#if children}
|
|
101
|
+
{@render children()}
|
|
102
|
+
{/if}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{#if isLoading}
|
|
106
|
+
<div class="mermaid-loading">
|
|
107
|
+
<div class="mermaid-spinner"></div>
|
|
108
|
+
<span>Rendering diagram...</span>
|
|
109
|
+
</div>
|
|
110
|
+
{:else if hasError}
|
|
111
|
+
<div class="mermaid-error" role="alert">
|
|
112
|
+
<svg
|
|
113
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
114
|
+
width="24"
|
|
115
|
+
height="24"
|
|
116
|
+
viewBox="0 0 24 24"
|
|
117
|
+
fill="none"
|
|
118
|
+
stroke="currentColor"
|
|
119
|
+
stroke-width="2"
|
|
120
|
+
>
|
|
121
|
+
<circle cx="12" cy="12" r="10" />
|
|
122
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
|
123
|
+
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
124
|
+
</svg>
|
|
125
|
+
<div>
|
|
126
|
+
<strong>Failed to render diagram</strong>
|
|
127
|
+
<p>{errorMessage}</p>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
{:else}
|
|
131
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
132
|
+
<div class="mermaid-diagram">{@html renderedSvg}</div>
|
|
133
|
+
{/if}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<style>
|
|
137
|
+
.mermaid-container {
|
|
138
|
+
width: 100%;
|
|
139
|
+
font-family: inherit;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Loading */
|
|
143
|
+
.mermaid-loading {
|
|
144
|
+
display: flex;
|
|
145
|
+
flex-direction: column;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: center;
|
|
148
|
+
gap: var(--space-md, 1rem);
|
|
149
|
+
padding: var(--space-xl, 2rem);
|
|
150
|
+
color: var(--color-text-muted, #6b7280);
|
|
151
|
+
font-size: var(--text-sm, 0.875rem);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.mermaid-spinner {
|
|
155
|
+
width: 2rem;
|
|
156
|
+
height: 2rem;
|
|
157
|
+
border: 3px solid var(--color-border, #e5e7eb);
|
|
158
|
+
border-top-color: var(--color-primary, #3b82f6);
|
|
159
|
+
border-radius: var(--radius-full, 9999px);
|
|
160
|
+
animation: spin 0.75s linear infinite;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@keyframes spin {
|
|
164
|
+
to {
|
|
165
|
+
transform: rotate(360deg);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Error */
|
|
170
|
+
.mermaid-error {
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: flex-start;
|
|
173
|
+
gap: var(--space-md, 1rem);
|
|
174
|
+
padding: var(--space-md, 1rem);
|
|
175
|
+
background: var(--color-error-bg, #fee2e2);
|
|
176
|
+
color: var(--color-error, #dc2626);
|
|
177
|
+
border-radius: var(--radius-md, 0.375rem);
|
|
178
|
+
font-size: var(--text-sm, 0.875rem);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.mermaid-error svg {
|
|
182
|
+
flex-shrink: 0;
|
|
183
|
+
margin-top: 2px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.mermaid-error strong {
|
|
187
|
+
display: block;
|
|
188
|
+
font-weight: var(--font-semibold, 600);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.mermaid-error p {
|
|
192
|
+
margin: var(--space-xs, 0.25rem) 0 0;
|
|
193
|
+
opacity: 0.9;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Diagram */
|
|
197
|
+
.mermaid-diagram {
|
|
198
|
+
width: 100%;
|
|
199
|
+
overflow-x: auto;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.mermaid-diagram :global(svg) {
|
|
203
|
+
max-width: 100%;
|
|
204
|
+
height: auto;
|
|
205
|
+
}
|
|
206
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface ThemeVariables {
|
|
3
|
+
primaryColor?: string;
|
|
4
|
+
primaryTextColor?: string;
|
|
5
|
+
primaryBorderColor?: string;
|
|
6
|
+
lineColor?: string;
|
|
7
|
+
secondaryColor?: string;
|
|
8
|
+
tertiaryColor?: string;
|
|
9
|
+
background?: string;
|
|
10
|
+
mainBkg?: string;
|
|
11
|
+
nodeBorder?: string;
|
|
12
|
+
clusterBkg?: string;
|
|
13
|
+
titleColor?: string;
|
|
14
|
+
edgeLabelBackground?: string;
|
|
15
|
+
}
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Mermaid diagram code. If not provided, uses slot content */
|
|
18
|
+
code?: string;
|
|
19
|
+
/** Custom theme variables */
|
|
20
|
+
theme?: ThemeVariables;
|
|
21
|
+
/** Custom class */
|
|
22
|
+
class?: string;
|
|
23
|
+
/** Slot content for diagram code */
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
}
|
|
26
|
+
declare const Mermaid: import("svelte").Component<Props, {}, "">;
|
|
27
|
+
type Mermaid = ReturnType<typeof Mermaid>;
|
|
28
|
+
export default Mermaid;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Mermaid } from './Mermaid.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Mermaid } from './Mermaid.svelte';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Type declarations for mermaid
|
|
2
|
+
declare module 'mermaid' {
|
|
3
|
+
interface MermaidConfig {
|
|
4
|
+
startOnLoad?: boolean;
|
|
5
|
+
theme?: string;
|
|
6
|
+
themeVariables?: Record<string, string | undefined>;
|
|
7
|
+
securityLevel?: string;
|
|
8
|
+
fontFamily?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface RenderResult {
|
|
12
|
+
svg: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const mermaid: {
|
|
16
|
+
initialize: (config: MermaidConfig) => void;
|
|
17
|
+
render: (id: string, code: string) => Promise<RenderResult>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default mermaid;
|
|
21
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
value?: number | null;
|
|
4
|
+
min?: number;
|
|
5
|
+
max?: number;
|
|
6
|
+
step?: number;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
error?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
onValueChange?: (value: number | null) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
value = $bindable(null),
|
|
18
|
+
min,
|
|
19
|
+
max,
|
|
20
|
+
step = 1,
|
|
21
|
+
disabled = false,
|
|
22
|
+
placeholder,
|
|
23
|
+
name,
|
|
24
|
+
id,
|
|
25
|
+
error = false,
|
|
26
|
+
class: className = '',
|
|
27
|
+
onValueChange
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
let inputValue = $state(value?.toString() ?? '');
|
|
31
|
+
|
|
32
|
+
// Sync internal state with external value prop
|
|
33
|
+
$effect(() => {
|
|
34
|
+
inputValue = value?.toString() ?? '';
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const isDecrementDisabled = $derived(
|
|
38
|
+
disabled || (min !== undefined && value !== null && value <= min)
|
|
39
|
+
);
|
|
40
|
+
const isIncrementDisabled = $derived(
|
|
41
|
+
disabled || (max !== undefined && value !== null && value >= max)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
function handleIncrement() {
|
|
45
|
+
if (isIncrementDisabled) return;
|
|
46
|
+
|
|
47
|
+
const currentVal = value ?? 0;
|
|
48
|
+
const newValue = currentVal + step;
|
|
49
|
+
const clampedValue = max !== undefined ? Math.min(newValue, max) : newValue;
|
|
50
|
+
|
|
51
|
+
value = clampedValue;
|
|
52
|
+
onValueChange?.(clampedValue);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleDecrement() {
|
|
56
|
+
if (isDecrementDisabled) return;
|
|
57
|
+
|
|
58
|
+
const currentVal = value ?? 0;
|
|
59
|
+
const newValue = currentVal - step;
|
|
60
|
+
const clampedValue = min !== undefined ? Math.max(newValue, min) : newValue;
|
|
61
|
+
|
|
62
|
+
value = clampedValue;
|
|
63
|
+
onValueChange?.(clampedValue);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
|
|
67
|
+
inputValue = e.currentTarget.value;
|
|
68
|
+
|
|
69
|
+
if (inputValue === '') {
|
|
70
|
+
value = null;
|
|
71
|
+
onValueChange?.(null);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const parsed = parseFloat(inputValue);
|
|
76
|
+
if (!isNaN(parsed)) {
|
|
77
|
+
value = parsed;
|
|
78
|
+
onValueChange?.(parsed);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function handleBlur() {
|
|
83
|
+
if (value === null) {
|
|
84
|
+
inputValue = '';
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Clamp value to min/max on blur
|
|
89
|
+
let clampedValue = value;
|
|
90
|
+
if (min !== undefined && clampedValue < min) {
|
|
91
|
+
clampedValue = min;
|
|
92
|
+
}
|
|
93
|
+
if (max !== undefined && clampedValue > max) {
|
|
94
|
+
clampedValue = max;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (clampedValue !== value) {
|
|
98
|
+
value = clampedValue;
|
|
99
|
+
onValueChange?.(clampedValue);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
inputValue = clampedValue.toString();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
106
|
+
if (e.key === 'ArrowUp') {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
handleIncrement();
|
|
109
|
+
} else if (e.key === 'ArrowDown') {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
handleDecrement();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<div
|
|
117
|
+
class="number-input {error ? 'number-input--error' : ''} {disabled
|
|
118
|
+
? 'number-input--disabled'
|
|
119
|
+
: ''} {className}"
|
|
120
|
+
>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="number-input__button number-input__button--decrement"
|
|
124
|
+
disabled={isDecrementDisabled}
|
|
125
|
+
onclick={handleDecrement}
|
|
126
|
+
aria-label="Decrement value"
|
|
127
|
+
>
|
|
128
|
+
<svg
|
|
129
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
130
|
+
width="16"
|
|
131
|
+
height="16"
|
|
132
|
+
viewBox="0 0 24 24"
|
|
133
|
+
fill="none"
|
|
134
|
+
stroke="currentColor"
|
|
135
|
+
stroke-width="2"
|
|
136
|
+
stroke-linecap="round"
|
|
137
|
+
stroke-linejoin="round"
|
|
138
|
+
>
|
|
139
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
140
|
+
</svg>
|
|
141
|
+
</button>
|
|
142
|
+
|
|
143
|
+
<input
|
|
144
|
+
type="text"
|
|
145
|
+
inputmode="decimal"
|
|
146
|
+
class="number-input__field"
|
|
147
|
+
role="spinbutton"
|
|
148
|
+
aria-valuemin={min}
|
|
149
|
+
aria-valuemax={max}
|
|
150
|
+
aria-valuenow={value ?? undefined}
|
|
151
|
+
{name}
|
|
152
|
+
{id}
|
|
153
|
+
{placeholder}
|
|
154
|
+
{disabled}
|
|
155
|
+
aria-invalid={error}
|
|
156
|
+
value={inputValue}
|
|
157
|
+
oninput={handleInput}
|
|
158
|
+
onblur={handleBlur}
|
|
159
|
+
onkeydown={handleKeyDown}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
class="number-input__button number-input__button--increment"
|
|
165
|
+
disabled={isIncrementDisabled}
|
|
166
|
+
onclick={handleIncrement}
|
|
167
|
+
aria-label="Increment value"
|
|
168
|
+
>
|
|
169
|
+
<svg
|
|
170
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
171
|
+
width="16"
|
|
172
|
+
height="16"
|
|
173
|
+
viewBox="0 0 24 24"
|
|
174
|
+
fill="none"
|
|
175
|
+
stroke="currentColor"
|
|
176
|
+
stroke-width="2"
|
|
177
|
+
stroke-linecap="round"
|
|
178
|
+
stroke-linejoin="round"
|
|
179
|
+
>
|
|
180
|
+
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
181
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
182
|
+
</svg>
|
|
183
|
+
</button>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<style>
|
|
187
|
+
.number-input {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: stretch;
|
|
190
|
+
width: 100%;
|
|
191
|
+
gap: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.number-input--disabled {
|
|
195
|
+
opacity: 0.5;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.number-input__button {
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
justify-content: center;
|
|
202
|
+
min-width: var(--touch-target-min, 44px);
|
|
203
|
+
min-height: var(--touch-target-min, 44px);
|
|
204
|
+
padding: 0;
|
|
205
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
206
|
+
background: var(--color-bg, #ffffff);
|
|
207
|
+
color: var(--color-text, #1f2937);
|
|
208
|
+
font-family: inherit;
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
transition: all var(--transition-fast, 150ms ease);
|
|
211
|
+
-webkit-tap-highlight-color: transparent;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.number-input__button:hover:not(:disabled) {
|
|
215
|
+
background: var(--color-bg-muted, #f3f4f6);
|
|
216
|
+
border-color: var(--color-border-hover, #d1d5db);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.number-input__button:active:not(:disabled) {
|
|
220
|
+
background: var(--color-border, #e5e7eb);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.number-input__button:focus-visible {
|
|
224
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
225
|
+
outline-offset: 2px;
|
|
226
|
+
z-index: 1;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.number-input__button:disabled {
|
|
230
|
+
opacity: 0.3;
|
|
231
|
+
cursor: not-allowed;
|
|
232
|
+
pointer-events: none;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.number-input__button--decrement {
|
|
236
|
+
border-top-left-radius: var(--radius-md, 0.375rem);
|
|
237
|
+
border-bottom-left-radius: var(--radius-md, 0.375rem);
|
|
238
|
+
border-right: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.number-input__button--increment {
|
|
242
|
+
border-top-right-radius: var(--radius-md, 0.375rem);
|
|
243
|
+
border-bottom-right-radius: var(--radius-md, 0.375rem);
|
|
244
|
+
border-left: none;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.number-input__field {
|
|
248
|
+
flex: 1;
|
|
249
|
+
min-width: 0;
|
|
250
|
+
min-height: var(--touch-target-min, 44px);
|
|
251
|
+
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
|
252
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
253
|
+
border-left: none;
|
|
254
|
+
border-right: none;
|
|
255
|
+
background: var(--color-bg, #ffffff);
|
|
256
|
+
color: var(--color-text, #1f2937);
|
|
257
|
+
font-family: inherit;
|
|
258
|
+
font-size: var(--text-sm, 0.875rem);
|
|
259
|
+
line-height: 1.5;
|
|
260
|
+
text-align: center;
|
|
261
|
+
transition:
|
|
262
|
+
border-color var(--transition-fast, 150ms ease),
|
|
263
|
+
box-shadow var(--transition-fast, 150ms ease);
|
|
264
|
+
-webkit-tap-highlight-color: transparent;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.number-input__field::placeholder {
|
|
268
|
+
color: var(--color-text-muted, #9ca3af);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.number-input__field:focus {
|
|
272
|
+
outline: none;
|
|
273
|
+
border-color: var(--color-primary, #3b82f6);
|
|
274
|
+
box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
|
|
275
|
+
z-index: 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.number-input__field:disabled {
|
|
279
|
+
background: var(--color-bg-muted, #f3f4f6);
|
|
280
|
+
cursor: not-allowed;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Error state */
|
|
284
|
+
.number-input--error .number-input__field,
|
|
285
|
+
.number-input--error .number-input__button {
|
|
286
|
+
border-color: var(--color-error, #ef4444);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.number-input--error .number-input__field:focus {
|
|
290
|
+
border-color: var(--color-error, #ef4444);
|
|
291
|
+
box-shadow: 0 0 0 3px var(--color-error-alpha, rgba(239, 68, 68, 0.1));
|
|
292
|
+
}
|
|
293
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value?: number | null;
|
|
3
|
+
min?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
step?: number;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
error?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
onValueChange?: (value: number | null) => void;
|
|
13
|
+
}
|
|
14
|
+
declare const NumberInput: import("svelte").Component<Props, {}, "value">;
|
|
15
|
+
type NumberInput = ReturnType<typeof NumberInput>;
|
|
16
|
+
export default NumberInput;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as NumberInput } from './NumberInput.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as NumberInput } from './NumberInput.svelte';
|