@proyecto-viviana/ui 0.1.0 → 0.1.2
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 +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/src/alert/index.tsx +48 -0
- package/src/assets/favicon.png +0 -0
- package/src/assets/fire.gif +0 -0
- package/src/avatar/index.tsx +75 -0
- package/src/badge/index.tsx +43 -0
- package/src/button/Button.tsx +75 -0
- package/src/button/index.ts +2 -0
- package/src/button/types.ts +24 -0
- package/src/checkbox/index.tsx +258 -0
- package/src/components.css +975 -0
- package/src/custom/calendar-card/index.tsx +66 -0
- package/src/custom/chip/index.tsx +32 -0
- package/src/custom/conversation/index.tsx +105 -0
- package/src/custom/event-card/index.tsx +126 -0
- package/src/custom/lateral-nav/index.tsx +88 -0
- package/src/custom/logo/index.tsx +54 -0
- package/src/custom/nav-header/index.tsx +42 -0
- package/src/custom/page-layout/index.tsx +20 -0
- package/src/custom/profile-card/index.tsx +58 -0
- package/src/custom/project-card/index.tsx +59 -0
- package/src/custom/timeline-item/index.tsx +81 -0
- package/src/dialog/index.tsx +69 -0
- package/src/icon/icons/GitHubIcon.tsx +20 -0
- package/src/icon/index.tsx +48 -0
- package/src/index.ts +107 -0
- package/src/link/index.tsx +131 -0
- package/src/progress-bar/index.tsx +169 -0
- package/src/radio/index.tsx +219 -0
- package/src/separator/index.tsx +141 -0
- package/src/styles.css +450 -0
- package/src/switch/ToggleSwitch.tsx +113 -0
- package/src/switch/index.tsx +90 -0
- package/src/test-utils/index.ts +32 -0
- package/src/textfield/index.tsx +211 -0
- package/src/theme.css +101 -0
- package/src/tooltip/index.tsx +31 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proyecto-viviana/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Styled UI components for SolidJS - inspired by React Spectrum",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"solid": "./
|
|
11
|
+
"solid": "./src/index.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"types": "./dist/index.d.ts"
|
|
14
14
|
},
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"./components.css": "./dist/components.css"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"src"
|
|
21
22
|
],
|
|
22
23
|
"sideEffects": [
|
|
23
24
|
"*.css"
|
|
@@ -28,8 +29,8 @@
|
|
|
28
29
|
"prepublishOnly": "bun run build"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@proyecto-viviana/solidaria": "^0.0.
|
|
32
|
-
"@proyecto-viviana/solidaria-components": "^0.0.
|
|
32
|
+
"@proyecto-viviana/solidaria": "^0.0.2",
|
|
33
|
+
"@proyecto-viviana/solidaria-components": "^0.0.2"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"solid-js": "^1.9.0"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js'
|
|
2
|
+
import { Show } from 'solid-js'
|
|
3
|
+
|
|
4
|
+
export type AlertVariant = 'info' | 'success' | 'warning' | 'error'
|
|
5
|
+
|
|
6
|
+
export interface AlertProps {
|
|
7
|
+
children: JSX.Element
|
|
8
|
+
variant?: AlertVariant
|
|
9
|
+
title?: string
|
|
10
|
+
dismissible?: boolean
|
|
11
|
+
onDismiss?: () => void
|
|
12
|
+
class?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const variantStyles: Record<AlertVariant, string> = {
|
|
16
|
+
info: 'bg-primary-700 text-primary-200 border border-primary-500',
|
|
17
|
+
success: 'bg-success-600 text-success-100 border border-success-400',
|
|
18
|
+
warning: 'bg-warning-600 text-warning-100 border border-warning-400',
|
|
19
|
+
error: 'bg-danger-600 text-danger-100 border border-danger-400',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Alert(props: AlertProps) {
|
|
23
|
+
const variant = () => props.variant ?? 'info'
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
class={`flex items-center min-h-[50px] font-normal rounded-lg px-4 py-2 ${variantStyles[variant()]} ${props.class ?? ''}`}
|
|
28
|
+
role="alert"
|
|
29
|
+
>
|
|
30
|
+
<div class="flex items-center gap-3 flex-1">
|
|
31
|
+
<Show when={props.title}>
|
|
32
|
+
<span class="font-semibold font-jost">{props.title}</span>
|
|
33
|
+
<span class="opacity-50">|</span>
|
|
34
|
+
</Show>
|
|
35
|
+
<div class="flex-1">{props.children}</div>
|
|
36
|
+
<Show when={props.dismissible}>
|
|
37
|
+
<button
|
|
38
|
+
class="hover:opacity-70 transition-opacity ml-2"
|
|
39
|
+
onClick={props.onDismiss}
|
|
40
|
+
aria-label="Dismiss"
|
|
41
|
+
>
|
|
42
|
+
✕
|
|
43
|
+
</button>
|
|
44
|
+
</Show>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Show } from 'solid-js'
|
|
2
|
+
|
|
3
|
+
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
4
|
+
|
|
5
|
+
export interface AvatarProps {
|
|
6
|
+
src?: string
|
|
7
|
+
alt?: string
|
|
8
|
+
size?: AvatarSize
|
|
9
|
+
fallback?: string
|
|
10
|
+
online?: boolean
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const sizeStyles: Record<AvatarSize, { container: string; text: string; indicator: string }> = {
|
|
15
|
+
xs: { container: 'w-6 h-6', text: 'text-xs', indicator: 'w-1.5 h-1.5' },
|
|
16
|
+
sm: { container: 'w-8 h-8', text: 'text-sm', indicator: 'w-2 h-2' },
|
|
17
|
+
md: { container: 'w-10 h-10', text: 'text-base', indicator: 'w-2.5 h-2.5' },
|
|
18
|
+
lg: { container: 'w-14 h-14', text: 'text-lg', indicator: 'w-3 h-3' },
|
|
19
|
+
xl: { container: 'w-20 h-20', text: 'text-xl', indicator: 'w-4 h-4' },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Avatar(props: AvatarProps) {
|
|
23
|
+
const size = () => props.size ?? 'md'
|
|
24
|
+
const styles = () => sizeStyles[size()]
|
|
25
|
+
|
|
26
|
+
const initials = () => {
|
|
27
|
+
if (props.fallback) return props.fallback.slice(0, 2).toUpperCase()
|
|
28
|
+
if (props.alt) return props.alt.slice(0, 2).toUpperCase()
|
|
29
|
+
return '?'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div class={`relative inline-block ${props.class ?? ''}`}>
|
|
34
|
+
<div
|
|
35
|
+
class={`${styles().container} rounded-full overflow-hidden bg-bg-200 flex items-center justify-center ring-2 ring-accent/50`}
|
|
36
|
+
>
|
|
37
|
+
<Show
|
|
38
|
+
when={props.src}
|
|
39
|
+
fallback={
|
|
40
|
+
<span class={`${styles().text} font-medium text-primary-300`}>
|
|
41
|
+
{initials()}
|
|
42
|
+
</span>
|
|
43
|
+
}
|
|
44
|
+
>
|
|
45
|
+
<img
|
|
46
|
+
src={props.src}
|
|
47
|
+
alt={props.alt ?? 'Avatar'}
|
|
48
|
+
class="w-full h-full object-cover"
|
|
49
|
+
/>
|
|
50
|
+
</Show>
|
|
51
|
+
</div>
|
|
52
|
+
<Show when={props.online !== undefined}>
|
|
53
|
+
<span
|
|
54
|
+
class={`absolute bottom-0 right-0 ${styles().indicator} rounded-full ring-2 ring-bg-400 ${
|
|
55
|
+
props.online ? 'bg-success-400' : 'bg-bg-light'
|
|
56
|
+
}`}
|
|
57
|
+
/>
|
|
58
|
+
</Show>
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AvatarGroupProps {
|
|
64
|
+
children: any
|
|
65
|
+
max?: number
|
|
66
|
+
size?: AvatarSize
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function AvatarGroup(props: AvatarGroupProps) {
|
|
70
|
+
return (
|
|
71
|
+
<div class="flex -space-x-2">
|
|
72
|
+
{props.children}
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js'
|
|
2
|
+
import { Show } from 'solid-js'
|
|
3
|
+
|
|
4
|
+
export type BadgeVariant = 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'danger'
|
|
5
|
+
export type BadgeSize = 'sm' | 'md' | 'lg'
|
|
6
|
+
|
|
7
|
+
export interface BadgeProps {
|
|
8
|
+
children?: JSX.Element
|
|
9
|
+
count?: number
|
|
10
|
+
variant?: BadgeVariant
|
|
11
|
+
size?: BadgeSize
|
|
12
|
+
class?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const variantStyles: Record<BadgeVariant, string> = {
|
|
16
|
+
primary: 'bg-primary-500 text-white',
|
|
17
|
+
secondary: 'bg-bg-300 text-primary-300',
|
|
18
|
+
accent: 'bg-accent-300 text-black',
|
|
19
|
+
success: 'bg-success-400 text-white',
|
|
20
|
+
warning: 'bg-warning-400 text-black',
|
|
21
|
+
danger: 'bg-danger-400 text-white',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sizeStyles: Record<BadgeSize, string> = {
|
|
25
|
+
sm: 'w-5 h-5 text-xs',
|
|
26
|
+
md: 'w-7 h-7 text-xs',
|
|
27
|
+
lg: 'w-9 h-9 text-sm',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function Badge(props: BadgeProps) {
|
|
31
|
+
const variant = () => props.variant ?? 'accent'
|
|
32
|
+
const size = () => props.size ?? 'md'
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
class={`flex items-center justify-center rounded-full border-b border-white font-semibold ${variantStyles[variant()]} ${sizeStyles[size()]} ${props.class ?? ''}`}
|
|
37
|
+
>
|
|
38
|
+
<Show when={props.count !== undefined} fallback={props.children}>
|
|
39
|
+
<span>{props.count}</span>
|
|
40
|
+
</Show>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button component for proyecto-viviana-ui
|
|
3
|
+
*
|
|
4
|
+
* A styled button component built on top of solidaria-components.
|
|
5
|
+
* This component only handles styling - all behavior and accessibility
|
|
6
|
+
* is provided by the headless Button from solidaria-components.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { type JSX, splitProps, mergeProps as solidMergeProps } from 'solid-js';
|
|
10
|
+
import { Button as HeadlessButton, type ButtonRenderProps } from '@proyecto-viviana/solidaria-components';
|
|
11
|
+
import type { ButtonProps } from './types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Buttons allow users to perform an action or to navigate to another page.
|
|
15
|
+
* They have multiple styles for various needs, and are ideal for calling attention to
|
|
16
|
+
* where a user needs to do something in order to move forward in a flow.
|
|
17
|
+
*
|
|
18
|
+
* Built on solidaria-components Button for full accessibility support.
|
|
19
|
+
* Styles are defined in components.css using the vui-button class system.
|
|
20
|
+
*/
|
|
21
|
+
export function Button(props: ButtonProps): JSX.Element {
|
|
22
|
+
const defaultProps: Partial<ButtonProps> = {
|
|
23
|
+
variant: 'primary',
|
|
24
|
+
buttonStyle: 'fill',
|
|
25
|
+
size: 'md',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const merged = solidMergeProps(defaultProps, props);
|
|
29
|
+
|
|
30
|
+
const [local, headlessProps] = splitProps(merged, [
|
|
31
|
+
'children',
|
|
32
|
+
'variant',
|
|
33
|
+
'buttonStyle',
|
|
34
|
+
'size',
|
|
35
|
+
'fullWidth',
|
|
36
|
+
'staticColor',
|
|
37
|
+
'class',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
// Generate class based on render props
|
|
41
|
+
const getClassName = (renderProps: ButtonRenderProps): string => {
|
|
42
|
+
const classList = [
|
|
43
|
+
'vui-button',
|
|
44
|
+
`vui-button--${local.buttonStyle}`,
|
|
45
|
+
`vui-button--${local.variant}`,
|
|
46
|
+
`vui-button--${local.size}`,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
if (renderProps.isPressed) {
|
|
50
|
+
classList.push('is-pressed');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (local.fullWidth) {
|
|
54
|
+
classList.push('vui-button--full-width');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (local.class) {
|
|
58
|
+
classList.push(local.class);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return classList.join(' ');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<HeadlessButton
|
|
66
|
+
{...headlessProps}
|
|
67
|
+
class={getClassName}
|
|
68
|
+
data-variant={local.variant}
|
|
69
|
+
data-style={local.buttonStyle}
|
|
70
|
+
data-static-color={local.staticColor || undefined}
|
|
71
|
+
>
|
|
72
|
+
{local.children}
|
|
73
|
+
</HeadlessButton>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js';
|
|
2
|
+
import type { ButtonProps as HeadlessButtonProps } from '@proyecto-viviana/solidaria-components';
|
|
3
|
+
|
|
4
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'accent' | 'positive' | 'negative' | 'ghost' | 'link';
|
|
5
|
+
export type ButtonStyle = 'fill' | 'outline';
|
|
6
|
+
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
7
|
+
export type StaticColor = 'white' | 'black';
|
|
8
|
+
|
|
9
|
+
export interface ButtonProps extends Omit<HeadlessButtonProps, 'class' | 'children' | 'style'> {
|
|
10
|
+
/** The content to display in the button. */
|
|
11
|
+
children?: JSX.Element;
|
|
12
|
+
/** The visual style of the button. */
|
|
13
|
+
variant?: ButtonVariant;
|
|
14
|
+
/** The background style of the button. */
|
|
15
|
+
buttonStyle?: ButtonStyle;
|
|
16
|
+
/** The size of the button. */
|
|
17
|
+
size?: ButtonSize;
|
|
18
|
+
/** Whether the button should take up the full width of its container. */
|
|
19
|
+
fullWidth?: boolean;
|
|
20
|
+
/** The static color style to apply. Useful when the button appears over a color background. */
|
|
21
|
+
staticColor?: StaticColor;
|
|
22
|
+
/** Additional CSS class name. */
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
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={local.children}>
|
|
198
|
+
<span class={labelClasses()}>{local.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
|
+
'children',
|
|
220
|
+
'label',
|
|
221
|
+
'description',
|
|
222
|
+
'errorMessage',
|
|
223
|
+
])
|
|
224
|
+
|
|
225
|
+
// Generate class based on render props
|
|
226
|
+
const getClassName = (renderProps: CheckboxGroupRenderProps): string => {
|
|
227
|
+
const base = 'flex flex-col gap-2'
|
|
228
|
+
const disabledClass = renderProps.isDisabled ? 'opacity-50' : ''
|
|
229
|
+
const custom = local.class || ''
|
|
230
|
+
return [base, disabledClass, custom].filter(Boolean).join(' ')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Render children function for the headless component
|
|
234
|
+
const renderChildren = (renderProps: CheckboxGroupRenderProps) => (
|
|
235
|
+
<>
|
|
236
|
+
<Show when={local.label}>
|
|
237
|
+
<span class="text-sm font-medium text-primary-200">{local.label}</span>
|
|
238
|
+
</Show>
|
|
239
|
+
<div class="flex flex-col gap-2">
|
|
240
|
+
{local.children}
|
|
241
|
+
</div>
|
|
242
|
+
<Show when={local.description && !renderProps.isInvalid}>
|
|
243
|
+
<span class="text-sm text-primary-400">{local.description}</span>
|
|
244
|
+
</Show>
|
|
245
|
+
<Show when={local.errorMessage && renderProps.isInvalid}>
|
|
246
|
+
<span class="text-sm text-danger-400">{local.errorMessage}</span>
|
|
247
|
+
</Show>
|
|
248
|
+
</>
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<HeadlessCheckboxGroup
|
|
253
|
+
{...headlessProps}
|
|
254
|
+
class={getClassName}
|
|
255
|
+
children={renderChildren as any}
|
|
256
|
+
/>
|
|
257
|
+
)
|
|
258
|
+
}
|