@proyecto-viviana/ui 0.2.3 → 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 +192 -179
- package/dist/index.js.map +3 -3
- package/dist/index.ssr.js +24 -21
- package/dist/index.ssr.js.map +3 -3
- package/dist/radio/index.d.ts +27 -12
- package/dist/radio/index.d.ts.map +1 -1
- package/package.json +12 -13
- 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
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TimeField component for proyecto-viviana-ui
|
|
3
|
-
*
|
|
4
|
-
* Styled time field component with segment-based editing.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { type JSX, splitProps } from 'solid-js';
|
|
8
|
-
import {
|
|
9
|
-
TimeField as HeadlessTimeField,
|
|
10
|
-
TimeInput,
|
|
11
|
-
TimeSegment,
|
|
12
|
-
type TimeFieldProps as HeadlessTimeFieldProps,
|
|
13
|
-
type TimeValue,
|
|
14
|
-
} from '@proyecto-viviana/solidaria-components';
|
|
15
|
-
|
|
16
|
-
// ============================================
|
|
17
|
-
// TYPES
|
|
18
|
-
// ============================================
|
|
19
|
-
|
|
20
|
-
export type TimeFieldSize = 'sm' | 'md' | 'lg';
|
|
21
|
-
|
|
22
|
-
export interface TimeFieldProps<T extends TimeValue = TimeValue>
|
|
23
|
-
extends Omit<HeadlessTimeFieldProps<T>, 'class' | 'style' | 'children'> {
|
|
24
|
-
/** The size of the field. @default 'md' */
|
|
25
|
-
size?: TimeFieldSize;
|
|
26
|
-
/** Additional CSS class name. */
|
|
27
|
-
class?: string;
|
|
28
|
-
/** Label for the field. */
|
|
29
|
-
label?: string;
|
|
30
|
-
/** Description text. */
|
|
31
|
-
description?: string;
|
|
32
|
-
/** Error message. */
|
|
33
|
-
errorMessage?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ============================================
|
|
37
|
-
// STYLES
|
|
38
|
-
// ============================================
|
|
39
|
-
|
|
40
|
-
const sizeStyles = {
|
|
41
|
-
sm: {
|
|
42
|
-
container: 'text-sm',
|
|
43
|
-
input: 'px-2 py-1 gap-0.5',
|
|
44
|
-
segment: 'px-0.5',
|
|
45
|
-
label: 'text-xs',
|
|
46
|
-
},
|
|
47
|
-
md: {
|
|
48
|
-
container: 'text-base',
|
|
49
|
-
input: 'px-3 py-2 gap-1',
|
|
50
|
-
segment: 'px-1',
|
|
51
|
-
label: 'text-sm',
|
|
52
|
-
},
|
|
53
|
-
lg: {
|
|
54
|
-
container: 'text-lg',
|
|
55
|
-
input: 'px-4 py-3 gap-1.5',
|
|
56
|
-
segment: 'px-1.5',
|
|
57
|
-
label: 'text-base',
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// ============================================
|
|
62
|
-
// TIME FIELD COMPONENT
|
|
63
|
-
// ============================================
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* A time field allows users to enter and edit time values using a keyboard.
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```tsx
|
|
70
|
-
* // Basic usage
|
|
71
|
-
* <TimeField label="Start time" />
|
|
72
|
-
*
|
|
73
|
-
* // With 24-hour format
|
|
74
|
-
* <TimeField
|
|
75
|
-
* label="Meeting time"
|
|
76
|
-
* hourCycle={24}
|
|
77
|
-
* />
|
|
78
|
-
*
|
|
79
|
-
* // With seconds
|
|
80
|
-
* <TimeField
|
|
81
|
-
* label="Precise time"
|
|
82
|
-
* granularity="second"
|
|
83
|
-
* />
|
|
84
|
-
* ```
|
|
85
|
-
*/
|
|
86
|
-
export function TimeField<T extends TimeValue = TimeValue>(
|
|
87
|
-
props: TimeFieldProps<T>
|
|
88
|
-
): JSX.Element {
|
|
89
|
-
const [local, rest] = splitProps(props, [
|
|
90
|
-
'size',
|
|
91
|
-
'class',
|
|
92
|
-
'label',
|
|
93
|
-
'description',
|
|
94
|
-
'errorMessage',
|
|
95
|
-
'isInvalid',
|
|
96
|
-
]);
|
|
97
|
-
|
|
98
|
-
const size = () => local.size ?? 'md';
|
|
99
|
-
const sizeConfig = () => sizeStyles[size()];
|
|
100
|
-
const isInvalid = () => local.isInvalid || !!local.errorMessage;
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<HeadlessTimeField
|
|
104
|
-
{...rest}
|
|
105
|
-
isInvalid={isInvalid()}
|
|
106
|
-
class={`
|
|
107
|
-
flex flex-col gap-1
|
|
108
|
-
${sizeConfig().container}
|
|
109
|
-
${local.class ?? ''}
|
|
110
|
-
`}
|
|
111
|
-
>
|
|
112
|
-
{/* Label */}
|
|
113
|
-
{local.label && (
|
|
114
|
-
<label class={`font-medium text-primary-200 ${sizeConfig().label}`}>
|
|
115
|
-
{local.label}
|
|
116
|
-
{rest.isRequired && <span class="text-red-500 ml-0.5">*</span>}
|
|
117
|
-
</label>
|
|
118
|
-
)}
|
|
119
|
-
|
|
120
|
-
{/* Input container */}
|
|
121
|
-
<TimeInput
|
|
122
|
-
class={({ isFocused, isDisabled }) => {
|
|
123
|
-
const base = `
|
|
124
|
-
inline-flex items-center
|
|
125
|
-
${sizeConfig().input}
|
|
126
|
-
bg-bg-400 rounded-md border
|
|
127
|
-
transition-colors duration-150
|
|
128
|
-
`;
|
|
129
|
-
|
|
130
|
-
let borderClass = 'border-primary-600';
|
|
131
|
-
if (isInvalid()) {
|
|
132
|
-
borderClass = 'border-red-500';
|
|
133
|
-
} else if (isFocused) {
|
|
134
|
-
borderClass = 'border-accent';
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const disabledClass = isDisabled
|
|
138
|
-
? 'opacity-50 cursor-not-allowed'
|
|
139
|
-
: '';
|
|
140
|
-
|
|
141
|
-
const focusClass = isFocused
|
|
142
|
-
? 'ring-2 ring-accent/30'
|
|
143
|
-
: '';
|
|
144
|
-
|
|
145
|
-
return `${base} ${borderClass} ${disabledClass} ${focusClass}`.trim();
|
|
146
|
-
}}
|
|
147
|
-
>
|
|
148
|
-
{(segment) => (
|
|
149
|
-
<TimeSegment
|
|
150
|
-
segment={segment}
|
|
151
|
-
class={({ isFocused, isPlaceholder, isEditable }) => {
|
|
152
|
-
const base = `
|
|
153
|
-
${sizeConfig().segment}
|
|
154
|
-
rounded
|
|
155
|
-
outline-none
|
|
156
|
-
tabular-nums
|
|
157
|
-
`;
|
|
158
|
-
|
|
159
|
-
let stateClass = '';
|
|
160
|
-
if (segment.type === 'literal') {
|
|
161
|
-
stateClass = 'text-primary-400';
|
|
162
|
-
} else if (isPlaceholder) {
|
|
163
|
-
stateClass = 'text-primary-500 italic';
|
|
164
|
-
} else {
|
|
165
|
-
stateClass = 'text-primary-100';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const focusClass = isFocused && isEditable
|
|
169
|
-
? 'bg-accent text-white'
|
|
170
|
-
: '';
|
|
171
|
-
|
|
172
|
-
return `${base} ${stateClass} ${focusClass}`.trim();
|
|
173
|
-
}}
|
|
174
|
-
/>
|
|
175
|
-
)}
|
|
176
|
-
</TimeInput>
|
|
177
|
-
|
|
178
|
-
{/* Description */}
|
|
179
|
-
{local.description && !isInvalid() && (
|
|
180
|
-
<p class={`text-primary-400 ${sizeConfig().label}`}>
|
|
181
|
-
{local.description}
|
|
182
|
-
</p>
|
|
183
|
-
)}
|
|
184
|
-
|
|
185
|
-
{/* Error message */}
|
|
186
|
-
{isInvalid() && local.errorMessage && (
|
|
187
|
-
<p class={`text-red-500 ${sizeConfig().label}`}>
|
|
188
|
-
{local.errorMessage}
|
|
189
|
-
</p>
|
|
190
|
-
)}
|
|
191
|
-
</HeadlessTimeField>
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Re-export types
|
|
196
|
-
export type { TimeValue };
|
package/src/calendar/index.tsx
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calendar component for proyecto-viviana-ui
|
|
3
|
-
*
|
|
4
|
-
* Styled calendar component built on top of solidaria-components.
|
|
5
|
-
* A calendar displays a grid of days and allows users to select dates.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { type JSX, splitProps } from 'solid-js';
|
|
9
|
-
import {
|
|
10
|
-
Calendar as HeadlessCalendar,
|
|
11
|
-
CalendarHeading,
|
|
12
|
-
CalendarButton,
|
|
13
|
-
CalendarGrid,
|
|
14
|
-
CalendarCell,
|
|
15
|
-
type CalendarDate,
|
|
16
|
-
type DateValue,
|
|
17
|
-
} from '@proyecto-viviana/solidaria-components';
|
|
18
|
-
import type { CalendarStateProps } from '@proyecto-viviana/solid-stately';
|
|
19
|
-
|
|
20
|
-
// ============================================
|
|
21
|
-
// TYPES
|
|
22
|
-
// ============================================
|
|
23
|
-
|
|
24
|
-
export type CalendarSize = 'sm' | 'md' | 'lg';
|
|
25
|
-
|
|
26
|
-
export interface CalendarProps<T extends DateValue = DateValue>
|
|
27
|
-
extends Omit<CalendarStateProps<T>, 'locale'> {
|
|
28
|
-
/** The size of the calendar. @default 'md' */
|
|
29
|
-
size?: CalendarSize;
|
|
30
|
-
/** Additional CSS class name. */
|
|
31
|
-
class?: string;
|
|
32
|
-
/** Whether to show week numbers. */
|
|
33
|
-
showWeekNumbers?: boolean;
|
|
34
|
-
/** The locale to use for formatting. */
|
|
35
|
-
locale?: string;
|
|
36
|
-
/** Custom aria label. */
|
|
37
|
-
'aria-label'?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ============================================
|
|
41
|
-
// STYLES
|
|
42
|
-
// ============================================
|
|
43
|
-
|
|
44
|
-
const sizeStyles = {
|
|
45
|
-
sm: {
|
|
46
|
-
container: 'w-64',
|
|
47
|
-
header: 'text-sm',
|
|
48
|
-
cell: 'w-8 h-8 text-xs',
|
|
49
|
-
button: 'w-6 h-6',
|
|
50
|
-
},
|
|
51
|
-
md: {
|
|
52
|
-
container: 'w-80',
|
|
53
|
-
header: 'text-base',
|
|
54
|
-
cell: 'w-10 h-10 text-sm',
|
|
55
|
-
button: 'w-8 h-8',
|
|
56
|
-
},
|
|
57
|
-
lg: {
|
|
58
|
-
container: 'w-96',
|
|
59
|
-
header: 'text-lg',
|
|
60
|
-
cell: 'w-12 h-12 text-base',
|
|
61
|
-
button: 'w-10 h-10',
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// ============================================
|
|
66
|
-
// CALENDAR COMPONENT
|
|
67
|
-
// ============================================
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* A calendar displays a grid of days and allows users to select a date.
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```tsx
|
|
74
|
-
* // Basic usage
|
|
75
|
-
* <Calendar
|
|
76
|
-
* aria-label="Event date"
|
|
77
|
-
* onChange={(date) => console.log(date)}
|
|
78
|
-
* />
|
|
79
|
-
*
|
|
80
|
-
* // Controlled
|
|
81
|
-
* const [date, setDate] = createSignal<CalendarDate | null>(null);
|
|
82
|
-
* <Calendar
|
|
83
|
-
* value={date()}
|
|
84
|
-
* onChange={setDate}
|
|
85
|
-
* />
|
|
86
|
-
*
|
|
87
|
-
* // With min/max dates
|
|
88
|
-
* <Calendar
|
|
89
|
-
* minValue={today(getLocalTimeZone())}
|
|
90
|
-
* maxValue={today(getLocalTimeZone()).add({ months: 3 })}
|
|
91
|
-
* />
|
|
92
|
-
* ```
|
|
93
|
-
*/
|
|
94
|
-
export function Calendar<T extends DateValue = CalendarDate>(
|
|
95
|
-
props: CalendarProps<T>
|
|
96
|
-
): JSX.Element {
|
|
97
|
-
const [local, rest] = splitProps(props, [
|
|
98
|
-
'size',
|
|
99
|
-
'class',
|
|
100
|
-
'showWeekNumbers',
|
|
101
|
-
'aria-label',
|
|
102
|
-
]);
|
|
103
|
-
|
|
104
|
-
const size = () => local.size ?? 'md';
|
|
105
|
-
const sizeConfig = () => sizeStyles[size()];
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<HeadlessCalendar
|
|
109
|
-
{...rest}
|
|
110
|
-
aria-label={local['aria-label']}
|
|
111
|
-
class={`
|
|
112
|
-
${sizeConfig().container}
|
|
113
|
-
bg-bg-500 rounded-lg border border-primary-700 p-4
|
|
114
|
-
${local.class ?? ''}
|
|
115
|
-
`}
|
|
116
|
-
>
|
|
117
|
-
{/* Header with navigation */}
|
|
118
|
-
<header class="flex items-center justify-between mb-4">
|
|
119
|
-
<CalendarButton
|
|
120
|
-
slot="previous"
|
|
121
|
-
class={`
|
|
122
|
-
${sizeConfig().button}
|
|
123
|
-
flex items-center justify-center
|
|
124
|
-
rounded-md text-primary-200
|
|
125
|
-
hover:bg-bg-400 transition-colors
|
|
126
|
-
disabled:opacity-50 disabled:cursor-not-allowed
|
|
127
|
-
focus:outline-none focus:ring-2 focus:ring-accent/50
|
|
128
|
-
`}
|
|
129
|
-
>
|
|
130
|
-
<svg
|
|
131
|
-
viewBox="0 0 24 24"
|
|
132
|
-
fill="none"
|
|
133
|
-
stroke="currentColor"
|
|
134
|
-
stroke-width="2"
|
|
135
|
-
stroke-linecap="round"
|
|
136
|
-
stroke-linejoin="round"
|
|
137
|
-
class="w-4 h-4"
|
|
138
|
-
>
|
|
139
|
-
<polyline points="15 18 9 12 15 6" />
|
|
140
|
-
</svg>
|
|
141
|
-
</CalendarButton>
|
|
142
|
-
|
|
143
|
-
<CalendarHeading
|
|
144
|
-
class={`
|
|
145
|
-
font-semibold text-primary-100
|
|
146
|
-
${sizeConfig().header}
|
|
147
|
-
`}
|
|
148
|
-
/>
|
|
149
|
-
|
|
150
|
-
<CalendarButton
|
|
151
|
-
slot="next"
|
|
152
|
-
class={`
|
|
153
|
-
${sizeConfig().button}
|
|
154
|
-
flex items-center justify-center
|
|
155
|
-
rounded-md text-primary-200
|
|
156
|
-
hover:bg-bg-400 transition-colors
|
|
157
|
-
disabled:opacity-50 disabled:cursor-not-allowed
|
|
158
|
-
focus:outline-none focus:ring-2 focus:ring-accent/50
|
|
159
|
-
`}
|
|
160
|
-
>
|
|
161
|
-
<svg
|
|
162
|
-
viewBox="0 0 24 24"
|
|
163
|
-
fill="none"
|
|
164
|
-
stroke="currentColor"
|
|
165
|
-
stroke-width="2"
|
|
166
|
-
stroke-linecap="round"
|
|
167
|
-
stroke-linejoin="round"
|
|
168
|
-
class="w-4 h-4"
|
|
169
|
-
>
|
|
170
|
-
<polyline points="9 18 15 12 9 6" />
|
|
171
|
-
</svg>
|
|
172
|
-
</CalendarButton>
|
|
173
|
-
</header>
|
|
174
|
-
|
|
175
|
-
{/* Calendar grid */}
|
|
176
|
-
<CalendarGrid
|
|
177
|
-
class="w-full border-collapse"
|
|
178
|
-
>
|
|
179
|
-
{(date) => (
|
|
180
|
-
<CalendarCell
|
|
181
|
-
date={date}
|
|
182
|
-
class={({ isSelected, isFocused, isDisabled, isOutsideMonth, isToday, isPressed }) => {
|
|
183
|
-
const base = `
|
|
184
|
-
${sizeConfig().cell}
|
|
185
|
-
flex items-center justify-center
|
|
186
|
-
rounded-md cursor-pointer
|
|
187
|
-
transition-colors duration-150
|
|
188
|
-
focus:outline-none
|
|
189
|
-
`;
|
|
190
|
-
|
|
191
|
-
let stateClass = '';
|
|
192
|
-
|
|
193
|
-
if (isDisabled) {
|
|
194
|
-
stateClass = 'text-primary-600 cursor-not-allowed';
|
|
195
|
-
} else if (isSelected) {
|
|
196
|
-
stateClass = 'bg-accent text-white font-medium';
|
|
197
|
-
} else if (isOutsideMonth) {
|
|
198
|
-
stateClass = 'text-primary-600';
|
|
199
|
-
} else if (isToday) {
|
|
200
|
-
stateClass = 'ring-1 ring-accent text-primary-100';
|
|
201
|
-
} else {
|
|
202
|
-
stateClass = 'text-primary-200 hover:bg-bg-400';
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const focusClass = isFocused && !isSelected
|
|
206
|
-
? 'ring-2 ring-accent/50'
|
|
207
|
-
: '';
|
|
208
|
-
|
|
209
|
-
const pressedClass = isPressed && !isDisabled
|
|
210
|
-
? 'scale-95'
|
|
211
|
-
: '';
|
|
212
|
-
|
|
213
|
-
return `${base} ${stateClass} ${focusClass} ${pressedClass}`.trim();
|
|
214
|
-
}}
|
|
215
|
-
/>
|
|
216
|
-
)}
|
|
217
|
-
</CalendarGrid>
|
|
218
|
-
</HeadlessCalendar>
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Re-export types
|
|
223
|
-
export type { CalendarDate, DateValue };
|
package/src/checkbox/index.tsx
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Checkbox component for proyecto-viviana-ui
|
|
3
|
-
*
|
|
4
|
-
* A styled checkbox component built on top of solidaria-components.
|
|
5
|
-
* This component only handles styling - all behavior and accessibility
|
|
6
|
-
* is provided by the headless Checkbox from solidaria-components.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { type JSX, splitProps, mergeProps as solidMergeProps, Show } from 'solid-js'
|
|
10
|
-
import {
|
|
11
|
-
Checkbox as HeadlessCheckbox,
|
|
12
|
-
CheckboxGroup as HeadlessCheckboxGroup,
|
|
13
|
-
type CheckboxProps as HeadlessCheckboxProps,
|
|
14
|
-
type CheckboxGroupProps as HeadlessCheckboxGroupProps,
|
|
15
|
-
type CheckboxRenderProps,
|
|
16
|
-
type CheckboxGroupRenderProps,
|
|
17
|
-
} from '@proyecto-viviana/solidaria-components'
|
|
18
|
-
|
|
19
|
-
// ============================================
|
|
20
|
-
// TYPES
|
|
21
|
-
// ============================================
|
|
22
|
-
|
|
23
|
-
export type CheckboxSize = 'sm' | 'md' | 'lg'
|
|
24
|
-
|
|
25
|
-
export interface CheckboxProps extends Omit<HeadlessCheckboxProps, 'class' | 'children' | 'style'> {
|
|
26
|
-
/** The size of the checkbox. */
|
|
27
|
-
size?: CheckboxSize
|
|
28
|
-
/** Additional CSS class name. */
|
|
29
|
-
class?: string
|
|
30
|
-
/** Label text for the checkbox. */
|
|
31
|
-
children?: JSX.Element
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface CheckboxGroupProps extends Omit<HeadlessCheckboxGroupProps, 'class' | 'children' | 'style'> {
|
|
35
|
-
/** Additional CSS class name. */
|
|
36
|
-
class?: string
|
|
37
|
-
/** Children checkboxes. */
|
|
38
|
-
children?: JSX.Element
|
|
39
|
-
/** Label for the group. */
|
|
40
|
-
label?: string
|
|
41
|
-
/** Description for the group. */
|
|
42
|
-
description?: string
|
|
43
|
-
/** Error message when invalid. */
|
|
44
|
-
errorMessage?: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ============================================
|
|
48
|
-
// STYLES
|
|
49
|
-
// ============================================
|
|
50
|
-
|
|
51
|
-
const sizeStyles = {
|
|
52
|
-
sm: {
|
|
53
|
-
box: 'h-4 w-4',
|
|
54
|
-
icon: 'h-3 w-3',
|
|
55
|
-
label: 'text-sm',
|
|
56
|
-
},
|
|
57
|
-
md: {
|
|
58
|
-
box: 'h-5 w-5',
|
|
59
|
-
icon: 'h-3.5 w-3.5',
|
|
60
|
-
label: 'text-base',
|
|
61
|
-
},
|
|
62
|
-
lg: {
|
|
63
|
-
box: 'h-6 w-6',
|
|
64
|
-
icon: 'h-4 w-4',
|
|
65
|
-
label: 'text-lg',
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ============================================
|
|
70
|
-
// ICONS
|
|
71
|
-
// ============================================
|
|
72
|
-
|
|
73
|
-
function CheckIcon(props: { class?: string }) {
|
|
74
|
-
return (
|
|
75
|
-
<svg
|
|
76
|
-
class={props.class}
|
|
77
|
-
viewBox="0 0 12 10"
|
|
78
|
-
fill="none"
|
|
79
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
80
|
-
>
|
|
81
|
-
<path
|
|
82
|
-
d="M1 5L4.5 8.5L11 1"
|
|
83
|
-
stroke="currentColor"
|
|
84
|
-
stroke-width="2"
|
|
85
|
-
stroke-linecap="round"
|
|
86
|
-
stroke-linejoin="round"
|
|
87
|
-
/>
|
|
88
|
-
</svg>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function IndeterminateIcon(props: { class?: string }) {
|
|
93
|
-
return (
|
|
94
|
-
<svg
|
|
95
|
-
class={props.class}
|
|
96
|
-
viewBox="0 0 12 2"
|
|
97
|
-
fill="none"
|
|
98
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
99
|
-
>
|
|
100
|
-
<path
|
|
101
|
-
d="M1 1H11"
|
|
102
|
-
stroke="currentColor"
|
|
103
|
-
stroke-width="2"
|
|
104
|
-
stroke-linecap="round"
|
|
105
|
-
/>
|
|
106
|
-
</svg>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ============================================
|
|
111
|
-
// CHECKBOX COMPONENT
|
|
112
|
-
// ============================================
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* A checkbox allows users to select one or more items from a set.
|
|
116
|
-
*
|
|
117
|
-
* Built on solidaria-components Checkbox for full accessibility support.
|
|
118
|
-
*/
|
|
119
|
-
export function Checkbox(props: CheckboxProps): JSX.Element {
|
|
120
|
-
const defaultProps: Partial<CheckboxProps> = {
|
|
121
|
-
size: 'md',
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const merged = solidMergeProps(defaultProps, props)
|
|
125
|
-
|
|
126
|
-
const [local, headlessProps] = splitProps(merged, [
|
|
127
|
-
'size',
|
|
128
|
-
'class',
|
|
129
|
-
'children',
|
|
130
|
-
])
|
|
131
|
-
|
|
132
|
-
const size = () => sizeStyles[local.size!]
|
|
133
|
-
|
|
134
|
-
// Generate class based on render props
|
|
135
|
-
const getClassName = (renderProps: CheckboxRenderProps): string => {
|
|
136
|
-
const base = 'inline-flex items-center gap-2 cursor-pointer'
|
|
137
|
-
const disabledClass = renderProps.isDisabled ? 'cursor-not-allowed opacity-50' : ''
|
|
138
|
-
const custom = local.class || ''
|
|
139
|
-
return [base, disabledClass, custom].filter(Boolean).join(' ')
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return (
|
|
143
|
-
<HeadlessCheckbox
|
|
144
|
-
{...headlessProps}
|
|
145
|
-
class={getClassName}
|
|
146
|
-
>
|
|
147
|
-
{(renderProps: CheckboxRenderProps) => {
|
|
148
|
-
const boxClasses = () => {
|
|
149
|
-
const base = 'relative flex items-center justify-center rounded border-2 transition-all duration-200'
|
|
150
|
-
const sizeClass = size().box
|
|
151
|
-
|
|
152
|
-
let colorClass: string
|
|
153
|
-
if (renderProps.isDisabled) {
|
|
154
|
-
colorClass = 'border-bg-300 bg-bg-200'
|
|
155
|
-
} else if (renderProps.isSelected || renderProps.isIndeterminate) {
|
|
156
|
-
colorClass = 'border-accent bg-accent'
|
|
157
|
-
} else {
|
|
158
|
-
colorClass = 'border-primary-600 bg-transparent hover:border-accent-300'
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const focusClass = renderProps.isFocusVisible
|
|
162
|
-
? 'ring-2 ring-accent-300 ring-offset-2 ring-offset-bg-400'
|
|
163
|
-
: ''
|
|
164
|
-
const cursorClass = renderProps.isDisabled ? 'cursor-not-allowed' : 'cursor-pointer'
|
|
165
|
-
|
|
166
|
-
return [base, sizeClass, colorClass, focusClass, cursorClass].filter(Boolean).join(' ')
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const iconClasses = () => {
|
|
170
|
-
const base = 'text-white transition-opacity duration-200'
|
|
171
|
-
const sizeClass = size().icon
|
|
172
|
-
const visibilityClass = (renderProps.isSelected || renderProps.isIndeterminate)
|
|
173
|
-
? 'opacity-100'
|
|
174
|
-
: 'opacity-0'
|
|
175
|
-
|
|
176
|
-
return [base, sizeClass, visibilityClass].filter(Boolean).join(' ')
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const labelClasses = () => {
|
|
180
|
-
const base = 'text-primary-200'
|
|
181
|
-
const sizeClass = size().label
|
|
182
|
-
const disabledClass = renderProps.isDisabled ? 'opacity-50' : ''
|
|
183
|
-
|
|
184
|
-
return [base, sizeClass, disabledClass].filter(Boolean).join(' ')
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return (
|
|
188
|
-
<>
|
|
189
|
-
<span class={boxClasses()}>
|
|
190
|
-
<Show
|
|
191
|
-
when={!renderProps.isIndeterminate}
|
|
192
|
-
fallback={<IndeterminateIcon class={iconClasses()} />}
|
|
193
|
-
>
|
|
194
|
-
<CheckIcon class={iconClasses()} />
|
|
195
|
-
</Show>
|
|
196
|
-
</span>
|
|
197
|
-
<Show when={props.children}>
|
|
198
|
-
<span class={labelClasses()}>{props.children}</span>
|
|
199
|
-
</Show>
|
|
200
|
-
</>
|
|
201
|
-
)
|
|
202
|
-
}}
|
|
203
|
-
</HeadlessCheckbox>
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ============================================
|
|
208
|
-
// CHECKBOX GROUP COMPONENT
|
|
209
|
-
// ============================================
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* A checkbox group allows users to select multiple items from a list.
|
|
213
|
-
*
|
|
214
|
-
* Built on solidaria-components CheckboxGroup for full accessibility support.
|
|
215
|
-
*/
|
|
216
|
-
export function CheckboxGroup(props: CheckboxGroupProps): JSX.Element {
|
|
217
|
-
const [local, headlessProps] = splitProps(props, [
|
|
218
|
-
'class',
|
|
219
|
-
'label',
|
|
220
|
-
'description',
|
|
221
|
-
'errorMessage',
|
|
222
|
-
])
|
|
223
|
-
|
|
224
|
-
// Generate class based on render props
|
|
225
|
-
const getClassName = (renderProps: CheckboxGroupRenderProps): string => {
|
|
226
|
-
const base = 'flex flex-col gap-2'
|
|
227
|
-
const disabledClass = renderProps.isDisabled ? 'opacity-50' : ''
|
|
228
|
-
const custom = local.class || ''
|
|
229
|
-
return [base, disabledClass, custom].filter(Boolean).join(' ')
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Render children function for the headless component
|
|
233
|
-
const renderChildren = (renderProps: CheckboxGroupRenderProps) => (
|
|
234
|
-
<>
|
|
235
|
-
<Show when={local.label}>
|
|
236
|
-
<span class="text-sm font-medium text-primary-200">{local.label}</span>
|
|
237
|
-
</Show>
|
|
238
|
-
<div class="flex flex-col gap-2">
|
|
239
|
-
{props.children}
|
|
240
|
-
</div>
|
|
241
|
-
<Show when={local.description && !renderProps.isInvalid}>
|
|
242
|
-
<span class="text-sm text-primary-400">{local.description}</span>
|
|
243
|
-
</Show>
|
|
244
|
-
<Show when={local.errorMessage && renderProps.isInvalid}>
|
|
245
|
-
<span class="text-sm text-danger-400">{local.errorMessage}</span>
|
|
246
|
-
</Show>
|
|
247
|
-
</>
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
return (
|
|
251
|
-
<HeadlessCheckboxGroup
|
|
252
|
-
{...headlessProps}
|
|
253
|
-
class={getClassName}
|
|
254
|
-
children={renderChildren as any}
|
|
255
|
-
/>
|
|
256
|
-
)
|
|
257
|
-
}
|