@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
@@ -0,0 +1,81 @@
1
+ import type { JSX } from 'solid-js'
2
+ import { Show } from 'solid-js'
3
+ import { Avatar } from '../../avatar'
4
+
5
+ export type TimelineEventType = 'follow' | 'like' | 'comment' | 'event' | 'custom'
6
+
7
+ export interface TimelineItemProps {
8
+ type?: TimelineEventType
9
+ icon?: JSX.Element
10
+ leftUser?: {
11
+ name: string
12
+ avatar?: string
13
+ }
14
+ rightUser?: {
15
+ name: string
16
+ avatar?: string
17
+ }
18
+ message?: JSX.Element
19
+ class?: string
20
+ }
21
+
22
+ const eventMessages: Record<TimelineEventType, (left: string, right: string) => JSX.Element> = {
23
+ follow: (left, right) => (
24
+ <>
25
+ <span class="font-semibold text-accent-200">{left}</span>
26
+ {' ha empezado a seguir a '}
27
+ <span class="font-semibold text-accent-200">{right}</span>
28
+ </>
29
+ ),
30
+ like: (left, right) => (
31
+ <>
32
+ <span class="font-semibold text-accent-200">{left}</span>
33
+ {' le ha dado like a '}
34
+ <span class="font-semibold text-accent-200">{right}</span>
35
+ </>
36
+ ),
37
+ comment: (left, right) => (
38
+ <>
39
+ <span class="font-semibold text-accent-200">{left}</span>
40
+ {' ha comentado en '}
41
+ <span class="font-semibold text-accent-200">{right}</span>
42
+ </>
43
+ ),
44
+ event: (left, right) => (
45
+ <>
46
+ <span class="font-semibold text-accent-200">{left}</span>
47
+ {' asistirá al evento de '}
48
+ <span class="font-semibold text-accent-200">{right}</span>
49
+ </>
50
+ ),
51
+ custom: () => null,
52
+ }
53
+
54
+ export function TimelineItem(props: TimelineItemProps) {
55
+ const type = () => props.type ?? 'follow'
56
+ const leftName = () => props.leftUser?.name ?? ''
57
+ const rightName = () => props.rightUser?.name ?? ''
58
+
59
+ return (
60
+ <div class={`inline-flex w-auto flex-col gap-5 rounded-2xl border border-primary-700 bg-bg-200 p-5 hover:bg-bg-300 transition-colors ${props.class ?? ''}`}>
61
+ <div class="flex items-center justify-around gap-3">
62
+ <Show when={props.leftUser}>
63
+ <Avatar src={props.leftUser!.avatar} alt={props.leftUser!.name} />
64
+ </Show>
65
+ <Show when={props.icon}>
66
+ {props.icon}
67
+ </Show>
68
+ <Show when={props.rightUser}>
69
+ <Avatar src={props.rightUser!.avatar} alt={props.rightUser!.name} />
70
+ </Show>
71
+ </div>
72
+ <div class="flex items-center justify-center gap-3 text-center">
73
+ <span class="font-light text-primary-300">
74
+ <Show when={props.message} fallback={eventMessages[type()](leftName(), rightName())}>
75
+ {props.message}
76
+ </Show>
77
+ </span>
78
+ </div>
79
+ </div>
80
+ )
81
+ }
@@ -0,0 +1,69 @@
1
+ import type { JSX } from 'solid-js'
2
+ import { Show } from 'solid-js'
3
+ import { Button } from '../button'
4
+ import { Chip } from '../custom/chip'
5
+
6
+ export interface DialogProps {
7
+ title: string
8
+ subtitle?: string
9
+ children: JSX.Element
10
+ tags?: string[]
11
+ primaryAction?: {
12
+ label: string
13
+ onClick: () => void
14
+ }
15
+ secondaryAction?: {
16
+ label: string
17
+ onClick: () => void
18
+ }
19
+ class?: string
20
+ }
21
+
22
+ export function Dialog(props: DialogProps) {
23
+ return (
24
+ <div class={`flex flex-col w-full max-w-[540px] ${props.class ?? ''}`}>
25
+ <span class="relative left-8 top-4 bg-gradient-to-b from-primary-100 to-primary-500 bg-clip-text text-4xl font-extrabold text-transparent">
26
+ {props.title}
27
+ </span>
28
+ <div class="flex h-[50px] flex-col justify-end rounded-t-[26px] bg-primary-800">
29
+ <div class="flex h-[40px] flex-col justify-end rounded-t-[30px] bg-primary-600">
30
+ <div class="flex h-[20px] items-center justify-end rounded-t-[100px] bg-primary-700 pr-4">
31
+ <Show when={props.subtitle}>
32
+ <span class="text-base font-normal text-primary-300">
33
+ {props.subtitle}
34
+ </span>
35
+ </Show>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ <div class="mt-1 flex min-h-[200px] flex-col rounded-b-[26px] rounded-t-lg border-b border-accent-500 bg-bg-300">
40
+ <div class="flex flex-1 flex-col p-4">
41
+ <div class="text-lg text-gray-100">{props.children}</div>
42
+ <Show when={props.primaryAction || props.secondaryAction}>
43
+ <div class="flex flex-1 flex-row items-end justify-center gap-5 py-5">
44
+ <Show when={props.secondaryAction}>
45
+ <Button variant="primary" buttonStyle="outline" onPress={props.secondaryAction!.onClick}>
46
+ {props.secondaryAction!.label}
47
+ </Button>
48
+ </Show>
49
+ <Show when={props.primaryAction}>
50
+ <Button variant="primary" onPress={props.primaryAction!.onClick}>
51
+ {props.primaryAction!.label}
52
+ </Button>
53
+ </Show>
54
+ </div>
55
+ </Show>
56
+ </div>
57
+ </div>
58
+ <Show when={props.tags && props.tags.length > 0}>
59
+ <div class="relative bottom-4 right-4 w-full">
60
+ <div class="flex flex-row justify-end gap-2 pr-4">
61
+ {props.tags!.map((tag) => (
62
+ <Chip variant="primary" text={tag} />
63
+ ))}
64
+ </div>
65
+ </div>
66
+ </Show>
67
+ </div>
68
+ )
69
+ }
@@ -0,0 +1,20 @@
1
+ import type { JSX } from 'solid-js';
2
+
3
+ export interface GitHubIconProps {
4
+ size?: number;
5
+ color?: string;
6
+ }
7
+
8
+ export function GitHubIcon(props: GitHubIconProps): JSX.Element {
9
+ return (
10
+ <svg
11
+ viewBox="0 0 24 24"
12
+ width={props.size}
13
+ height={props.size}
14
+ fill={props.color}
15
+ aria-hidden="true"
16
+ >
17
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
18
+ </svg>
19
+ );
20
+ }
@@ -0,0 +1,48 @@
1
+ import type { Component, JSX } from 'solid-js'
2
+
3
+ export interface IconProps {
4
+ /** The icon component to render (should accept size and color props) */
5
+ icon: Component<{ size?: string | number; color?: string }>
6
+ /** Size of the icon (e.g., '24px' or 24) */
7
+ size?: string | number
8
+ /** Color of the icon */
9
+ color?: string
10
+ /** Whether to show the accent shadow effect (4px offset to bottom) */
11
+ withShadow?: boolean
12
+ /** Additional CSS class */
13
+ class?: string
14
+ /** Click handler */
15
+ onClick?: () => void
16
+ }
17
+
18
+ /**
19
+ * Icon wrapper component with optional accent shadow effect.
20
+ *
21
+ * The shadow effect creates a 4px offset accent-colored duplicate
22
+ * of the icon behind it for a stylized look.
23
+ */
24
+ export function Icon(props: IconProps): JSX.Element {
25
+ const size = () => props.size ?? 24
26
+ const color = () => props.color ?? 'var(--color-primary-500)'
27
+ const IconComponent = props.icon
28
+
29
+ return (
30
+ <div
31
+ class={`vui-icon ${props.withShadow ? 'vui-icon--with-shadow' : ''} ${props.class ?? ''}`}
32
+ onClick={props.onClick}
33
+ >
34
+ {props.withShadow && (
35
+ <div class="vui-icon__shadow">
36
+ <IconComponent size={size()} color="var(--color-accent)" />
37
+ </div>
38
+ )}
39
+ <div class="vui-icon__main">
40
+ <IconComponent size={size()} color={color()} />
41
+ </div>
42
+ </div>
43
+ )
44
+ }
45
+
46
+ // Re-export common icons
47
+ export { GitHubIcon } from './icons/GitHubIcon'
48
+ export type { GitHubIconProps } from './icons/GitHubIcon'
package/src/index.ts ADDED
@@ -0,0 +1,107 @@
1
+ // ============================================
2
+ // SPECTRUM-BASED COMPONENTS
3
+ // ============================================
4
+
5
+ // Button
6
+ export { Button } from './button';
7
+ export type { ButtonProps, ButtonVariant, ButtonStyle, StaticColor } from './button';
8
+
9
+ // Badge
10
+ export { Badge } from './badge';
11
+ export type { BadgeProps, BadgeVariant, BadgeSize } from './badge';
12
+
13
+ // Alert
14
+ export { Alert } from './alert';
15
+ export type { AlertProps, AlertVariant } from './alert';
16
+
17
+ // Avatar
18
+ export { Avatar, AvatarGroup } from './avatar';
19
+ export type { AvatarProps, AvatarGroupProps, AvatarSize } from './avatar';
20
+
21
+ // Switch
22
+ export { TabSwitch, ToggleSwitch } from './switch';
23
+ export type { TabSwitchProps, ToggleSwitchProps, SwitchSize } from './switch';
24
+
25
+ // Checkbox
26
+ export { Checkbox, CheckboxGroup } from './checkbox';
27
+ export type { CheckboxProps, CheckboxGroupProps, CheckboxSize } from './checkbox';
28
+
29
+ // Radio
30
+ export { RadioGroup, Radio } from './radio';
31
+ export type { RadioGroupProps, RadioProps, RadioGroupOrientation, RadioGroupSize } from './radio';
32
+
33
+ // Dialog
34
+ export { Dialog } from './dialog';
35
+ export type { DialogProps } from './dialog';
36
+
37
+ // Icon
38
+ export { Icon, GitHubIcon } from './icon';
39
+ export type { IconProps, GitHubIconProps } from './icon';
40
+
41
+ // Tooltip
42
+ export { Tooltip } from './tooltip';
43
+ export type { TooltipProps } from './tooltip';
44
+
45
+ // TextField
46
+ export { TextField } from './textfield';
47
+ export type { TextFieldProps, TextFieldSize, TextFieldVariant } from './textfield';
48
+
49
+ // Link
50
+ export { Link } from './link';
51
+ export type { LinkProps, LinkVariant } from './link';
52
+
53
+ // ProgressBar
54
+ export { ProgressBar } from './progress-bar';
55
+ export type { ProgressBarProps, ProgressBarSize, ProgressBarVariant } from './progress-bar';
56
+
57
+ // Separator
58
+ export { Separator } from './separator';
59
+ export type { SeparatorProps, SeparatorVariant, SeparatorSize } from './separator';
60
+
61
+ // ============================================
62
+ // CUSTOM COMPONENTS
63
+ // ============================================
64
+
65
+ // Chip
66
+ export { Chip } from './custom/chip';
67
+ export type { ChipProps, ChipVariant } from './custom/chip';
68
+
69
+ // NavHeader
70
+ export { NavHeader } from './custom/nav-header';
71
+ export type { NavHeaderProps } from './custom/nav-header';
72
+
73
+ // LateralNav
74
+ export { LateralNav, NavItem, NavLink, NavSection } from './custom/lateral-nav';
75
+ export type { LateralNavProps, NavItemProps, NavLinkProps, NavSectionProps } from './custom/lateral-nav';
76
+
77
+ // TimelineItem
78
+ export { TimelineItem } from './custom/timeline-item';
79
+ export type { TimelineItemProps, TimelineEventType } from './custom/timeline-item';
80
+
81
+ // Conversation
82
+ export { Conversation, ConversationPreview, ConversationBubble } from './custom/conversation';
83
+ export type { ConversationProps, ConversationPreviewProps, ConversationBubbleProps, Message } from './custom/conversation';
84
+
85
+ // ProfileCard
86
+ export { ProfileCard } from './custom/profile-card';
87
+ export type { ProfileCardProps } from './custom/profile-card';
88
+
89
+ // EventCard
90
+ export { EventCard, EventListItem } from './custom/event-card';
91
+ export type { EventCardProps, EventListItemProps } from './custom/event-card';
92
+
93
+ // CalendarCard
94
+ export { CalendarCard } from './custom/calendar-card';
95
+ export type { CalendarCardProps } from './custom/calendar-card';
96
+
97
+ // Logo
98
+ export { Logo } from './custom/logo';
99
+ export type { LogoProps, LogoSize } from './custom/logo';
100
+
101
+ // ProjectCard
102
+ export { ProjectCard } from './custom/project-card';
103
+ export type { ProjectCardProps, ProjectCardSize } from './custom/project-card';
104
+
105
+ // PageLayout
106
+ export { PageLayout } from './custom/page-layout';
107
+ export type { PageLayoutProps } from './custom/page-layout';
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Link component for proyecto-viviana-ui
3
+ *
4
+ * Styled link component built on top of solidaria-components.
5
+ */
6
+
7
+ import { type JSX, splitProps } from 'solid-js';
8
+ import {
9
+ Link as HeadlessLink,
10
+ type LinkProps as HeadlessLinkProps,
11
+ type LinkRenderProps,
12
+ } from '@proyecto-viviana/solidaria-components';
13
+
14
+ // ============================================
15
+ // TYPES
16
+ // ============================================
17
+
18
+ export type LinkVariant = 'primary' | 'secondary' | 'subtle';
19
+
20
+ export interface LinkProps extends Omit<HeadlessLinkProps, 'class' | 'style' | 'children'> {
21
+ /** The visual style of the link. @default 'primary' */
22
+ variant?: LinkVariant;
23
+ /** Whether the link is on its own vs inside a longer string of text. */
24
+ isStandalone?: boolean;
25
+ /** Whether the link should be displayed with a quiet style (no underline by default). */
26
+ isQuiet?: boolean;
27
+ /** Additional CSS class name. */
28
+ class?: string;
29
+ /** The content of the link. */
30
+ children?: JSX.Element;
31
+ }
32
+
33
+ // ============================================
34
+ // STYLES
35
+ // ============================================
36
+
37
+ const variantStyles = {
38
+ primary: 'text-accent hover:text-accent-300',
39
+ secondary: 'text-primary-300 hover:text-primary-200',
40
+ subtle: 'text-primary-400 hover:text-primary-300',
41
+ };
42
+
43
+ // ============================================
44
+ // LINK COMPONENT
45
+ // ============================================
46
+
47
+ /**
48
+ * Links allow users to navigate to a different location.
49
+ * They can be presented inline inside a paragraph or as standalone text.
50
+ *
51
+ * Built on solidaria-components Link for full accessibility support.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * <Link href="/about">About Us</Link>
56
+ *
57
+ * // Secondary variant
58
+ * <Link href="/help" variant="secondary">Help</Link>
59
+ *
60
+ * // Standalone (bold, no underline until hover)
61
+ * <Link href="/home" isStandalone isQuiet>Home</Link>
62
+ * ```
63
+ */
64
+ export function Link(props: LinkProps): JSX.Element {
65
+ const [local, headlessProps] = splitProps(props, [
66
+ 'variant',
67
+ 'isStandalone',
68
+ 'isQuiet',
69
+ 'class',
70
+ 'children',
71
+ ]);
72
+
73
+ const variant = local.variant ?? 'primary';
74
+ const customClass = local.class ?? '';
75
+
76
+ // Generate class based on render props
77
+ const getClassName = (renderProps: LinkRenderProps): string => {
78
+ const base = 'transition-colors duration-200 cursor-pointer rounded-sm outline-none';
79
+
80
+ // Variant colors
81
+ const variantClass = variantStyles[variant];
82
+
83
+ // Underline behavior
84
+ let underlineClass: string;
85
+ if (local.isStandalone && local.isQuiet) {
86
+ // Quiet standalone: no underline by default, underline on hover/focus
87
+ underlineClass = renderProps.isHovered || renderProps.isFocusVisible
88
+ ? 'underline'
89
+ : 'no-underline';
90
+ } else {
91
+ // Inline links always have underline for accessibility
92
+ underlineClass = 'underline';
93
+ }
94
+
95
+ // Font weight for standalone
96
+ const weightClass = local.isStandalone ? 'font-medium' : '';
97
+
98
+ // Focus ring
99
+ const focusClass = renderProps.isFocusVisible
100
+ ? 'ring-2 ring-accent-300 ring-offset-2 ring-offset-bg-400'
101
+ : '';
102
+
103
+ // Disabled state
104
+ const disabledClass = renderProps.isDisabled
105
+ ? 'opacity-50 cursor-not-allowed'
106
+ : '';
107
+
108
+ // Pressed state
109
+ const pressedClass = renderProps.isPressed ? 'opacity-80' : '';
110
+
111
+ return [
112
+ base,
113
+ variantClass,
114
+ underlineClass,
115
+ weightClass,
116
+ focusClass,
117
+ disabledClass,
118
+ pressedClass,
119
+ customClass,
120
+ ].filter(Boolean).join(' ');
121
+ };
122
+
123
+ return (
124
+ <HeadlessLink
125
+ {...headlessProps}
126
+ class={getClassName}
127
+ >
128
+ {local.children}
129
+ </HeadlessLink>
130
+ );
131
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * ProgressBar component for proyecto-viviana-ui
3
+ *
4
+ * Styled progress bar component built on top of the solidaria hook directly.
5
+ */
6
+
7
+ import { type JSX, splitProps, Show, createMemo } from 'solid-js';
8
+ import { createProgressBar } from '@proyecto-viviana/solidaria';
9
+
10
+ // ============================================
11
+ // TYPES
12
+ // ============================================
13
+
14
+ export type ProgressBarSize = 'sm' | 'md' | 'lg';
15
+ export type ProgressBarVariant = 'primary' | 'accent' | 'success' | 'warning' | 'danger';
16
+
17
+ export interface ProgressBarProps {
18
+ /** The current value (controlled). @default 0 */
19
+ value?: number;
20
+ /** The smallest value allowed. @default 0 */
21
+ minValue?: number;
22
+ /** The largest value allowed. @default 100 */
23
+ maxValue?: number;
24
+ /** The content to display as the value's label (e.g. "1 of 4"). */
25
+ valueLabel?: string;
26
+ /** Whether presentation is indeterminate when progress isn't known. */
27
+ isIndeterminate?: boolean;
28
+ /** The size of the progress bar. @default 'md' */
29
+ size?: ProgressBarSize;
30
+ /** The visual style variant. @default 'primary' */
31
+ variant?: ProgressBarVariant;
32
+ /** The label to display above the progress bar. */
33
+ label?: string;
34
+ /** Whether to show the value text. @default true for determinate progress */
35
+ showValueLabel?: boolean;
36
+ /** Additional CSS class name. */
37
+ class?: string;
38
+ /** An accessibility label. */
39
+ 'aria-label'?: string;
40
+ }
41
+
42
+ // ============================================
43
+ // STYLES
44
+ // ============================================
45
+
46
+ const sizeStyles = {
47
+ sm: {
48
+ track: 'h-1',
49
+ text: 'text-xs',
50
+ },
51
+ md: {
52
+ track: 'h-2',
53
+ text: 'text-sm',
54
+ },
55
+ lg: {
56
+ track: 'h-3',
57
+ text: 'text-base',
58
+ },
59
+ };
60
+
61
+ const variantStyles = {
62
+ primary: 'bg-primary-500',
63
+ accent: 'bg-accent',
64
+ success: 'bg-green-500',
65
+ warning: 'bg-yellow-500',
66
+ danger: 'bg-red-500',
67
+ };
68
+
69
+ // ============================================
70
+ // UTILITIES
71
+ // ============================================
72
+
73
+ function clamp(value: number, min: number, max: number): number {
74
+ return Math.min(Math.max(value, min), max);
75
+ }
76
+
77
+ // ============================================
78
+ // PROGRESSBAR COMPONENT
79
+ // ============================================
80
+
81
+ /**
82
+ * Progress bars show either determinate or indeterminate progress of an operation
83
+ * over time.
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * <ProgressBar value={50} label="Loading..." />
88
+ *
89
+ * // Indeterminate
90
+ * <ProgressBar isIndeterminate label="Processing..." />
91
+ *
92
+ * // Different variants
93
+ * <ProgressBar value={75} variant="success" />
94
+ * ```
95
+ */
96
+ export function ProgressBar(props: ProgressBarProps): JSX.Element {
97
+ const [local, ariaProps] = splitProps(props, [
98
+ 'size',
99
+ 'variant',
100
+ 'label',
101
+ 'showValueLabel',
102
+ 'class',
103
+ ]);
104
+
105
+ const size = () => local.size ?? 'md';
106
+ const variant = () => local.variant ?? 'primary';
107
+ const isIndeterminate = () => ariaProps.isIndeterminate ?? false;
108
+ const showValueLabel = () => local.showValueLabel ?? !isIndeterminate();
109
+
110
+ // Create progress bar aria props
111
+ const progressAria = createProgressBar({
112
+ get value() { return ariaProps.value; },
113
+ get minValue() { return ariaProps.minValue; },
114
+ get maxValue() { return ariaProps.maxValue; },
115
+ get valueLabel() { return ariaProps.valueLabel; },
116
+ get isIndeterminate() { return ariaProps.isIndeterminate; },
117
+ get label() { return local.label; },
118
+ get 'aria-label'() { return ariaProps['aria-label']; },
119
+ });
120
+
121
+ // Calculate percentage
122
+ const percentage = createMemo(() => {
123
+ if (isIndeterminate()) {
124
+ return undefined;
125
+ }
126
+ const value = ariaProps.value ?? 0;
127
+ const minValue = ariaProps.minValue ?? 0;
128
+ const maxValue = ariaProps.maxValue ?? 100;
129
+ const clampedValue = clamp(value, minValue, maxValue);
130
+ return ((clampedValue - minValue) / (maxValue - minValue)) * 100;
131
+ });
132
+
133
+ // Get value text from aria props
134
+ const valueText = () => progressAria.progressBarProps['aria-valuetext'] as string | undefined;
135
+
136
+ const sizeConfig = () => sizeStyles[size()];
137
+
138
+ return (
139
+ <div
140
+ {...progressAria.progressBarProps}
141
+ class={`w-full ${local.class ?? ''}`}
142
+ >
143
+ {/* Label and value row */}
144
+ <Show when={local.label || showValueLabel()}>
145
+ <div class={`flex justify-between items-center mb-1 ${sizeConfig().text}`}>
146
+ <Show when={local.label}>
147
+ <span class="text-primary-200 font-medium">{local.label}</span>
148
+ </Show>
149
+ <Show when={showValueLabel() && !isIndeterminate()}>
150
+ <span class="text-primary-300">{valueText()}</span>
151
+ </Show>
152
+ </div>
153
+ </Show>
154
+
155
+ {/* Track */}
156
+ <div class={`w-full ${sizeConfig().track} bg-bg-300 rounded-full overflow-hidden`}>
157
+ {/* Fill */}
158
+ <div
159
+ class={`h-full rounded-full transition-all duration-300 ${variantStyles[variant()]} ${
160
+ isIndeterminate() ? 'animate-progress-indeterminate' : ''
161
+ }`}
162
+ style={{
163
+ width: isIndeterminate() ? '30%' : `${percentage()}%`,
164
+ }}
165
+ />
166
+ </div>
167
+ </div>
168
+ );
169
+ }