@proyecto-viviana/ui 0.2.5 → 0.3.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/dist/index.js +210 -557
- package/dist/index.js.map +7 -1
- package/dist/index.ssr.js +42 -399
- package/dist/index.ssr.js.map +7 -1
- package/dist/radio/index.d.ts +27 -12
- package/dist/radio/index.d.ts.map +1 -1
- package/package.json +11 -12
- package/src/alert/index.tsx +0 -48
- package/src/assets/favicon.png +0 -0
- package/src/assets/fire.gif +0 -0
- package/src/autocomplete/index.tsx +0 -313
- package/src/avatar/index.tsx +0 -75
- package/src/badge/index.tsx +0 -43
- package/src/breadcrumbs/index.tsx +0 -207
- package/src/button/Button.tsx +0 -74
- package/src/button/index.ts +0 -2
- package/src/button/types.ts +0 -24
- package/src/calendar/DateField.tsx +0 -200
- package/src/calendar/DatePicker.tsx +0 -298
- package/src/calendar/RangeCalendar.tsx +0 -236
- package/src/calendar/TimeField.tsx +0 -196
- package/src/calendar/index.tsx +0 -223
- package/src/checkbox/index.tsx +0 -257
- package/src/color/index.tsx +0 -687
- package/src/combobox/index.tsx +0 -383
- package/src/components.css +0 -1077
- package/src/custom/calendar-card/index.tsx +0 -66
- package/src/custom/chip/index.tsx +0 -46
- package/src/custom/conversation/index.tsx +0 -105
- package/src/custom/event-card/index.tsx +0 -132
- package/src/custom/header/index.tsx +0 -33
- package/src/custom/lateral-nav/index.tsx +0 -88
- package/src/custom/logo/index.tsx +0 -58
- package/src/custom/nav-header/index.tsx +0 -42
- package/src/custom/page-layout/index.tsx +0 -29
- package/src/custom/profile-card/index.tsx +0 -64
- package/src/custom/project-card/index.tsx +0 -59
- package/src/custom/timeline-item/index.tsx +0 -105
- package/src/dialog/Dialog.tsx +0 -260
- package/src/dialog/index.tsx +0 -3
- package/src/disclosure/index.tsx +0 -307
- package/src/gridlist/index.tsx +0 -403
- package/src/icon/icons/GitHubIcon.tsx +0 -20
- package/src/icon/index.tsx +0 -48
- package/src/index.ts +0 -322
- package/src/landmark/index.tsx +0 -231
- package/src/link/index.tsx +0 -130
- package/src/listbox/index.tsx +0 -231
- package/src/menu/index.tsx +0 -297
- package/src/meter/index.tsx +0 -163
- package/src/numberfield/index.tsx +0 -482
- package/src/popover/index.tsx +0 -260
- package/src/progress-bar/index.tsx +0 -169
- package/src/radio/index.tsx +0 -173
- package/src/searchfield/index.tsx +0 -453
- package/src/select/index.tsx +0 -349
- package/src/separator/index.tsx +0 -141
- package/src/slider/index.tsx +0 -382
- package/src/styles.css +0 -450
- package/src/switch/ToggleSwitch.tsx +0 -112
- package/src/switch/index.tsx +0 -90
- package/src/table/index.tsx +0 -531
- package/src/tabs/index.tsx +0 -273
- package/src/tag-group/index.tsx +0 -240
- package/src/test-utils/index.ts +0 -32
- package/src/textfield/index.tsx +0 -211
- package/src/theme.css +0 -101
- package/src/toast/index.tsx +0 -324
- package/src/toolbar/index.tsx +0 -108
- package/src/tooltip/index.tsx +0 -197
- package/src/tree/index.tsx +0 -494
package/src/meter/index.tsx
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Meter component for proyecto-viviana-ui
|
|
3
|
-
*
|
|
4
|
-
* Styled meter component built on top of the solidaria hook directly.
|
|
5
|
-
* Meters represent a quantity within a known range (unlike progress bars which show progress toward a goal).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { type JSX, splitProps, Show, createMemo } from 'solid-js';
|
|
9
|
-
import { createMeter } from '@proyecto-viviana/solidaria';
|
|
10
|
-
|
|
11
|
-
// ============================================
|
|
12
|
-
// TYPES
|
|
13
|
-
// ============================================
|
|
14
|
-
|
|
15
|
-
export type MeterSize = 'sm' | 'md' | 'lg';
|
|
16
|
-
export type MeterVariant = 'primary' | 'accent' | 'success' | 'warning' | 'danger' | 'info';
|
|
17
|
-
|
|
18
|
-
export interface MeterProps {
|
|
19
|
-
/** The current value (controlled). @default 0 */
|
|
20
|
-
value?: number;
|
|
21
|
-
/** The smallest value allowed. @default 0 */
|
|
22
|
-
minValue?: number;
|
|
23
|
-
/** The largest value allowed. @default 100 */
|
|
24
|
-
maxValue?: number;
|
|
25
|
-
/** The content to display as the value's label (e.g. "75 GB"). */
|
|
26
|
-
valueLabel?: string;
|
|
27
|
-
/** The size of the meter. @default 'md' */
|
|
28
|
-
size?: MeterSize;
|
|
29
|
-
/** The visual style variant. @default 'primary' */
|
|
30
|
-
variant?: MeterVariant;
|
|
31
|
-
/** The label to display above the meter. */
|
|
32
|
-
label?: string;
|
|
33
|
-
/** Whether to show the value text. @default true */
|
|
34
|
-
showValueLabel?: boolean;
|
|
35
|
-
/** Additional CSS class name. */
|
|
36
|
-
class?: string;
|
|
37
|
-
/** An accessibility label. */
|
|
38
|
-
'aria-label'?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ============================================
|
|
42
|
-
// STYLES
|
|
43
|
-
// ============================================
|
|
44
|
-
|
|
45
|
-
const sizeStyles = {
|
|
46
|
-
sm: {
|
|
47
|
-
track: 'h-1',
|
|
48
|
-
text: 'text-xs',
|
|
49
|
-
},
|
|
50
|
-
md: {
|
|
51
|
-
track: 'h-2',
|
|
52
|
-
text: 'text-sm',
|
|
53
|
-
},
|
|
54
|
-
lg: {
|
|
55
|
-
track: 'h-3',
|
|
56
|
-
text: 'text-base',
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const variantStyles = {
|
|
61
|
-
primary: 'bg-primary-500',
|
|
62
|
-
accent: 'bg-accent',
|
|
63
|
-
success: 'bg-green-500',
|
|
64
|
-
warning: 'bg-yellow-500',
|
|
65
|
-
danger: 'bg-red-500',
|
|
66
|
-
info: 'bg-blue-500',
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// ============================================
|
|
70
|
-
// UTILITIES
|
|
71
|
-
// ============================================
|
|
72
|
-
|
|
73
|
-
function clamp(value: number, min: number, max: number): number {
|
|
74
|
-
return Math.min(Math.max(value, min), max);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ============================================
|
|
78
|
-
// METER COMPONENT
|
|
79
|
-
// ============================================
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Meters represent a quantity within a known range, or a fractional value.
|
|
83
|
-
* Unlike progress bars, meters represent a current value rather than progress toward a goal.
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* ```tsx
|
|
87
|
-
* // Storage usage meter
|
|
88
|
-
* <Meter value={75} label="Storage space" valueLabel="75 GB of 100 GB" />
|
|
89
|
-
*
|
|
90
|
-
* // Battery level
|
|
91
|
-
* <Meter value={25} variant="warning" label="Battery" />
|
|
92
|
-
*
|
|
93
|
-
* // CPU usage with dynamic color
|
|
94
|
-
* <Meter value={cpuUsage} variant={cpuUsage > 80 ? 'danger' : 'success'} label="CPU" />
|
|
95
|
-
* ```
|
|
96
|
-
*/
|
|
97
|
-
export function Meter(props: MeterProps): JSX.Element {
|
|
98
|
-
const [local, ariaProps] = splitProps(props, [
|
|
99
|
-
'size',
|
|
100
|
-
'variant',
|
|
101
|
-
'label',
|
|
102
|
-
'showValueLabel',
|
|
103
|
-
'class',
|
|
104
|
-
]);
|
|
105
|
-
|
|
106
|
-
const size = () => local.size ?? 'md';
|
|
107
|
-
const variant = () => local.variant ?? 'primary';
|
|
108
|
-
const showValueLabel = () => local.showValueLabel ?? true;
|
|
109
|
-
|
|
110
|
-
// Create meter aria props
|
|
111
|
-
const meterAria = createMeter({
|
|
112
|
-
get value() { return ariaProps.value; },
|
|
113
|
-
get minValue() { return ariaProps.minValue; },
|
|
114
|
-
get maxValue() { return ariaProps.maxValue; },
|
|
115
|
-
get valueLabel() { return ariaProps.valueLabel; },
|
|
116
|
-
get label() { return local.label; },
|
|
117
|
-
get 'aria-label'() { return ariaProps['aria-label']; },
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// Calculate percentage
|
|
121
|
-
const percentage = createMemo(() => {
|
|
122
|
-
const value = ariaProps.value ?? 0;
|
|
123
|
-
const minValue = ariaProps.minValue ?? 0;
|
|
124
|
-
const maxValue = ariaProps.maxValue ?? 100;
|
|
125
|
-
const clampedValue = clamp(value, minValue, maxValue);
|
|
126
|
-
return ((clampedValue - minValue) / (maxValue - minValue)) * 100;
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Get value text from aria props
|
|
130
|
-
const valueText = () => meterAria.meterProps['aria-valuetext'] as string | undefined;
|
|
131
|
-
|
|
132
|
-
const sizeConfig = () => sizeStyles[size()];
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<div
|
|
136
|
-
{...meterAria.meterProps}
|
|
137
|
-
class={`w-full ${local.class ?? ''}`}
|
|
138
|
-
>
|
|
139
|
-
{/* Label and value row */}
|
|
140
|
-
<Show when={local.label || showValueLabel()}>
|
|
141
|
-
<div class={`flex justify-between items-center mb-1 ${sizeConfig().text}`}>
|
|
142
|
-
<Show when={local.label}>
|
|
143
|
-
<span class="text-primary-200 font-medium">{local.label}</span>
|
|
144
|
-
</Show>
|
|
145
|
-
<Show when={showValueLabel()}>
|
|
146
|
-
<span class="text-primary-300">{valueText()}</span>
|
|
147
|
-
</Show>
|
|
148
|
-
</div>
|
|
149
|
-
</Show>
|
|
150
|
-
|
|
151
|
-
{/* Track */}
|
|
152
|
-
<div class={`w-full ${sizeConfig().track} bg-bg-300 rounded-full overflow-hidden`}>
|
|
153
|
-
{/* Fill */}
|
|
154
|
-
<div
|
|
155
|
-
class={`h-full rounded-full transition-all duration-300 ${variantStyles[variant()]}`}
|
|
156
|
-
style={{
|
|
157
|
-
width: `${percentage()}%`,
|
|
158
|
-
}}
|
|
159
|
-
/>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
@@ -1,482 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NumberField component for proyecto-viviana-ui
|
|
3
|
-
*
|
|
4
|
-
* A styled number field component with increment/decrement buttons.
|
|
5
|
-
* Built directly on solidaria hooks for full accessibility support.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { type JSX, splitProps, mergeProps as solidMergeProps, Show } from 'solid-js'
|
|
9
|
-
import {
|
|
10
|
-
createNumberField,
|
|
11
|
-
createFocusRing,
|
|
12
|
-
createPress,
|
|
13
|
-
createHover,
|
|
14
|
-
type AriaNumberFieldProps,
|
|
15
|
-
} from '@proyecto-viviana/solidaria'
|
|
16
|
-
import {
|
|
17
|
-
createNumberFieldState,
|
|
18
|
-
} from '@proyecto-viviana/solid-stately'
|
|
19
|
-
|
|
20
|
-
// ============================================
|
|
21
|
-
// TYPES
|
|
22
|
-
// ============================================
|
|
23
|
-
|
|
24
|
-
export type NumberFieldSize = 'sm' | 'md' | 'lg'
|
|
25
|
-
export type NumberFieldVariant = 'outline' | 'filled'
|
|
26
|
-
|
|
27
|
-
export interface NumberFieldProps extends Omit<AriaNumberFieldProps, 'label'> {
|
|
28
|
-
/** The size of the number field. */
|
|
29
|
-
size?: NumberFieldSize
|
|
30
|
-
/** The visual variant of the number field. */
|
|
31
|
-
variant?: NumberFieldVariant
|
|
32
|
-
/** Additional CSS class name. */
|
|
33
|
-
class?: string
|
|
34
|
-
/** Label text for the input. */
|
|
35
|
-
label?: string
|
|
36
|
-
/** Description text shown below the input. */
|
|
37
|
-
description?: string
|
|
38
|
-
/** Error message shown when invalid. */
|
|
39
|
-
errorMessage?: string
|
|
40
|
-
/** The current value (controlled). */
|
|
41
|
-
value?: number
|
|
42
|
-
/** The default value (uncontrolled). */
|
|
43
|
-
defaultValue?: number
|
|
44
|
-
/** Handler called when the value changes. */
|
|
45
|
-
onChange?: (value: number) => void
|
|
46
|
-
/** The minimum value. */
|
|
47
|
-
minValue?: number
|
|
48
|
-
/** The maximum value. */
|
|
49
|
-
maxValue?: number
|
|
50
|
-
/** The step value for increment/decrement. */
|
|
51
|
-
step?: number
|
|
52
|
-
/** The locale for number formatting. */
|
|
53
|
-
locale?: string
|
|
54
|
-
/** Number format options. */
|
|
55
|
-
formatOptions?: Intl.NumberFormatOptions
|
|
56
|
-
/** Whether to hide the stepper buttons. */
|
|
57
|
-
hideStepper?: boolean
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ============================================
|
|
61
|
-
// STYLES
|
|
62
|
-
// ============================================
|
|
63
|
-
|
|
64
|
-
const sizeStyles = {
|
|
65
|
-
sm: {
|
|
66
|
-
input: 'h-8 px-2 text-sm',
|
|
67
|
-
label: 'text-sm',
|
|
68
|
-
description: 'text-xs',
|
|
69
|
-
button: 'w-6 h-6 text-sm',
|
|
70
|
-
buttonGap: 'gap-0.5',
|
|
71
|
-
},
|
|
72
|
-
md: {
|
|
73
|
-
input: 'h-10 px-3 text-base',
|
|
74
|
-
label: 'text-sm',
|
|
75
|
-
description: 'text-sm',
|
|
76
|
-
button: 'w-8 h-8 text-base',
|
|
77
|
-
buttonGap: 'gap-1',
|
|
78
|
-
},
|
|
79
|
-
lg: {
|
|
80
|
-
input: 'h-12 px-4 text-lg',
|
|
81
|
-
label: 'text-base',
|
|
82
|
-
description: 'text-sm',
|
|
83
|
-
button: 'w-10 h-10 text-lg',
|
|
84
|
-
buttonGap: 'gap-1',
|
|
85
|
-
},
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ============================================
|
|
89
|
-
// ICONS
|
|
90
|
-
// ============================================
|
|
91
|
-
|
|
92
|
-
function PlusIcon(props: { class?: string }) {
|
|
93
|
-
return (
|
|
94
|
-
<svg
|
|
95
|
-
class={props.class}
|
|
96
|
-
viewBox="0 0 16 16"
|
|
97
|
-
fill="none"
|
|
98
|
-
stroke="currentColor"
|
|
99
|
-
stroke-width="2"
|
|
100
|
-
>
|
|
101
|
-
<path d="M8 3v10M3 8h10" />
|
|
102
|
-
</svg>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function MinusIcon(props: { class?: string }) {
|
|
107
|
-
return (
|
|
108
|
-
<svg
|
|
109
|
-
class={props.class}
|
|
110
|
-
viewBox="0 0 16 16"
|
|
111
|
-
fill="none"
|
|
112
|
-
stroke="currentColor"
|
|
113
|
-
stroke-width="2"
|
|
114
|
-
>
|
|
115
|
-
<path d="M3 8h10" />
|
|
116
|
-
</svg>
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ============================================
|
|
121
|
-
// COMPONENT
|
|
122
|
-
// ============================================
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* A number field allows users to enter a numeric value with increment/decrement controls.
|
|
126
|
-
*
|
|
127
|
-
* Built directly on solidaria hooks for full accessibility support.
|
|
128
|
-
*/
|
|
129
|
-
export function NumberField(props: NumberFieldProps): JSX.Element {
|
|
130
|
-
const defaultProps: Partial<NumberFieldProps> = {
|
|
131
|
-
size: 'md',
|
|
132
|
-
variant: 'outline',
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const merged = solidMergeProps(defaultProps, props)
|
|
136
|
-
|
|
137
|
-
const [local, stateProps, ariaProps] = splitProps(merged, [
|
|
138
|
-
'size',
|
|
139
|
-
'variant',
|
|
140
|
-
'class',
|
|
141
|
-
'label',
|
|
142
|
-
'description',
|
|
143
|
-
'errorMessage',
|
|
144
|
-
'hideStepper',
|
|
145
|
-
], [
|
|
146
|
-
'value',
|
|
147
|
-
'defaultValue',
|
|
148
|
-
'onChange',
|
|
149
|
-
'minValue',
|
|
150
|
-
'maxValue',
|
|
151
|
-
'step',
|
|
152
|
-
'locale',
|
|
153
|
-
'formatOptions',
|
|
154
|
-
])
|
|
155
|
-
|
|
156
|
-
const size = () => sizeStyles[local.size!]
|
|
157
|
-
|
|
158
|
-
// Ref for input element
|
|
159
|
-
let inputRef: HTMLInputElement | undefined
|
|
160
|
-
|
|
161
|
-
// Create number field state
|
|
162
|
-
const state = createNumberFieldState({
|
|
163
|
-
get value() {
|
|
164
|
-
return stateProps.value
|
|
165
|
-
},
|
|
166
|
-
get defaultValue() {
|
|
167
|
-
return stateProps.defaultValue
|
|
168
|
-
},
|
|
169
|
-
get onChange() {
|
|
170
|
-
return stateProps.onChange
|
|
171
|
-
},
|
|
172
|
-
get minValue() {
|
|
173
|
-
return stateProps.minValue
|
|
174
|
-
},
|
|
175
|
-
get maxValue() {
|
|
176
|
-
return stateProps.maxValue
|
|
177
|
-
},
|
|
178
|
-
get step() {
|
|
179
|
-
return stateProps.step
|
|
180
|
-
},
|
|
181
|
-
get locale() {
|
|
182
|
-
return stateProps.locale
|
|
183
|
-
},
|
|
184
|
-
get formatOptions() {
|
|
185
|
-
return stateProps.formatOptions
|
|
186
|
-
},
|
|
187
|
-
get isDisabled() {
|
|
188
|
-
return ariaProps.isDisabled
|
|
189
|
-
},
|
|
190
|
-
get isReadOnly() {
|
|
191
|
-
return ariaProps.isReadOnly
|
|
192
|
-
},
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
// Create number field aria props
|
|
196
|
-
const numberFieldAria = createNumberField(
|
|
197
|
-
{
|
|
198
|
-
get label() {
|
|
199
|
-
return local.label
|
|
200
|
-
},
|
|
201
|
-
get 'aria-label'() {
|
|
202
|
-
return ariaProps['aria-label']
|
|
203
|
-
},
|
|
204
|
-
get 'aria-labelledby'() {
|
|
205
|
-
return ariaProps['aria-labelledby']
|
|
206
|
-
},
|
|
207
|
-
get 'aria-describedby'() {
|
|
208
|
-
return ariaProps['aria-describedby']
|
|
209
|
-
},
|
|
210
|
-
get isDisabled() {
|
|
211
|
-
return ariaProps.isDisabled
|
|
212
|
-
},
|
|
213
|
-
get isReadOnly() {
|
|
214
|
-
return ariaProps.isReadOnly
|
|
215
|
-
},
|
|
216
|
-
get isRequired() {
|
|
217
|
-
return ariaProps.isRequired
|
|
218
|
-
},
|
|
219
|
-
get isInvalid() {
|
|
220
|
-
return ariaProps.isInvalid
|
|
221
|
-
},
|
|
222
|
-
get description() {
|
|
223
|
-
return local.description
|
|
224
|
-
},
|
|
225
|
-
get errorMessage() {
|
|
226
|
-
return local.errorMessage
|
|
227
|
-
},
|
|
228
|
-
get id() {
|
|
229
|
-
return ariaProps.id
|
|
230
|
-
},
|
|
231
|
-
get autoFocus() {
|
|
232
|
-
return ariaProps.autoFocus
|
|
233
|
-
},
|
|
234
|
-
get name() {
|
|
235
|
-
return ariaProps.name
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
state,
|
|
239
|
-
() => inputRef ?? null
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
// Create focus ring for input
|
|
243
|
-
const { isFocused, isFocusVisible, focusProps } = createFocusRing()
|
|
244
|
-
|
|
245
|
-
// Increment button interactions
|
|
246
|
-
const { isPressed: incrementPressed, pressProps: incrementPressProps } = createPress({
|
|
247
|
-
get isDisabled() {
|
|
248
|
-
return ariaProps.isDisabled || !state.canIncrement()
|
|
249
|
-
},
|
|
250
|
-
onPress: () => {
|
|
251
|
-
state.increment()
|
|
252
|
-
inputRef?.focus()
|
|
253
|
-
},
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
const { isHovered: incrementHovered, hoverProps: incrementHoverProps } = createHover({
|
|
257
|
-
get isDisabled() {
|
|
258
|
-
return ariaProps.isDisabled || !state.canIncrement()
|
|
259
|
-
},
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
// Decrement button interactions
|
|
263
|
-
const { isPressed: decrementPressed, pressProps: decrementPressProps } = createPress({
|
|
264
|
-
get isDisabled() {
|
|
265
|
-
return ariaProps.isDisabled || !state.canDecrement()
|
|
266
|
-
},
|
|
267
|
-
onPress: () => {
|
|
268
|
-
state.decrement()
|
|
269
|
-
inputRef?.focus()
|
|
270
|
-
},
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
const { isHovered: decrementHovered, hoverProps: decrementHoverProps } = createHover({
|
|
274
|
-
get isDisabled() {
|
|
275
|
-
return ariaProps.isDisabled || !state.canDecrement()
|
|
276
|
-
},
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
// Compute classes
|
|
280
|
-
const containerClasses = () => {
|
|
281
|
-
const base = 'flex flex-col'
|
|
282
|
-
const disabledClass = ariaProps.isDisabled ? 'opacity-60' : ''
|
|
283
|
-
const custom = local.class || ''
|
|
284
|
-
return [base, disabledClass, custom].filter(Boolean).join(' ')
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const groupClasses = () => {
|
|
288
|
-
const base = 'flex items-center'
|
|
289
|
-
const gapClass = size().buttonGap
|
|
290
|
-
return [base, gapClass].filter(Boolean).join(' ')
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const inputClasses = () => {
|
|
294
|
-
const base = 'flex-1 rounded-md transition-all duration-200 outline-none text-center'
|
|
295
|
-
const sizeClass = size().input
|
|
296
|
-
|
|
297
|
-
let variantClass: string
|
|
298
|
-
if (local.variant === 'filled') {
|
|
299
|
-
variantClass = 'bg-bg-200 border border-transparent'
|
|
300
|
-
} else {
|
|
301
|
-
variantClass = 'bg-transparent border border-bg-400'
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
let stateClass: string
|
|
305
|
-
if (ariaProps.isDisabled) {
|
|
306
|
-
stateClass = 'bg-bg-200 text-primary-500 cursor-not-allowed'
|
|
307
|
-
} else if (ariaProps.isInvalid) {
|
|
308
|
-
stateClass = 'border-danger-500 focus:border-danger-400 focus:ring-2 focus:ring-danger-400/20'
|
|
309
|
-
} else {
|
|
310
|
-
stateClass = 'text-primary-100 placeholder:text-primary-500 focus:border-accent focus:ring-2 focus:ring-accent/20'
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const hoverClass = ariaProps.isDisabled ? '' : 'hover:border-accent-300'
|
|
314
|
-
|
|
315
|
-
return [base, sizeClass, variantClass, stateClass, hoverClass].filter(Boolean).join(' ')
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const buttonClasses = (isIncrement: boolean) => {
|
|
319
|
-
const base = 'flex items-center justify-center rounded-md transition-all duration-150 select-none'
|
|
320
|
-
const sizeClass = size().button
|
|
321
|
-
|
|
322
|
-
const isDisabled = ariaProps.isDisabled || (isIncrement ? !state.canIncrement() : !state.canDecrement())
|
|
323
|
-
const isPressed = isIncrement ? incrementPressed() : decrementPressed()
|
|
324
|
-
const isHovered = isIncrement ? incrementHovered() : decrementHovered()
|
|
325
|
-
|
|
326
|
-
let stateClass: string
|
|
327
|
-
if (isDisabled) {
|
|
328
|
-
stateClass = 'bg-bg-300 text-primary-600 cursor-not-allowed'
|
|
329
|
-
} else if (isPressed) {
|
|
330
|
-
stateClass = 'bg-accent-600 text-white scale-95'
|
|
331
|
-
} else if (isHovered) {
|
|
332
|
-
stateClass = 'bg-accent-500 text-white'
|
|
333
|
-
} else {
|
|
334
|
-
stateClass = 'bg-bg-300 text-primary-200 hover:bg-accent-500 hover:text-white'
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return [base, sizeClass, stateClass].filter(Boolean).join(' ')
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const labelClasses = () => {
|
|
341
|
-
const base = 'block font-medium text-primary-200 mb-1'
|
|
342
|
-
const sizeClass = size().label
|
|
343
|
-
return [base, sizeClass].filter(Boolean).join(' ')
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const descriptionClasses = () => {
|
|
347
|
-
const base = 'mt-1 text-primary-400'
|
|
348
|
-
const sizeClass = size().description
|
|
349
|
-
return [base, sizeClass].filter(Boolean).join(' ')
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const errorClasses = () => {
|
|
353
|
-
const base = 'mt-1 text-danger-500'
|
|
354
|
-
const sizeClass = size().description
|
|
355
|
-
return [base, sizeClass].filter(Boolean).join(' ')
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Clean props helpers
|
|
359
|
-
const cleanInputProps = () => {
|
|
360
|
-
const { ref: _ref, ...rest } = numberFieldAria.inputProps as Record<string, unknown>
|
|
361
|
-
return rest
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const cleanFocusProps = () => {
|
|
365
|
-
const { ref: _ref, ...rest } = focusProps as Record<string, unknown>
|
|
366
|
-
return rest
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const cleanGroupProps = () => {
|
|
370
|
-
const { ref: _ref, ...rest } = numberFieldAria.groupProps as Record<string, unknown>
|
|
371
|
-
return rest
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const cleanDecrementProps = () => {
|
|
375
|
-
const { ref: _ref, ...rest } = numberFieldAria.decrementButtonProps as Record<string, unknown>
|
|
376
|
-
return rest
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const cleanIncrementProps = () => {
|
|
380
|
-
const { ref: _ref, ...rest } = numberFieldAria.incrementButtonProps as Record<string, unknown>
|
|
381
|
-
return rest
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const cleanDecrementPressProps = () => {
|
|
385
|
-
const { ref: _ref, ...rest } = decrementPressProps as Record<string, unknown>
|
|
386
|
-
return rest
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const cleanDecrementHoverProps = () => {
|
|
390
|
-
const { ref: _ref, ...rest } = decrementHoverProps as Record<string, unknown>
|
|
391
|
-
return rest
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const cleanIncrementPressProps = () => {
|
|
395
|
-
const { ref: _ref, ...rest } = incrementPressProps as Record<string, unknown>
|
|
396
|
-
return rest
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const cleanIncrementHoverProps = () => {
|
|
400
|
-
const { ref: _ref, ...rest } = incrementHoverProps as Record<string, unknown>
|
|
401
|
-
return rest
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return (
|
|
405
|
-
<div
|
|
406
|
-
{...cleanGroupProps()}
|
|
407
|
-
class={containerClasses()}
|
|
408
|
-
data-disabled={ariaProps.isDisabled || undefined}
|
|
409
|
-
data-invalid={ariaProps.isInvalid || undefined}
|
|
410
|
-
>
|
|
411
|
-
{/* Label */}
|
|
412
|
-
<Show when={local.label}>
|
|
413
|
-
<span {...numberFieldAria.labelProps} class={labelClasses()}>
|
|
414
|
-
{local.label}
|
|
415
|
-
<Show when={ariaProps.isRequired}>
|
|
416
|
-
<span class="text-danger-500 ml-1">*</span>
|
|
417
|
-
</Show>
|
|
418
|
-
</span>
|
|
419
|
-
</Show>
|
|
420
|
-
|
|
421
|
-
{/* Input Group */}
|
|
422
|
-
<div class={groupClasses()}>
|
|
423
|
-
{/* Decrement Button */}
|
|
424
|
-
<Show when={!local.hideStepper}>
|
|
425
|
-
<button
|
|
426
|
-
{...cleanDecrementProps()}
|
|
427
|
-
{...cleanDecrementPressProps()}
|
|
428
|
-
{...cleanDecrementHoverProps()}
|
|
429
|
-
class={buttonClasses(false)}
|
|
430
|
-
data-pressed={decrementPressed() || undefined}
|
|
431
|
-
data-hovered={decrementHovered() || undefined}
|
|
432
|
-
data-disabled={ariaProps.isDisabled || !state.canDecrement() || undefined}
|
|
433
|
-
>
|
|
434
|
-
<MinusIcon class="w-4 h-4" />
|
|
435
|
-
</button>
|
|
436
|
-
</Show>
|
|
437
|
-
|
|
438
|
-
{/* Input */}
|
|
439
|
-
<input
|
|
440
|
-
ref={inputRef}
|
|
441
|
-
{...cleanInputProps()}
|
|
442
|
-
{...cleanFocusProps()}
|
|
443
|
-
class={inputClasses()}
|
|
444
|
-
data-focused={isFocused() || undefined}
|
|
445
|
-
data-focus-visible={isFocusVisible() || undefined}
|
|
446
|
-
/>
|
|
447
|
-
|
|
448
|
-
{/* Increment Button */}
|
|
449
|
-
<Show when={!local.hideStepper}>
|
|
450
|
-
<button
|
|
451
|
-
{...cleanIncrementProps()}
|
|
452
|
-
{...cleanIncrementPressProps()}
|
|
453
|
-
{...cleanIncrementHoverProps()}
|
|
454
|
-
class={buttonClasses(true)}
|
|
455
|
-
data-pressed={incrementPressed() || undefined}
|
|
456
|
-
data-hovered={incrementHovered() || undefined}
|
|
457
|
-
data-disabled={ariaProps.isDisabled || !state.canIncrement() || undefined}
|
|
458
|
-
>
|
|
459
|
-
<PlusIcon class="w-4 h-4" />
|
|
460
|
-
</button>
|
|
461
|
-
</Show>
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
{/* Description */}
|
|
465
|
-
<Show when={local.description && !ariaProps.isInvalid}>
|
|
466
|
-
<span {...numberFieldAria.descriptionProps} class={descriptionClasses()}>
|
|
467
|
-
{local.description}
|
|
468
|
-
</span>
|
|
469
|
-
</Show>
|
|
470
|
-
|
|
471
|
-
{/* Error Message */}
|
|
472
|
-
<Show when={ariaProps.isInvalid && local.errorMessage}>
|
|
473
|
-
<span {...numberFieldAria.errorMessageProps} class={errorClasses()}>
|
|
474
|
-
{local.errorMessage}
|
|
475
|
-
</span>
|
|
476
|
-
</Show>
|
|
477
|
-
</div>
|
|
478
|
-
)
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Re-export types
|
|
482
|
-
export type { NumberFieldState } from '@proyecto-viviana/solid-stately'
|