@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
package/src/disclosure/index.tsx
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Disclosure and Accordion components for proyecto-viviana-ui
|
|
3
|
-
*
|
|
4
|
-
* Styled disclosure/accordion components built on top of solidaria-components.
|
|
5
|
-
* Inspired by Spectrum 2's disclosure patterns.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { type JSX, splitProps, createContext, useContext, Show } from 'solid-js';
|
|
9
|
-
import {
|
|
10
|
-
Disclosure as HeadlessDisclosure,
|
|
11
|
-
DisclosureGroup as HeadlessDisclosureGroup,
|
|
12
|
-
DisclosureTrigger as HeadlessDisclosureTrigger,
|
|
13
|
-
DisclosurePanel as HeadlessDisclosurePanel,
|
|
14
|
-
type DisclosureProps as HeadlessDisclosureProps,
|
|
15
|
-
type DisclosureGroupProps as HeadlessDisclosureGroupProps,
|
|
16
|
-
type DisclosureTriggerProps as HeadlessDisclosureTriggerProps,
|
|
17
|
-
type DisclosurePanelProps as HeadlessDisclosurePanelProps,
|
|
18
|
-
type DisclosureRenderProps,
|
|
19
|
-
type DisclosureGroupRenderProps,
|
|
20
|
-
} from '@proyecto-viviana/solidaria-components';
|
|
21
|
-
|
|
22
|
-
// ============================================
|
|
23
|
-
// SIZE AND VARIANT CONTEXT
|
|
24
|
-
// ============================================
|
|
25
|
-
|
|
26
|
-
export type DisclosureSize = 'sm' | 'md' | 'lg';
|
|
27
|
-
export type DisclosureVariant = 'default' | 'bordered' | 'filled' | 'ghost';
|
|
28
|
-
|
|
29
|
-
interface DisclosureContextValue {
|
|
30
|
-
size: DisclosureSize;
|
|
31
|
-
variant: DisclosureVariant;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const DisclosureSizeContext = createContext<DisclosureContextValue>({
|
|
35
|
-
size: 'md',
|
|
36
|
-
variant: 'default',
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// ============================================
|
|
40
|
-
// TYPES
|
|
41
|
-
// ============================================
|
|
42
|
-
|
|
43
|
-
export interface DisclosureGroupProps extends Omit<HeadlessDisclosureGroupProps, 'class' | 'style'> {
|
|
44
|
-
/** The size of all disclosures in the group. */
|
|
45
|
-
size?: DisclosureSize;
|
|
46
|
-
/** The visual variant of all disclosures in the group. */
|
|
47
|
-
variant?: DisclosureVariant;
|
|
48
|
-
/** Additional CSS class name. */
|
|
49
|
-
class?: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface DisclosureProps extends Omit<HeadlessDisclosureProps, 'class' | 'style'> {
|
|
53
|
-
/** The size of the disclosure. Overrides group size if set. */
|
|
54
|
-
size?: DisclosureSize;
|
|
55
|
-
/** The visual variant. Overrides group variant if set. */
|
|
56
|
-
variant?: DisclosureVariant;
|
|
57
|
-
/** Additional CSS class name. */
|
|
58
|
-
class?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface DisclosureTriggerProps extends Omit<HeadlessDisclosureTriggerProps, 'class' | 'style'> {
|
|
62
|
-
/** Additional CSS class name. */
|
|
63
|
-
class?: string;
|
|
64
|
-
/** Optional icon to show (defaults to chevron). */
|
|
65
|
-
hideIcon?: boolean;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface DisclosurePanelProps extends Omit<HeadlessDisclosurePanelProps, 'class' | 'style'> {
|
|
69
|
-
/** Additional CSS class name. */
|
|
70
|
-
class?: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ============================================
|
|
74
|
-
// STYLES
|
|
75
|
-
// ============================================
|
|
76
|
-
|
|
77
|
-
const sizeStyles = {
|
|
78
|
-
sm: {
|
|
79
|
-
trigger: 'px-3 py-2 text-sm',
|
|
80
|
-
panel: 'px-3 py-2 text-sm',
|
|
81
|
-
icon: 'w-4 h-4',
|
|
82
|
-
gap: 'gap-1',
|
|
83
|
-
},
|
|
84
|
-
md: {
|
|
85
|
-
trigger: 'px-4 py-3 text-base',
|
|
86
|
-
panel: 'px-4 py-3 text-base',
|
|
87
|
-
icon: 'w-5 h-5',
|
|
88
|
-
gap: 'gap-2',
|
|
89
|
-
},
|
|
90
|
-
lg: {
|
|
91
|
-
trigger: 'px-5 py-4 text-lg',
|
|
92
|
-
panel: 'px-5 py-4 text-lg',
|
|
93
|
-
icon: 'w-6 h-6',
|
|
94
|
-
gap: 'gap-3',
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const variantStyles = {
|
|
99
|
-
default: {
|
|
100
|
-
container: 'border-b border-primary-700',
|
|
101
|
-
trigger: {
|
|
102
|
-
base: 'w-full flex items-center justify-between text-left transition-colors duration-200',
|
|
103
|
-
default: 'text-primary-200 hover:text-primary-100 hover:bg-bg-400/50',
|
|
104
|
-
disabled: 'text-primary-500 cursor-not-allowed',
|
|
105
|
-
},
|
|
106
|
-
panel: 'text-primary-300',
|
|
107
|
-
},
|
|
108
|
-
bordered: {
|
|
109
|
-
container: 'border border-primary-600 rounded-lg mb-2 overflow-hidden',
|
|
110
|
-
trigger: {
|
|
111
|
-
base: 'w-full flex items-center justify-between text-left transition-colors duration-200',
|
|
112
|
-
default: 'text-primary-200 hover:bg-bg-400/50',
|
|
113
|
-
disabled: 'text-primary-500 cursor-not-allowed',
|
|
114
|
-
},
|
|
115
|
-
panel: 'text-primary-300 border-t border-primary-600',
|
|
116
|
-
},
|
|
117
|
-
filled: {
|
|
118
|
-
container: 'bg-bg-400 rounded-lg mb-2 overflow-hidden',
|
|
119
|
-
trigger: {
|
|
120
|
-
base: 'w-full flex items-center justify-between text-left transition-colors duration-200',
|
|
121
|
-
default: 'text-primary-200 hover:bg-bg-300',
|
|
122
|
-
disabled: 'text-primary-500 cursor-not-allowed',
|
|
123
|
-
},
|
|
124
|
-
panel: 'text-primary-300 bg-bg-300/50',
|
|
125
|
-
},
|
|
126
|
-
ghost: {
|
|
127
|
-
container: '',
|
|
128
|
-
trigger: {
|
|
129
|
-
base: 'w-full flex items-center justify-between text-left transition-colors duration-200 rounded-md',
|
|
130
|
-
default: 'text-primary-200 hover:bg-bg-400/50',
|
|
131
|
-
disabled: 'text-primary-500 cursor-not-allowed',
|
|
132
|
-
},
|
|
133
|
-
panel: 'text-primary-300',
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// ============================================
|
|
138
|
-
// DISCLOSURE GROUP COMPONENT
|
|
139
|
-
// ============================================
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* DisclosureGroup manages a group of Disclosure components.
|
|
143
|
-
* Use this to create an accordion where only one item can be expanded at a time.
|
|
144
|
-
*
|
|
145
|
-
* @example
|
|
146
|
-
* ```tsx
|
|
147
|
-
* <DisclosureGroup>
|
|
148
|
-
* <Disclosure id="item1">
|
|
149
|
-
* <DisclosureTrigger>Section 1</DisclosureTrigger>
|
|
150
|
-
* <DisclosurePanel>Content 1</DisclosurePanel>
|
|
151
|
-
* </Disclosure>
|
|
152
|
-
* <Disclosure id="item2">
|
|
153
|
-
* <DisclosureTrigger>Section 2</DisclosureTrigger>
|
|
154
|
-
* <DisclosurePanel>Content 2</DisclosurePanel>
|
|
155
|
-
* </Disclosure>
|
|
156
|
-
* </DisclosureGroup>
|
|
157
|
-
* ```
|
|
158
|
-
*/
|
|
159
|
-
export function DisclosureGroup(props: DisclosureGroupProps): JSX.Element {
|
|
160
|
-
const [local, headlessProps] = splitProps(props, [
|
|
161
|
-
'size',
|
|
162
|
-
'variant',
|
|
163
|
-
'class',
|
|
164
|
-
]);
|
|
165
|
-
|
|
166
|
-
const size = local.size ?? 'md';
|
|
167
|
-
const variant = local.variant ?? 'default';
|
|
168
|
-
const customClass = local.class ?? '';
|
|
169
|
-
|
|
170
|
-
const getClassName = (_renderProps: DisclosureGroupRenderProps): string => {
|
|
171
|
-
const base = 'flex flex-col';
|
|
172
|
-
const gapClass = sizeStyles[size].gap;
|
|
173
|
-
return [base, gapClass, customClass].filter(Boolean).join(' ');
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
<DisclosureSizeContext.Provider value={{ size, variant }}>
|
|
178
|
-
<HeadlessDisclosureGroup
|
|
179
|
-
{...headlessProps}
|
|
180
|
-
class={getClassName}
|
|
181
|
-
children={props.children}
|
|
182
|
-
/>
|
|
183
|
-
</DisclosureSizeContext.Provider>
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ============================================
|
|
188
|
-
// DISCLOSURE COMPONENT
|
|
189
|
-
// ============================================
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Disclosure is a widget that can be toggled to show or hide content.
|
|
193
|
-
*
|
|
194
|
-
* @example
|
|
195
|
-
* ```tsx
|
|
196
|
-
* <Disclosure>
|
|
197
|
-
* <DisclosureTrigger>Show more</DisclosureTrigger>
|
|
198
|
-
* <DisclosurePanel>Hidden content here...</DisclosurePanel>
|
|
199
|
-
* </Disclosure>
|
|
200
|
-
* ```
|
|
201
|
-
*/
|
|
202
|
-
export function Disclosure(props: DisclosureProps): JSX.Element {
|
|
203
|
-
const [local, headlessProps] = splitProps(props, [
|
|
204
|
-
'size',
|
|
205
|
-
'variant',
|
|
206
|
-
'class',
|
|
207
|
-
]);
|
|
208
|
-
|
|
209
|
-
const parentCtx = useContext(DisclosureSizeContext);
|
|
210
|
-
const size = local.size ?? parentCtx.size;
|
|
211
|
-
const variant = local.variant ?? parentCtx.variant;
|
|
212
|
-
const customClass = local.class ?? '';
|
|
213
|
-
|
|
214
|
-
const getClassName = (_renderProps: DisclosureRenderProps): string => {
|
|
215
|
-
const variantClass = variantStyles[variant].container;
|
|
216
|
-
return [variantClass, customClass].filter(Boolean).join(' ');
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
return (
|
|
220
|
-
<DisclosureSizeContext.Provider value={{ size, variant }}>
|
|
221
|
-
<HeadlessDisclosure
|
|
222
|
-
{...headlessProps}
|
|
223
|
-
class={getClassName}
|
|
224
|
-
>
|
|
225
|
-
{props.children}
|
|
226
|
-
</HeadlessDisclosure>
|
|
227
|
-
</DisclosureSizeContext.Provider>
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// ============================================
|
|
232
|
-
// DISCLOSURE TRIGGER COMPONENT
|
|
233
|
-
// ============================================
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* DisclosureTrigger is the button that toggles the disclosure.
|
|
237
|
-
* The chevron rotates based on the data-expanded attribute using Tailwind's group class.
|
|
238
|
-
*/
|
|
239
|
-
export function DisclosureTrigger(props: DisclosureTriggerProps): JSX.Element {
|
|
240
|
-
const [local, headlessProps] = splitProps(props, ['class', 'hideIcon']);
|
|
241
|
-
const ctx = useContext(DisclosureSizeContext);
|
|
242
|
-
const customClass = local.class ?? '';
|
|
243
|
-
|
|
244
|
-
return (
|
|
245
|
-
<HeadlessDisclosureTrigger
|
|
246
|
-
{...headlessProps}
|
|
247
|
-
class={[
|
|
248
|
-
'group', // Enable Tailwind group selector for chevron rotation
|
|
249
|
-
variantStyles[ctx.variant].trigger.base,
|
|
250
|
-
sizeStyles[ctx.size].trigger,
|
|
251
|
-
customClass,
|
|
252
|
-
].filter(Boolean).join(' ')}
|
|
253
|
-
>
|
|
254
|
-
{props.children}
|
|
255
|
-
<Show when={!local.hideIcon}>
|
|
256
|
-
<svg
|
|
257
|
-
class={[
|
|
258
|
-
sizeStyles[ctx.size].icon,
|
|
259
|
-
'transition-transform duration-200',
|
|
260
|
-
'group-data-[expanded=true]:rotate-180', // Rotate when expanded
|
|
261
|
-
].filter(Boolean).join(' ')}
|
|
262
|
-
viewBox="0 0 24 24"
|
|
263
|
-
fill="none"
|
|
264
|
-
stroke="currentColor"
|
|
265
|
-
stroke-width="2"
|
|
266
|
-
stroke-linecap="round"
|
|
267
|
-
stroke-linejoin="round"
|
|
268
|
-
style={{ "flex-shrink": 0 }}
|
|
269
|
-
>
|
|
270
|
-
<polyline points="6 9 12 15 18 9" />
|
|
271
|
-
</svg>
|
|
272
|
-
</Show>
|
|
273
|
-
</HeadlessDisclosureTrigger>
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ============================================
|
|
278
|
-
// DISCLOSURE PANEL COMPONENT
|
|
279
|
-
// ============================================
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* DisclosurePanel contains the content that is shown/hidden.
|
|
283
|
-
*/
|
|
284
|
-
export function DisclosurePanel(props: DisclosurePanelProps): JSX.Element {
|
|
285
|
-
const [local, headlessProps] = splitProps(props, ['class']);
|
|
286
|
-
const ctx = useContext(DisclosureSizeContext);
|
|
287
|
-
const customClass = local.class ?? '';
|
|
288
|
-
|
|
289
|
-
const getClassName = (_renderProps: DisclosureRenderProps): string => {
|
|
290
|
-
const base = variantStyles[ctx.variant].panel;
|
|
291
|
-
const sizeClass = sizeStyles[ctx.size].panel;
|
|
292
|
-
return [base, sizeClass, customClass].filter(Boolean).join(' ');
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
return (
|
|
296
|
-
<HeadlessDisclosurePanel
|
|
297
|
-
{...headlessProps}
|
|
298
|
-
class={getClassName}
|
|
299
|
-
children={props.children}
|
|
300
|
-
/>
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Attach sub-components for convenience
|
|
305
|
-
Disclosure.Trigger = DisclosureTrigger;
|
|
306
|
-
Disclosure.Panel = DisclosurePanel;
|
|
307
|
-
DisclosureGroup.Item = Disclosure;
|
package/src/gridlist/index.tsx
DELETED
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GridList component for proyecto-viviana-ui
|
|
3
|
-
*
|
|
4
|
-
* Styled grid list component built on top of solidaria-components.
|
|
5
|
-
* Inspired by Spectrum 2's GridList component patterns.
|
|
6
|
-
*
|
|
7
|
-
* GridList is similar to ListBox but supports interactive elements within items
|
|
8
|
-
* and uses grid keyboard navigation.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { type JSX, splitProps, createContext, useContext, Show } from 'solid-js'
|
|
12
|
-
import {
|
|
13
|
-
GridList as HeadlessGridList,
|
|
14
|
-
GridListItem as HeadlessGridListItem,
|
|
15
|
-
GridListSelectionCheckbox as HeadlessGridListSelectionCheckbox,
|
|
16
|
-
type GridListProps as HeadlessGridListProps,
|
|
17
|
-
type GridListItemProps as HeadlessGridListItemProps,
|
|
18
|
-
type GridListRenderProps,
|
|
19
|
-
type GridListItemRenderProps,
|
|
20
|
-
} from '@proyecto-viviana/solidaria-components'
|
|
21
|
-
import type { Key } from '@proyecto-viviana/solid-stately'
|
|
22
|
-
|
|
23
|
-
// ============================================
|
|
24
|
-
// SIZE CONTEXT
|
|
25
|
-
// ============================================
|
|
26
|
-
|
|
27
|
-
export type GridListSize = 'sm' | 'md' | 'lg'
|
|
28
|
-
export type GridListVariant = 'default' | 'cards' | 'bordered'
|
|
29
|
-
export type GridListLayout = 'list' | 'grid'
|
|
30
|
-
|
|
31
|
-
interface GridListContextValue {
|
|
32
|
-
size: GridListSize
|
|
33
|
-
variant: GridListVariant
|
|
34
|
-
layout: GridListLayout
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const GridListSizeContext = createContext<GridListContextValue>({
|
|
38
|
-
size: 'md',
|
|
39
|
-
variant: 'default',
|
|
40
|
-
layout: 'list',
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
// ============================================
|
|
44
|
-
// TYPES
|
|
45
|
-
// ============================================
|
|
46
|
-
|
|
47
|
-
export interface GridListProps<T extends object>
|
|
48
|
-
extends Omit<HeadlessGridListProps<T>, 'class' | 'style'> {
|
|
49
|
-
/** The size of the grid list. */
|
|
50
|
-
size?: GridListSize
|
|
51
|
-
/** The visual variant of the grid list. */
|
|
52
|
-
variant?: GridListVariant
|
|
53
|
-
/** The layout of the grid list. */
|
|
54
|
-
layout?: GridListLayout
|
|
55
|
-
/** Number of columns for grid layout (default: auto-fit). */
|
|
56
|
-
columns?: number | 'auto'
|
|
57
|
-
/** Additional CSS class name. */
|
|
58
|
-
class?: string
|
|
59
|
-
/** Label for the grid list. */
|
|
60
|
-
label?: string
|
|
61
|
-
/** Description for the grid list. */
|
|
62
|
-
description?: string
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface GridListItemProps<T extends object>
|
|
66
|
-
extends Omit<HeadlessGridListItemProps<T>, 'class' | 'style'> {
|
|
67
|
-
/** Additional CSS class name. */
|
|
68
|
-
class?: string
|
|
69
|
-
/** Optional description text. */
|
|
70
|
-
description?: string
|
|
71
|
-
/**
|
|
72
|
-
* Optional icon to display before the content.
|
|
73
|
-
* Use a function returning JSX for SSR compatibility: `icon={() => <MyIcon />}`
|
|
74
|
-
*/
|
|
75
|
-
icon?: () => JSX.Element
|
|
76
|
-
/**
|
|
77
|
-
* Optional image to display in the item.
|
|
78
|
-
*/
|
|
79
|
-
image?: string
|
|
80
|
-
/** Alt text for the image. */
|
|
81
|
-
imageAlt?: string
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ============================================
|
|
85
|
-
// STYLES
|
|
86
|
-
// ============================================
|
|
87
|
-
|
|
88
|
-
const sizeStyles = {
|
|
89
|
-
sm: {
|
|
90
|
-
list: 'gap-1 p-1',
|
|
91
|
-
item: 'text-sm py-2 px-3 gap-2',
|
|
92
|
-
icon: 'h-4 w-4',
|
|
93
|
-
image: 'h-10 w-10',
|
|
94
|
-
label: 'text-sm',
|
|
95
|
-
description: 'text-xs',
|
|
96
|
-
checkbox: 'w-4 h-4',
|
|
97
|
-
},
|
|
98
|
-
md: {
|
|
99
|
-
list: 'gap-2 p-2',
|
|
100
|
-
item: 'text-base py-3 px-4 gap-3',
|
|
101
|
-
icon: 'h-5 w-5',
|
|
102
|
-
image: 'h-12 w-12',
|
|
103
|
-
label: 'text-base',
|
|
104
|
-
description: 'text-sm',
|
|
105
|
-
checkbox: 'w-5 h-5',
|
|
106
|
-
},
|
|
107
|
-
lg: {
|
|
108
|
-
list: 'gap-3 p-3',
|
|
109
|
-
item: 'text-lg py-4 px-5 gap-4',
|
|
110
|
-
icon: 'h-6 w-6',
|
|
111
|
-
image: 'h-16 w-16',
|
|
112
|
-
label: 'text-lg',
|
|
113
|
-
description: 'text-base',
|
|
114
|
-
checkbox: 'w-6 h-6',
|
|
115
|
-
},
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const variantStyles = {
|
|
119
|
-
default: {
|
|
120
|
-
list: 'bg-bg-400 rounded-lg border border-bg-300',
|
|
121
|
-
item: 'rounded-md',
|
|
122
|
-
itemHover: 'hover:bg-bg-200/50',
|
|
123
|
-
itemSelected: 'bg-accent/10 text-accent',
|
|
124
|
-
},
|
|
125
|
-
cards: {
|
|
126
|
-
list: 'bg-transparent',
|
|
127
|
-
item: 'bg-bg-400 rounded-lg border border-bg-300 shadow-sm',
|
|
128
|
-
itemHover: 'hover:shadow-md hover:border-bg-200',
|
|
129
|
-
itemSelected: 'border-accent bg-accent/5 shadow-accent/20',
|
|
130
|
-
},
|
|
131
|
-
bordered: {
|
|
132
|
-
list: 'bg-bg-400 rounded-lg border-2 border-bg-400',
|
|
133
|
-
item: 'border-b border-bg-300 last:border-b-0 rounded-none',
|
|
134
|
-
itemHover: 'hover:bg-bg-200/50',
|
|
135
|
-
itemSelected: 'bg-accent/10',
|
|
136
|
-
},
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ============================================
|
|
140
|
-
// GRID LIST COMPONENT
|
|
141
|
-
// ============================================
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* A grid list displays a list of interactive items, with support for
|
|
145
|
-
* keyboard navigation, single or multiple selection, and row actions.
|
|
146
|
-
*
|
|
147
|
-
* Built on solidaria-components GridList for full accessibility support.
|
|
148
|
-
*
|
|
149
|
-
* @example
|
|
150
|
-
* ```tsx
|
|
151
|
-
* const items = [
|
|
152
|
-
* { id: '1', name: 'Item 1', description: 'Description 1' },
|
|
153
|
-
* { id: '2', name: 'Item 2', description: 'Description 2' },
|
|
154
|
-
* ]
|
|
155
|
-
*
|
|
156
|
-
* <GridList
|
|
157
|
-
* items={items}
|
|
158
|
-
* getKey={(item) => item.id}
|
|
159
|
-
* selectionMode="multiple"
|
|
160
|
-
* >
|
|
161
|
-
* {(item) => (
|
|
162
|
-
* <GridListItem id={item.id} description={item.description}>
|
|
163
|
-
* {item.name}
|
|
164
|
-
* </GridListItem>
|
|
165
|
-
* )}
|
|
166
|
-
* </GridList>
|
|
167
|
-
* ```
|
|
168
|
-
*/
|
|
169
|
-
export function GridList<T extends object>(props: GridListProps<T>): JSX.Element {
|
|
170
|
-
const [local, headlessProps] = splitProps(props, [
|
|
171
|
-
'size',
|
|
172
|
-
'variant',
|
|
173
|
-
'layout',
|
|
174
|
-
'columns',
|
|
175
|
-
'class',
|
|
176
|
-
'label',
|
|
177
|
-
'description',
|
|
178
|
-
])
|
|
179
|
-
|
|
180
|
-
const size = () => local.size ?? 'md'
|
|
181
|
-
const variant = () => local.variant ?? 'default'
|
|
182
|
-
const layout = () => local.layout ?? 'list'
|
|
183
|
-
const styles = () => sizeStyles[size()]
|
|
184
|
-
const variantStyle = () => variantStyles[variant()]
|
|
185
|
-
const customClass = local.class ?? ''
|
|
186
|
-
|
|
187
|
-
const getClassName = (renderProps: GridListRenderProps): string => {
|
|
188
|
-
const base = 'overflow-auto focus:outline-none'
|
|
189
|
-
const sizeClass = styles().list
|
|
190
|
-
const variantClass = variantStyle().list
|
|
191
|
-
|
|
192
|
-
// Layout classes
|
|
193
|
-
let layoutClass = ''
|
|
194
|
-
if (layout() === 'grid') {
|
|
195
|
-
if (local.columns === 'auto' || local.columns === undefined) {
|
|
196
|
-
layoutClass = 'grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))]'
|
|
197
|
-
} else {
|
|
198
|
-
layoutClass = `grid grid-cols-${local.columns}`
|
|
199
|
-
}
|
|
200
|
-
} else {
|
|
201
|
-
layoutClass = 'flex flex-col'
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let stateClass = ''
|
|
205
|
-
if (renderProps.isDisabled) {
|
|
206
|
-
stateClass = 'opacity-50'
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const focusClass = renderProps.isFocusVisible
|
|
210
|
-
? 'ring-2 ring-accent-300 ring-offset-2 ring-offset-bg-400'
|
|
211
|
-
: ''
|
|
212
|
-
|
|
213
|
-
return [base, sizeClass, variantClass, layoutClass, stateClass, focusClass, customClass]
|
|
214
|
-
.filter(Boolean)
|
|
215
|
-
.join(' ')
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const defaultEmptyState = () => (
|
|
219
|
-
<li class="py-8 text-center text-primary-400">
|
|
220
|
-
<div class="flex flex-col items-center gap-2">
|
|
221
|
-
<EmptyIcon class="w-12 h-12 text-primary-500" />
|
|
222
|
-
<span>No items</span>
|
|
223
|
-
</div>
|
|
224
|
-
</li>
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
const contextValue = () => ({ size: size(), variant: variant(), layout: layout() })
|
|
228
|
-
|
|
229
|
-
return (
|
|
230
|
-
<GridListSizeContext.Provider value={contextValue()}>
|
|
231
|
-
<div class="flex flex-col gap-2">
|
|
232
|
-
<Show when={local.label}>
|
|
233
|
-
<label class={`text-primary-200 font-medium ${styles().label}`}>
|
|
234
|
-
{local.label}
|
|
235
|
-
</label>
|
|
236
|
-
</Show>
|
|
237
|
-
<HeadlessGridList
|
|
238
|
-
{...headlessProps}
|
|
239
|
-
class={getClassName}
|
|
240
|
-
renderEmptyState={headlessProps.renderEmptyState ?? defaultEmptyState}
|
|
241
|
-
/>
|
|
242
|
-
<Show when={local.description}>
|
|
243
|
-
<span class="text-primary-400 text-sm">{local.description}</span>
|
|
244
|
-
</Show>
|
|
245
|
-
</div>
|
|
246
|
-
</GridListSizeContext.Provider>
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// ============================================
|
|
251
|
-
// GRID LIST ITEM COMPONENT
|
|
252
|
-
// ============================================
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* An item in a grid list.
|
|
256
|
-
*/
|
|
257
|
-
export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX.Element {
|
|
258
|
-
const [local, headlessProps] = splitProps(props, [
|
|
259
|
-
'class',
|
|
260
|
-
'description',
|
|
261
|
-
'icon',
|
|
262
|
-
'image',
|
|
263
|
-
'imageAlt',
|
|
264
|
-
])
|
|
265
|
-
|
|
266
|
-
const context = useContext(GridListSizeContext)
|
|
267
|
-
const sizeStyle = sizeStyles[context.size]
|
|
268
|
-
const variantStyle = variantStyles[context.variant]
|
|
269
|
-
const customClass = local.class ?? ''
|
|
270
|
-
|
|
271
|
-
const getClassName = (renderProps: GridListItemRenderProps): string => {
|
|
272
|
-
const base = 'flex items-center cursor-pointer transition-all duration-150 outline-none'
|
|
273
|
-
const sizeClass = sizeStyle.item
|
|
274
|
-
const variantClass = variantStyle.item
|
|
275
|
-
|
|
276
|
-
let stateClass = ''
|
|
277
|
-
if (renderProps.isDisabled) {
|
|
278
|
-
stateClass = 'opacity-50 cursor-not-allowed'
|
|
279
|
-
} else if (renderProps.isSelected) {
|
|
280
|
-
stateClass = variantStyle.itemSelected
|
|
281
|
-
} else if (renderProps.isHovered) {
|
|
282
|
-
stateClass = variantStyle.itemHover
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
let textClass = ''
|
|
286
|
-
if (!renderProps.isDisabled && !renderProps.isSelected) {
|
|
287
|
-
textClass = 'text-primary-200'
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const focusClass = renderProps.isFocusVisible
|
|
291
|
-
? 'ring-2 ring-inset ring-accent-300'
|
|
292
|
-
: ''
|
|
293
|
-
|
|
294
|
-
const pressedClass = renderProps.isPressed ? 'scale-[0.98]' : ''
|
|
295
|
-
|
|
296
|
-
return [base, sizeClass, variantClass, stateClass, textClass, focusClass, pressedClass, customClass]
|
|
297
|
-
.filter(Boolean)
|
|
298
|
-
.join(' ')
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return (
|
|
302
|
-
<HeadlessGridListItem {...headlessProps} class={getClassName}>
|
|
303
|
-
{(renderProps: GridListItemRenderProps) => (
|
|
304
|
-
<>
|
|
305
|
-
{/* Image */}
|
|
306
|
-
<Show when={local.image}>
|
|
307
|
-
<img
|
|
308
|
-
src={local.image}
|
|
309
|
-
alt={local.imageAlt ?? ''}
|
|
310
|
-
class={`${sizeStyle.image} rounded object-cover shrink-0`}
|
|
311
|
-
/>
|
|
312
|
-
</Show>
|
|
313
|
-
|
|
314
|
-
{/* Icon */}
|
|
315
|
-
<Show when={local.icon}>
|
|
316
|
-
<span class={`shrink-0 ${sizeStyle.icon}`}>{local.icon!()}</span>
|
|
317
|
-
</Show>
|
|
318
|
-
|
|
319
|
-
{/* Selection indicator */}
|
|
320
|
-
<Show when={renderProps.isSelected}>
|
|
321
|
-
<CheckIcon class={`shrink-0 ${sizeStyle.icon} text-accent`} />
|
|
322
|
-
</Show>
|
|
323
|
-
|
|
324
|
-
{/* Content */}
|
|
325
|
-
<div class="flex flex-col flex-1 min-w-0">
|
|
326
|
-
<span class="truncate">
|
|
327
|
-
{typeof props.children === 'function'
|
|
328
|
-
? props.children(renderProps)
|
|
329
|
-
: props.children}
|
|
330
|
-
</span>
|
|
331
|
-
<Show when={local.description}>
|
|
332
|
-
<span class={`text-primary-400 truncate ${sizeStyle.description}`}>
|
|
333
|
-
{local.description}
|
|
334
|
-
</span>
|
|
335
|
-
</Show>
|
|
336
|
-
</div>
|
|
337
|
-
</>
|
|
338
|
-
)}
|
|
339
|
-
</HeadlessGridListItem>
|
|
340
|
-
)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// ============================================
|
|
344
|
-
// GRID LIST SELECTION CHECKBOX COMPONENT
|
|
345
|
-
// ============================================
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* A styled checkbox for item selection in a grid list.
|
|
349
|
-
*/
|
|
350
|
-
export function GridListSelectionCheckbox(props: { itemKey: Key; class?: string }): JSX.Element {
|
|
351
|
-
const context = useContext(GridListSizeContext)
|
|
352
|
-
const sizeStyle = sizeStyles[context.size]
|
|
353
|
-
const className = `${sizeStyle.checkbox} rounded border-2 border-primary-500 bg-bg-400 text-accent cursor-pointer checked:bg-accent checked:border-accent focus:ring-2 focus:ring-accent-300 focus:ring-offset-1 focus:ring-offset-bg-400 ${props.class ?? ''}`
|
|
354
|
-
|
|
355
|
-
return (
|
|
356
|
-
<span class={className}>
|
|
357
|
-
<HeadlessGridListSelectionCheckbox itemKey={props.itemKey} />
|
|
358
|
-
</span>
|
|
359
|
-
)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// ============================================
|
|
363
|
-
// ICONS
|
|
364
|
-
// ============================================
|
|
365
|
-
|
|
366
|
-
function CheckIcon(props: { class?: string }): JSX.Element {
|
|
367
|
-
return (
|
|
368
|
-
<svg
|
|
369
|
-
class={props.class}
|
|
370
|
-
fill="none"
|
|
371
|
-
viewBox="0 0 24 24"
|
|
372
|
-
stroke="currentColor"
|
|
373
|
-
stroke-width="2"
|
|
374
|
-
>
|
|
375
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
|
376
|
-
</svg>
|
|
377
|
-
)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function EmptyIcon(props: { class?: string }): JSX.Element {
|
|
381
|
-
return (
|
|
382
|
-
<svg
|
|
383
|
-
class={props.class}
|
|
384
|
-
fill="none"
|
|
385
|
-
viewBox="0 0 24 24"
|
|
386
|
-
stroke="currentColor"
|
|
387
|
-
stroke-width="1.5"
|
|
388
|
-
>
|
|
389
|
-
<path
|
|
390
|
-
stroke-linecap="round"
|
|
391
|
-
stroke-linejoin="round"
|
|
392
|
-
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
|
393
|
-
/>
|
|
394
|
-
</svg>
|
|
395
|
-
)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Attach sub-components for convenience
|
|
399
|
-
GridList.Item = GridListItem
|
|
400
|
-
GridList.SelectionCheckbox = GridListSelectionCheckbox
|
|
401
|
-
|
|
402
|
-
// Re-export Key type for convenience
|
|
403
|
-
export type { Key }
|