@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.
Files changed (39) hide show
  1. package/dist/index.js +2 -2
  2. package/dist/index.js.map +1 -1
  3. package/package.json +6 -5
  4. package/src/alert/index.tsx +48 -0
  5. package/src/assets/favicon.png +0 -0
  6. package/src/assets/fire.gif +0 -0
  7. package/src/avatar/index.tsx +75 -0
  8. package/src/badge/index.tsx +43 -0
  9. package/src/button/Button.tsx +75 -0
  10. package/src/button/index.ts +2 -0
  11. package/src/button/types.ts +24 -0
  12. package/src/checkbox/index.tsx +258 -0
  13. package/src/components.css +975 -0
  14. package/src/custom/calendar-card/index.tsx +66 -0
  15. package/src/custom/chip/index.tsx +32 -0
  16. package/src/custom/conversation/index.tsx +105 -0
  17. package/src/custom/event-card/index.tsx +126 -0
  18. package/src/custom/lateral-nav/index.tsx +88 -0
  19. package/src/custom/logo/index.tsx +54 -0
  20. package/src/custom/nav-header/index.tsx +42 -0
  21. package/src/custom/page-layout/index.tsx +20 -0
  22. package/src/custom/profile-card/index.tsx +58 -0
  23. package/src/custom/project-card/index.tsx +59 -0
  24. package/src/custom/timeline-item/index.tsx +81 -0
  25. package/src/dialog/index.tsx +69 -0
  26. package/src/icon/icons/GitHubIcon.tsx +20 -0
  27. package/src/icon/index.tsx +48 -0
  28. package/src/index.ts +107 -0
  29. package/src/link/index.tsx +131 -0
  30. package/src/progress-bar/index.tsx +169 -0
  31. package/src/radio/index.tsx +219 -0
  32. package/src/separator/index.tsx +141 -0
  33. package/src/styles.css +450 -0
  34. package/src/switch/ToggleSwitch.tsx +113 -0
  35. package/src/switch/index.tsx +90 -0
  36. package/src/test-utils/index.ts +32 -0
  37. package/src/textfield/index.tsx +211 -0
  38. package/src/theme.css +101 -0
  39. 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.0",
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": "./dist/index.js",
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.1",
32
- "@proyecto-viviana/solidaria-components": "^0.0.1"
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,2 @@
1
+ export { Button } from './Button';
2
+ export type { ButtonProps, ButtonVariant, ButtonStyle, ButtonSize, StaticColor } from './types';
@@ -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
+ }