@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
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TextField component for proyecto-viviana-ui
|
|
3
|
+
*
|
|
4
|
+
* A styled text field component built on solidaria hooks directly.
|
|
5
|
+
* This bypasses solidaria-components for now due to context timing issues.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type JSX, splitProps, mergeProps as solidMergeProps, Show } from 'solid-js'
|
|
9
|
+
import {
|
|
10
|
+
createTextField,
|
|
11
|
+
createFocusRing,
|
|
12
|
+
type AriaTextFieldProps,
|
|
13
|
+
} from '@proyecto-viviana/solidaria'
|
|
14
|
+
import {
|
|
15
|
+
createTextFieldState,
|
|
16
|
+
} from '@proyecto-viviana/solid-stately'
|
|
17
|
+
|
|
18
|
+
// ============================================
|
|
19
|
+
// TYPES
|
|
20
|
+
// ============================================
|
|
21
|
+
|
|
22
|
+
export type TextFieldSize = 'sm' | 'md' | 'lg'
|
|
23
|
+
export type TextFieldVariant = 'outline' | 'filled'
|
|
24
|
+
|
|
25
|
+
export interface TextFieldProps extends Omit<AriaTextFieldProps, 'children'> {
|
|
26
|
+
/** The size of the text field. */
|
|
27
|
+
size?: TextFieldSize
|
|
28
|
+
/** The visual variant of the text field. */
|
|
29
|
+
variant?: TextFieldVariant
|
|
30
|
+
/** Additional CSS class name. */
|
|
31
|
+
class?: string
|
|
32
|
+
/** Label text for the input. */
|
|
33
|
+
label?: string
|
|
34
|
+
/** Description text shown below the input. */
|
|
35
|
+
description?: string
|
|
36
|
+
/** Error message shown when invalid. */
|
|
37
|
+
errorMessage?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================
|
|
41
|
+
// STYLES
|
|
42
|
+
// ============================================
|
|
43
|
+
|
|
44
|
+
const sizeStyles = {
|
|
45
|
+
sm: {
|
|
46
|
+
input: 'h-8 px-2 text-sm',
|
|
47
|
+
label: 'text-sm',
|
|
48
|
+
description: 'text-xs',
|
|
49
|
+
},
|
|
50
|
+
md: {
|
|
51
|
+
input: 'h-10 px-3 text-base',
|
|
52
|
+
label: 'text-sm',
|
|
53
|
+
description: 'text-sm',
|
|
54
|
+
},
|
|
55
|
+
lg: {
|
|
56
|
+
input: 'h-12 px-4 text-lg',
|
|
57
|
+
label: 'text-base',
|
|
58
|
+
description: 'text-sm',
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ============================================
|
|
63
|
+
// COMPONENT
|
|
64
|
+
// ============================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A text field allows users to enter a plain text value with a keyboard.
|
|
68
|
+
*
|
|
69
|
+
* Built directly on solidaria hooks for full accessibility support.
|
|
70
|
+
*/
|
|
71
|
+
export function TextField(props: TextFieldProps): JSX.Element {
|
|
72
|
+
const defaultProps: Partial<TextFieldProps> = {
|
|
73
|
+
size: 'md',
|
|
74
|
+
variant: 'outline',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const merged = solidMergeProps(defaultProps, props)
|
|
78
|
+
|
|
79
|
+
const [local, ariaProps] = splitProps(merged, [
|
|
80
|
+
'size',
|
|
81
|
+
'variant',
|
|
82
|
+
'class',
|
|
83
|
+
'label',
|
|
84
|
+
'description',
|
|
85
|
+
'errorMessage',
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
const size = () => sizeStyles[local.size!]
|
|
89
|
+
|
|
90
|
+
// Create text field state
|
|
91
|
+
const state = createTextFieldState(() => ({
|
|
92
|
+
value: ariaProps.value,
|
|
93
|
+
defaultValue: ariaProps.defaultValue,
|
|
94
|
+
onChange: ariaProps.onChange,
|
|
95
|
+
}))
|
|
96
|
+
|
|
97
|
+
// Create text field aria props
|
|
98
|
+
const textFieldAria = createTextField(() => ({
|
|
99
|
+
...ariaProps,
|
|
100
|
+
value: state.value(),
|
|
101
|
+
onChange: state.setValue,
|
|
102
|
+
}))
|
|
103
|
+
|
|
104
|
+
// Create focus ring
|
|
105
|
+
const { isFocused, isFocusVisible, focusProps } = createFocusRing()
|
|
106
|
+
|
|
107
|
+
// Compute classes
|
|
108
|
+
const containerClasses = () => {
|
|
109
|
+
const base = 'flex flex-col'
|
|
110
|
+
const disabledClass = ariaProps.isDisabled ? 'opacity-60' : ''
|
|
111
|
+
const custom = local.class || ''
|
|
112
|
+
return [base, disabledClass, custom].filter(Boolean).join(' ')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const inputClasses = () => {
|
|
116
|
+
const base = 'w-full rounded-md transition-all duration-200 outline-none'
|
|
117
|
+
const sizeClass = size().input
|
|
118
|
+
|
|
119
|
+
let variantClass: string
|
|
120
|
+
if (local.variant === 'filled') {
|
|
121
|
+
variantClass = 'bg-bg-200 border border-transparent'
|
|
122
|
+
} else {
|
|
123
|
+
variantClass = 'bg-transparent border border-bg-400'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let stateClass: string
|
|
127
|
+
if (ariaProps.isDisabled) {
|
|
128
|
+
stateClass = 'bg-bg-200 text-primary-500 cursor-not-allowed'
|
|
129
|
+
} else if (textFieldAria.isInvalid) {
|
|
130
|
+
stateClass = 'border-danger-500 focus:border-danger-400 focus:ring-2 focus:ring-danger-400/20'
|
|
131
|
+
} else {
|
|
132
|
+
stateClass = 'text-primary-100 placeholder:text-primary-500 focus:border-accent focus:ring-2 focus:ring-accent/20'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const hoverClass = ariaProps.isDisabled ? '' : 'hover:border-accent-300'
|
|
136
|
+
|
|
137
|
+
return [base, sizeClass, variantClass, stateClass, hoverClass].filter(Boolean).join(' ')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const labelClasses = () => {
|
|
141
|
+
const base = 'block font-medium text-primary-200 mb-1'
|
|
142
|
+
const sizeClass = size().label
|
|
143
|
+
return [base, sizeClass].filter(Boolean).join(' ')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const descriptionClasses = () => {
|
|
147
|
+
const base = 'mt-1 text-primary-400'
|
|
148
|
+
const sizeClass = size().description
|
|
149
|
+
return [base, sizeClass].filter(Boolean).join(' ')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const errorClasses = () => {
|
|
153
|
+
const base = 'mt-1 text-danger-400'
|
|
154
|
+
const sizeClass = size().description
|
|
155
|
+
return [base, sizeClass].filter(Boolean).join(' ')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Clean props - remove ref to avoid type conflicts
|
|
159
|
+
const cleanLabelProps = () => {
|
|
160
|
+
const { ref: _ref, ...rest } = textFieldAria.labelProps as Record<string, unknown>
|
|
161
|
+
return rest
|
|
162
|
+
}
|
|
163
|
+
const cleanInputProps = () => {
|
|
164
|
+
const { ref: _ref1, ...rest } = textFieldAria.inputProps as Record<string, unknown>
|
|
165
|
+
const { ref: _ref2, ...focusRest } = focusProps as Record<string, unknown>
|
|
166
|
+
return { ...rest, ...focusRest }
|
|
167
|
+
}
|
|
168
|
+
const cleanDescriptionProps = () => {
|
|
169
|
+
const { ref: _ref, ...rest } = textFieldAria.descriptionProps as Record<string, unknown>
|
|
170
|
+
return rest
|
|
171
|
+
}
|
|
172
|
+
const cleanErrorMessageProps = () => {
|
|
173
|
+
const { ref: _ref, ...rest } = textFieldAria.errorMessageProps as Record<string, unknown>
|
|
174
|
+
return rest
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div
|
|
179
|
+
class={containerClasses()}
|
|
180
|
+
data-disabled={ariaProps.isDisabled || undefined}
|
|
181
|
+
data-invalid={textFieldAria.isInvalid || undefined}
|
|
182
|
+
data-readonly={ariaProps.isReadOnly || undefined}
|
|
183
|
+
data-required={ariaProps.isRequired || undefined}
|
|
184
|
+
data-focused={isFocused() || undefined}
|
|
185
|
+
data-focus-visible={isFocusVisible() || undefined}
|
|
186
|
+
>
|
|
187
|
+
<Show when={local.label}>
|
|
188
|
+
<label {...cleanLabelProps()} class={labelClasses()}>
|
|
189
|
+
{local.label}
|
|
190
|
+
<Show when={ariaProps.isRequired}>
|
|
191
|
+
<span class="text-danger-400 ml-0.5">*</span>
|
|
192
|
+
</Show>
|
|
193
|
+
</label>
|
|
194
|
+
</Show>
|
|
195
|
+
|
|
196
|
+
<input {...cleanInputProps()} class={inputClasses()} />
|
|
197
|
+
|
|
198
|
+
<Show when={local.description && !textFieldAria.isInvalid}>
|
|
199
|
+
<p {...cleanDescriptionProps()} class={descriptionClasses()}>
|
|
200
|
+
{local.description}
|
|
201
|
+
</p>
|
|
202
|
+
</Show>
|
|
203
|
+
|
|
204
|
+
<Show when={local.errorMessage && textFieldAria.isInvalid}>
|
|
205
|
+
<p {...cleanErrorMessageProps()} class={errorClasses()}>
|
|
206
|
+
{local.errorMessage}
|
|
207
|
+
</p>
|
|
208
|
+
</Show>
|
|
209
|
+
</div>
|
|
210
|
+
)
|
|
211
|
+
}
|
package/src/theme.css
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Viviana UI Theme - Tailwind v4
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: Due to Tailwind v4's @theme processing, the @theme block
|
|
5
|
+
* should be COPIED into your app's CSS, not imported via @import.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
*
|
|
9
|
+
* 1. Add fonts in HTML <head>:
|
|
10
|
+
* <link rel="preconnect" href="https://fonts.googleapis.com">
|
|
11
|
+
* <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
12
|
+
* <link href="https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100..900;1,100..900&family=Sen:wght@400..800&display=swap" rel="stylesheet">
|
|
13
|
+
*
|
|
14
|
+
* 2. In your app's CSS:
|
|
15
|
+
* @import "tailwindcss";
|
|
16
|
+
*
|
|
17
|
+
* @theme {
|
|
18
|
+
* // Copy the @theme block below into your CSS
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* @import "proyecto-viviana-ui/components.css";
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
@theme {
|
|
25
|
+
/* Breakpoints */
|
|
26
|
+
--breakpoint-xxs: 320px;
|
|
27
|
+
--breakpoint-xs: 475px;
|
|
28
|
+
|
|
29
|
+
/* Background - Dark blue-gray */
|
|
30
|
+
--color-bg-100: #3B5260;
|
|
31
|
+
--color-bg-200: #293E4B;
|
|
32
|
+
--color-bg-300: #24313a;
|
|
33
|
+
--color-bg-400: #1D272E;
|
|
34
|
+
--color-bg-light: #515151;
|
|
35
|
+
|
|
36
|
+
/* Accent - Pink */
|
|
37
|
+
--color-accent: #DF5C9A;
|
|
38
|
+
--color-accent-200: #FFB2D7;
|
|
39
|
+
--color-accent-300: #FF88C0;
|
|
40
|
+
--color-accent-500: #DF5C9A;
|
|
41
|
+
--color-accent-highlight: #e2a2be;
|
|
42
|
+
|
|
43
|
+
/* Primary - Cyan/Blue */
|
|
44
|
+
--color-primary-100: #D9F2FF;
|
|
45
|
+
--color-primary-200: #D0E9F5;
|
|
46
|
+
--color-primary-300: #ADCCDC;
|
|
47
|
+
--color-primary-400: #B8DFFF;
|
|
48
|
+
--color-primary-500: #75ABC7;
|
|
49
|
+
--color-primary-600: #58748B;
|
|
50
|
+
--color-primary-700: #4F6D85;
|
|
51
|
+
--color-primary-800: #4C6477;
|
|
52
|
+
|
|
53
|
+
/* Success - Green */
|
|
54
|
+
--color-success-100: #E5FFF3;
|
|
55
|
+
--color-success-400: #C2FFC8;
|
|
56
|
+
--color-success-600: #559D87;
|
|
57
|
+
|
|
58
|
+
/* Warning - Yellow */
|
|
59
|
+
--color-warning-100: #FFFBE5;
|
|
60
|
+
--color-warning-400: #FFDD87;
|
|
61
|
+
--color-warning-600: #9D8D55;
|
|
62
|
+
|
|
63
|
+
/* Danger - Red */
|
|
64
|
+
--color-danger-100: #FFE5E5;
|
|
65
|
+
--color-danger-400: #FF8787;
|
|
66
|
+
--color-danger-600: #9D5555;
|
|
67
|
+
|
|
68
|
+
/* Cards */
|
|
69
|
+
--color-cards-bg: #373737;
|
|
70
|
+
--color-cards-bg-load: #484848;
|
|
71
|
+
--color-correct: #83f1a7;
|
|
72
|
+
--color-incorrect: #e74c3c;
|
|
73
|
+
|
|
74
|
+
/* Typography */
|
|
75
|
+
--font-sen: 'Sen', sans-serif;
|
|
76
|
+
--font-jost: 'Jost', sans-serif;
|
|
77
|
+
--font-sans: 'Sen', sans-serif;
|
|
78
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Font utility classes */
|
|
82
|
+
.font-sen {
|
|
83
|
+
font-family: 'Sen', sans-serif;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.font-jost {
|
|
87
|
+
font-family: 'Jost', sans-serif;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Custom drop shadows */
|
|
91
|
+
.drop-shadow-title-card {
|
|
92
|
+
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.drop-shadow-logo {
|
|
96
|
+
filter: drop-shadow(0 0 10px rgba(223, 92, 154, 0.8));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.shadow-primary-chip {
|
|
100
|
+
box-shadow: 0 2px 8px rgba(79, 109, 133, 0.4);
|
|
101
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js'
|
|
2
|
+
|
|
3
|
+
export interface TooltipProps {
|
|
4
|
+
/** The content to show in the tooltip */
|
|
5
|
+
label: string
|
|
6
|
+
/** The trigger element */
|
|
7
|
+
children: JSX.Element
|
|
8
|
+
/** Position of the tooltip */
|
|
9
|
+
position?: 'top' | 'bottom'
|
|
10
|
+
/** Additional CSS class */
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tooltip component that shows a label on hover.
|
|
16
|
+
* Uses CSS-only hover effect for performance.
|
|
17
|
+
*/
|
|
18
|
+
export function Tooltip(props: TooltipProps): JSX.Element {
|
|
19
|
+
const position = () => props.position ?? 'bottom'
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div class={`vui-tooltip ${props.class ?? ''}`}>
|
|
23
|
+
<div class="vui-tooltip__trigger">
|
|
24
|
+
{props.children}
|
|
25
|
+
</div>
|
|
26
|
+
<div class={`vui-tooltip__content vui-tooltip__content--${position()}`}>
|
|
27
|
+
<span>{props.label}</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|