@fragments-sdk/ui 0.1.0
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/package.json +44 -0
- package/src/brand.ts +15 -0
- package/src/components/Alert/Alert.fragment.tsx +163 -0
- package/src/components/Alert/Alert.module.scss +116 -0
- package/src/components/Alert/index.tsx +95 -0
- package/src/components/Avatar/Avatar.fragment.tsx +147 -0
- package/src/components/Avatar/Avatar.module.scss +136 -0
- package/src/components/Avatar/index.tsx +177 -0
- package/src/components/Badge/Badge.fragment.tsx +151 -0
- package/src/components/Badge/Badge.module.scss +87 -0
- package/src/components/Badge/index.tsx +55 -0
- package/src/components/Button/Button.fragment.tsx +159 -0
- package/src/components/Button/Button.module.scss +97 -0
- package/src/components/Button/index.tsx +51 -0
- package/src/components/Card/Card.fragment.tsx +156 -0
- package/src/components/Card/Card.module.scss +86 -0
- package/src/components/Card/index.tsx +79 -0
- package/src/components/Checkbox/Checkbox.fragment.tsx +166 -0
- package/src/components/Checkbox/Checkbox.module.scss +144 -0
- package/src/components/Checkbox/index.tsx +166 -0
- package/src/components/Dialog/Dialog.fragment.tsx +179 -0
- package/src/components/Dialog/Dialog.module.scss +158 -0
- package/src/components/Dialog/index.tsx +230 -0
- package/src/components/EmptyState/EmptyState.fragment.tsx +222 -0
- package/src/components/EmptyState/EmptyState.module.scss +120 -0
- package/src/components/EmptyState/index.tsx +80 -0
- package/src/components/Input/Input.fragment.tsx +174 -0
- package/src/components/Input/Input.module.scss +64 -0
- package/src/components/Input/index.tsx +76 -0
- package/src/components/Menu/Menu.fragment.tsx +168 -0
- package/src/components/Menu/Menu.module.scss +190 -0
- package/src/components/Menu/index.tsx +318 -0
- package/src/components/Popover/Popover.fragment.tsx +178 -0
- package/src/components/Popover/Popover.module.scss +165 -0
- package/src/components/Popover/index.tsx +229 -0
- package/src/components/Progress/Progress.fragment.tsx +142 -0
- package/src/components/Progress/Progress.module.scss +185 -0
- package/src/components/Progress/index.tsx +196 -0
- package/src/components/RadioGroup/RadioGroup.fragment.tsx +188 -0
- package/src/components/RadioGroup/RadioGroup.module.scss +155 -0
- package/src/components/RadioGroup/index.tsx +166 -0
- package/src/components/Select/Select.fragment.tsx +173 -0
- package/src/components/Select/Select.module.scss +187 -0
- package/src/components/Select/index.tsx +233 -0
- package/src/components/Separator/Separator.fragment.tsx +148 -0
- package/src/components/Separator/Separator.module.scss +92 -0
- package/src/components/Separator/index.tsx +89 -0
- package/src/components/Skeleton/Skeleton.fragment.tsx +147 -0
- package/src/components/Skeleton/Skeleton.module.scss +166 -0
- package/src/components/Skeleton/index.tsx +185 -0
- package/src/components/Table/Table.fragment.tsx +193 -0
- package/src/components/Table/Table.module.scss +152 -0
- package/src/components/Table/index.tsx +266 -0
- package/src/components/Tabs/Tabs.fragment.tsx +155 -0
- package/src/components/Tabs/Tabs.module.scss +142 -0
- package/src/components/Tabs/index.tsx +142 -0
- package/src/components/Textarea/Textarea.fragment.tsx +171 -0
- package/src/components/Textarea/Textarea.module.scss +89 -0
- package/src/components/Textarea/index.tsx +128 -0
- package/src/components/Toast/Toast.fragment.tsx +210 -0
- package/src/components/Toast/Toast.module.scss +227 -0
- package/src/components/Toast/index.tsx +315 -0
- package/src/components/Toggle/Toggle.fragment.tsx +174 -0
- package/src/components/Toggle/Toggle.module.scss +103 -0
- package/src/components/Toggle/index.tsx +80 -0
- package/src/components/Tooltip/Tooltip.fragment.tsx +158 -0
- package/src/components/Tooltip/Tooltip.module.scss +82 -0
- package/src/components/Tooltip/index.tsx +135 -0
- package/src/index.ts +151 -0
- package/src/scss.d.ts +4 -0
- package/src/styles/globals.scss +17 -0
- package/src/tokens/_mixins.scss +93 -0
- package/src/tokens/_variables.scss +276 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
// Positioner
|
|
5
|
+
.positioner {
|
|
6
|
+
z-index: 50;
|
|
7
|
+
outline: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Popup container
|
|
11
|
+
.popup {
|
|
12
|
+
@include surface-elevated;
|
|
13
|
+
@include text-base;
|
|
14
|
+
|
|
15
|
+
min-width: 12rem;
|
|
16
|
+
max-width: 24rem;
|
|
17
|
+
padding: var(--fui-space-4, $fui-space-4);
|
|
18
|
+
box-shadow: var(--fui-shadow-md, $fui-shadow-md);
|
|
19
|
+
|
|
20
|
+
// Animation
|
|
21
|
+
opacity: 0;
|
|
22
|
+
transform: scale(0.95) translateY(-4px);
|
|
23
|
+
transform-origin: var(--transform-origin);
|
|
24
|
+
transition:
|
|
25
|
+
opacity var(--fui-transition-fast, $fui-transition-fast),
|
|
26
|
+
transform var(--fui-transition-fast, $fui-transition-fast);
|
|
27
|
+
|
|
28
|
+
&[data-open] {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
transform: scale(1) translateY(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&[data-starting-style] {
|
|
34
|
+
opacity: 0;
|
|
35
|
+
transform: scale(0.95) translateY(-4px);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&[data-ending-style] {
|
|
39
|
+
opacity: 0;
|
|
40
|
+
transform: scale(0.95) translateY(4px);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Adjust animation direction based on side
|
|
44
|
+
&[data-side='top'] {
|
|
45
|
+
&[data-starting-style] {
|
|
46
|
+
transform: scale(0.95) translateY(4px);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&[data-ending-style] {
|
|
50
|
+
transform: scale(0.95) translateY(-4px);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&[data-side='left'],
|
|
55
|
+
&[data-side='right'] {
|
|
56
|
+
&[data-starting-style],
|
|
57
|
+
&[data-ending-style] {
|
|
58
|
+
transform: scale(0.95) translateY(0);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Size variants
|
|
64
|
+
.sm {
|
|
65
|
+
padding: var(--fui-space-3, $fui-space-3);
|
|
66
|
+
max-width: 16rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.md {
|
|
70
|
+
// Default styles
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.lg {
|
|
74
|
+
padding: var(--fui-space-5, $fui-space-5);
|
|
75
|
+
max-width: 32rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Title
|
|
79
|
+
.title {
|
|
80
|
+
margin: 0 0 var(--fui-space-1, $fui-space-1);
|
|
81
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
82
|
+
font-weight: var(--fui-font-weight-semibold, $fui-font-weight-semibold);
|
|
83
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
84
|
+
line-height: var(--fui-line-height-tight, $fui-line-height-tight);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Description
|
|
88
|
+
.description {
|
|
89
|
+
margin: 0;
|
|
90
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
91
|
+
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
92
|
+
line-height: var(--fui-line-height-normal, $fui-line-height-normal);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Close button
|
|
96
|
+
.close {
|
|
97
|
+
@include button-reset;
|
|
98
|
+
@include interactive-base;
|
|
99
|
+
|
|
100
|
+
position: absolute;
|
|
101
|
+
top: var(--fui-space-2, $fui-space-2);
|
|
102
|
+
right: var(--fui-space-2, $fui-space-2);
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
width: 1.5rem;
|
|
107
|
+
height: 1.5rem;
|
|
108
|
+
border-radius: var(--fui-radius-sm, $fui-radius-sm);
|
|
109
|
+
color: var(--fui-text-tertiary, $fui-text-tertiary);
|
|
110
|
+
|
|
111
|
+
&:hover {
|
|
112
|
+
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
113
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
svg {
|
|
117
|
+
width: 0.875rem;
|
|
118
|
+
height: 0.875rem;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Arrow
|
|
123
|
+
.arrow {
|
|
124
|
+
width: 10px;
|
|
125
|
+
height: 10px;
|
|
126
|
+
transform: rotate(45deg);
|
|
127
|
+
background-color: var(--fui-bg-elevated, $fui-bg-elevated);
|
|
128
|
+
border: 1px solid var(--fui-border, $fui-border);
|
|
129
|
+
|
|
130
|
+
&[data-side='top'] {
|
|
131
|
+
border-top: none;
|
|
132
|
+
border-left: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&[data-side='bottom'] {
|
|
136
|
+
border-bottom: none;
|
|
137
|
+
border-right: none;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&[data-side='left'] {
|
|
141
|
+
border-left: none;
|
|
142
|
+
border-bottom: none;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
&[data-side='right'] {
|
|
146
|
+
border-right: none;
|
|
147
|
+
border-top: none;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Body content area
|
|
152
|
+
.body {
|
|
153
|
+
margin-top: var(--fui-space-3, $fui-space-3);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Footer for actions
|
|
157
|
+
.footer {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: flex-end;
|
|
161
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
162
|
+
margin-top: var(--fui-space-4, $fui-space-4);
|
|
163
|
+
padding-top: var(--fui-space-3, $fui-space-3);
|
|
164
|
+
border-top: 1px solid var(--fui-border, $fui-border);
|
|
165
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Popover as BasePopover } from '@base-ui/react/popover';
|
|
3
|
+
import styles from './Popover.module.scss';
|
|
4
|
+
// Import globals to ensure CSS variables are defined
|
|
5
|
+
import '../../styles/globals.scss';
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// Types
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
export interface PopoverProps {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
open?: boolean;
|
|
14
|
+
defaultOpen?: boolean;
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
modal?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PopoverTriggerProps {
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
asChild?: boolean;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PopoverContentProps {
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
size?: 'sm' | 'md' | 'lg';
|
|
28
|
+
side?: 'top' | 'bottom' | 'left' | 'right';
|
|
29
|
+
align?: 'start' | 'center' | 'end';
|
|
30
|
+
sideOffset?: number;
|
|
31
|
+
arrow?: boolean;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PopoverTitleProps {
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PopoverDescriptionProps {
|
|
41
|
+
children: React.ReactNode;
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PopoverBodyProps {
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PopoverFooterProps {
|
|
51
|
+
children: React.ReactNode;
|
|
52
|
+
className?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PopoverCloseProps {
|
|
56
|
+
children?: React.ReactNode;
|
|
57
|
+
asChild?: boolean;
|
|
58
|
+
className?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================
|
|
62
|
+
// Icons
|
|
63
|
+
// ============================================
|
|
64
|
+
|
|
65
|
+
function CloseIcon() {
|
|
66
|
+
return (
|
|
67
|
+
<svg
|
|
68
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
69
|
+
width="14"
|
|
70
|
+
height="14"
|
|
71
|
+
viewBox="0 0 24 24"
|
|
72
|
+
fill="none"
|
|
73
|
+
stroke="currentColor"
|
|
74
|
+
strokeWidth="2"
|
|
75
|
+
strokeLinecap="round"
|
|
76
|
+
strokeLinejoin="round"
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
>
|
|
79
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
80
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
81
|
+
</svg>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================
|
|
86
|
+
// Components
|
|
87
|
+
// ============================================
|
|
88
|
+
|
|
89
|
+
function PopoverRoot({
|
|
90
|
+
children,
|
|
91
|
+
open,
|
|
92
|
+
defaultOpen,
|
|
93
|
+
onOpenChange,
|
|
94
|
+
modal = false,
|
|
95
|
+
}: PopoverProps) {
|
|
96
|
+
return (
|
|
97
|
+
<BasePopover.Root
|
|
98
|
+
open={open}
|
|
99
|
+
defaultOpen={defaultOpen}
|
|
100
|
+
onOpenChange={onOpenChange}
|
|
101
|
+
modal={modal}
|
|
102
|
+
>
|
|
103
|
+
{children}
|
|
104
|
+
</BasePopover.Root>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function PopoverTrigger({ children, asChild, className }: PopoverTriggerProps) {
|
|
109
|
+
if (asChild) {
|
|
110
|
+
return (
|
|
111
|
+
<BasePopover.Trigger className={className} render={children as React.ReactElement}>
|
|
112
|
+
{null}
|
|
113
|
+
</BasePopover.Trigger>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<BasePopover.Trigger className={className}>
|
|
119
|
+
{children}
|
|
120
|
+
</BasePopover.Trigger>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function PopoverContent({
|
|
125
|
+
children,
|
|
126
|
+
size = 'md',
|
|
127
|
+
side = 'bottom',
|
|
128
|
+
align = 'center',
|
|
129
|
+
sideOffset = 8,
|
|
130
|
+
arrow = false,
|
|
131
|
+
className,
|
|
132
|
+
}: PopoverContentProps) {
|
|
133
|
+
const popupClasses = [
|
|
134
|
+
styles.popup,
|
|
135
|
+
size !== 'md' && styles[size],
|
|
136
|
+
className,
|
|
137
|
+
].filter(Boolean).join(' ');
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<BasePopover.Portal>
|
|
141
|
+
<BasePopover.Positioner
|
|
142
|
+
side={side}
|
|
143
|
+
align={align}
|
|
144
|
+
sideOffset={sideOffset}
|
|
145
|
+
className={styles.positioner}
|
|
146
|
+
>
|
|
147
|
+
<BasePopover.Popup className={popupClasses}>
|
|
148
|
+
{children}
|
|
149
|
+
{arrow && <BasePopover.Arrow className={styles.arrow} />}
|
|
150
|
+
</BasePopover.Popup>
|
|
151
|
+
</BasePopover.Positioner>
|
|
152
|
+
</BasePopover.Portal>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function PopoverTitle({ children, className }: PopoverTitleProps) {
|
|
157
|
+
const classes = [styles.title, className].filter(Boolean).join(' ');
|
|
158
|
+
return <BasePopover.Title className={classes}>{children}</BasePopover.Title>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function PopoverDescription({ children, className }: PopoverDescriptionProps) {
|
|
162
|
+
const classes = [styles.description, className].filter(Boolean).join(' ');
|
|
163
|
+
return (
|
|
164
|
+
<BasePopover.Description className={classes}>
|
|
165
|
+
{children}
|
|
166
|
+
</BasePopover.Description>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function PopoverBody({ children, className }: PopoverBodyProps) {
|
|
171
|
+
const classes = [styles.body, className].filter(Boolean).join(' ');
|
|
172
|
+
return <div className={classes}>{children}</div>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function PopoverFooter({ children, className }: PopoverFooterProps) {
|
|
176
|
+
const classes = [styles.footer, className].filter(Boolean).join(' ');
|
|
177
|
+
return <div className={classes}>{children}</div>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function PopoverClose({ children, asChild, className }: PopoverCloseProps) {
|
|
181
|
+
// Default close button (X icon)
|
|
182
|
+
if (!children) {
|
|
183
|
+
return (
|
|
184
|
+
<BasePopover.Close className={[styles.close, className].filter(Boolean).join(' ')}>
|
|
185
|
+
<CloseIcon />
|
|
186
|
+
</BasePopover.Close>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (asChild) {
|
|
191
|
+
return (
|
|
192
|
+
<BasePopover.Close className={className} render={children as React.ReactElement}>
|
|
193
|
+
{null}
|
|
194
|
+
</BasePopover.Close>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<BasePopover.Close className={className}>
|
|
200
|
+
{children}
|
|
201
|
+
</BasePopover.Close>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ============================================
|
|
206
|
+
// Export compound component
|
|
207
|
+
// ============================================
|
|
208
|
+
|
|
209
|
+
export const Popover = Object.assign(PopoverRoot, {
|
|
210
|
+
Trigger: PopoverTrigger,
|
|
211
|
+
Content: PopoverContent,
|
|
212
|
+
Title: PopoverTitle,
|
|
213
|
+
Description: PopoverDescription,
|
|
214
|
+
Body: PopoverBody,
|
|
215
|
+
Footer: PopoverFooter,
|
|
216
|
+
Close: PopoverClose,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Re-export individual components
|
|
220
|
+
export {
|
|
221
|
+
PopoverRoot,
|
|
222
|
+
PopoverTrigger,
|
|
223
|
+
PopoverContent,
|
|
224
|
+
PopoverTitle,
|
|
225
|
+
PopoverDescription,
|
|
226
|
+
PopoverBody,
|
|
227
|
+
PopoverFooter,
|
|
228
|
+
PopoverClose,
|
|
229
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Progress, CircularProgress } from './index.js';
|
|
4
|
+
|
|
5
|
+
export default defineSegment({
|
|
6
|
+
component: Progress,
|
|
7
|
+
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'Progress',
|
|
10
|
+
description: 'Visual indicator of task completion or loading state. Available in linear and circular variants.',
|
|
11
|
+
category: 'feedback',
|
|
12
|
+
status: 'stable',
|
|
13
|
+
tags: ['progress', 'loading', 'indicator', 'percentage', 'status'],
|
|
14
|
+
since: '0.1.0',
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
usage: {
|
|
18
|
+
when: [
|
|
19
|
+
'Showing upload/download progress',
|
|
20
|
+
'Displaying task completion percentage',
|
|
21
|
+
'Form completion indicators',
|
|
22
|
+
'Loading states with known duration',
|
|
23
|
+
],
|
|
24
|
+
whenNot: [
|
|
25
|
+
'Unknown loading duration (use Spinner)',
|
|
26
|
+
'Step-based progress (use Stepper)',
|
|
27
|
+
'Status without percentage (use Badge)',
|
|
28
|
+
],
|
|
29
|
+
guidelines: [
|
|
30
|
+
'Use determinate progress when you know the completion percentage',
|
|
31
|
+
'Use indeterminate for unknown durations',
|
|
32
|
+
'Include a label for context when the purpose isnt obvious',
|
|
33
|
+
'Use appropriate color variants for success/warning/danger states',
|
|
34
|
+
],
|
|
35
|
+
accessibility: [
|
|
36
|
+
'Uses role="progressbar" with aria-valuenow',
|
|
37
|
+
'Label is associated with the progress bar',
|
|
38
|
+
'State changes are announced to screen readers',
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
props: {
|
|
43
|
+
value: {
|
|
44
|
+
type: 'number',
|
|
45
|
+
description: 'Current progress value (0-100). Null for indeterminate.',
|
|
46
|
+
},
|
|
47
|
+
size: {
|
|
48
|
+
type: 'enum',
|
|
49
|
+
description: 'Size of the progress bar',
|
|
50
|
+
values: ['sm', 'md', 'lg'],
|
|
51
|
+
default: 'md',
|
|
52
|
+
},
|
|
53
|
+
variant: {
|
|
54
|
+
type: 'enum',
|
|
55
|
+
description: 'Color variant',
|
|
56
|
+
values: ['default', 'success', 'warning', 'danger'],
|
|
57
|
+
default: 'default',
|
|
58
|
+
},
|
|
59
|
+
label: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Label text above the progress bar',
|
|
62
|
+
},
|
|
63
|
+
showValue: {
|
|
64
|
+
type: 'boolean',
|
|
65
|
+
description: 'Show percentage value',
|
|
66
|
+
default: 'false',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
relations: [
|
|
71
|
+
{ component: 'Badge', relationship: 'alternative', note: 'Use Badge for status without percentage' },
|
|
72
|
+
{ component: 'Alert', relationship: 'sibling', note: 'Use Alert for completion messages' },
|
|
73
|
+
],
|
|
74
|
+
|
|
75
|
+
contract: {
|
|
76
|
+
propsSummary: [
|
|
77
|
+
'value: number|null - progress percentage (null for indeterminate)',
|
|
78
|
+
'size: sm|md|lg - bar thickness',
|
|
79
|
+
'variant: default|success|warning|danger - color',
|
|
80
|
+
'label: string - descriptive label',
|
|
81
|
+
'showValue: boolean - display percentage',
|
|
82
|
+
],
|
|
83
|
+
scenarioTags: [
|
|
84
|
+
'feedback.progress',
|
|
85
|
+
'status.loading',
|
|
86
|
+
'display.percentage',
|
|
87
|
+
],
|
|
88
|
+
a11yRules: ['A11Y_PROGRESS_ROLE', 'A11Y_PROGRESS_VALUE'],
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
variants: [
|
|
92
|
+
{
|
|
93
|
+
name: 'Default',
|
|
94
|
+
description: 'Basic progress bar with percentage',
|
|
95
|
+
render: () => (
|
|
96
|
+
<Progress value={60} label="Uploading..." showValue />
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'Variants',
|
|
101
|
+
description: 'Different color variants for different states',
|
|
102
|
+
render: () => (
|
|
103
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
104
|
+
<Progress value={75} variant="default" label="Processing" showValue />
|
|
105
|
+
<Progress value={100} variant="success" label="Complete" showValue />
|
|
106
|
+
<Progress value={80} variant="warning" label="Almost full" showValue />
|
|
107
|
+
<Progress value={95} variant="danger" label="Storage critical" showValue />
|
|
108
|
+
</div>
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'Sizes',
|
|
113
|
+
description: 'Different progress bar sizes',
|
|
114
|
+
render: () => (
|
|
115
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
116
|
+
<Progress value={50} size="sm" label="Small" />
|
|
117
|
+
<Progress value={50} size="md" label="Medium" />
|
|
118
|
+
<Progress value={50} size="lg" label="Large" />
|
|
119
|
+
</div>
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Indeterminate',
|
|
124
|
+
description: 'Loading state with unknown duration',
|
|
125
|
+
render: () => (
|
|
126
|
+
<Progress value={null} label="Loading..." />
|
|
127
|
+
),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'Circular',
|
|
131
|
+
description: 'Circular progress indicator',
|
|
132
|
+
render: () => (
|
|
133
|
+
<div style={{ display: 'flex', gap: '24px', alignItems: 'center' }}>
|
|
134
|
+
<CircularProgress value={25} size="sm" />
|
|
135
|
+
<CircularProgress value={50} size="md" showValue />
|
|
136
|
+
<CircularProgress value={75} size="lg" showValue variant="success" />
|
|
137
|
+
<CircularProgress value={null} size="md" />
|
|
138
|
+
</div>
|
|
139
|
+
),
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
// Root container
|
|
5
|
+
.root {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: var(--fui-space-1, $fui-space-1);
|
|
9
|
+
width: 100%;
|
|
10
|
+
font-family: var(--fui-font-sans, $fui-font-sans);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Header with label and value
|
|
14
|
+
.header {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Label text
|
|
22
|
+
.label {
|
|
23
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
24
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
25
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Value display
|
|
29
|
+
.value {
|
|
30
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
31
|
+
color: var(--fui-text-secondary, $fui-text-secondary);
|
|
32
|
+
font-variant-numeric: tabular-nums;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Track (background bar)
|
|
36
|
+
.track {
|
|
37
|
+
position: relative;
|
|
38
|
+
width: 100%;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
|
|
41
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Size variants for track
|
|
45
|
+
.trackSm {
|
|
46
|
+
height: 4px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.trackMd {
|
|
50
|
+
height: 8px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.trackLg {
|
|
54
|
+
height: 12px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Indicator (filled portion)
|
|
58
|
+
.indicator {
|
|
59
|
+
height: 100%;
|
|
60
|
+
background-color: var(--fui-color-accent, $fui-color-accent);
|
|
61
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
62
|
+
transition: width var(--fui-transition-normal, $fui-transition-normal);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Color variants
|
|
66
|
+
.indicatorSuccess {
|
|
67
|
+
background-color: var(--fui-color-success, $fui-color-success);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.indicatorWarning {
|
|
71
|
+
background-color: var(--fui-color-warning, $fui-color-warning);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.indicatorDanger {
|
|
75
|
+
background-color: var(--fui-color-danger, $fui-color-danger);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Indeterminate animation
|
|
79
|
+
.indicatorIndeterminate {
|
|
80
|
+
width: 50% !important;
|
|
81
|
+
animation: indeterminate 1.5s ease-in-out infinite;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@keyframes indeterminate {
|
|
85
|
+
0% {
|
|
86
|
+
transform: translateX(-100%);
|
|
87
|
+
}
|
|
88
|
+
50% {
|
|
89
|
+
transform: translateX(100%);
|
|
90
|
+
}
|
|
91
|
+
100% {
|
|
92
|
+
transform: translateX(-100%);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Complete state
|
|
97
|
+
.root[data-complete] .indicator {
|
|
98
|
+
background-color: var(--fui-color-success, $fui-color-success);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Circular progress variant
|
|
102
|
+
.circular {
|
|
103
|
+
position: relative;
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.circularSvg {
|
|
110
|
+
transform: rotate(-90deg);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.circularTrack {
|
|
114
|
+
stroke: var(--fui-bg-tertiary, $fui-bg-tertiary);
|
|
115
|
+
fill: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.circularIndicator {
|
|
119
|
+
stroke: var(--fui-color-accent, $fui-color-accent);
|
|
120
|
+
fill: none;
|
|
121
|
+
stroke-linecap: round;
|
|
122
|
+
transition: stroke-dashoffset var(--fui-transition-normal, $fui-transition-normal);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.circularIndicatorSuccess {
|
|
126
|
+
stroke: var(--fui-color-success, $fui-color-success);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.circularIndicatorWarning {
|
|
130
|
+
stroke: var(--fui-color-warning, $fui-color-warning);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.circularIndicatorDanger {
|
|
134
|
+
stroke: var(--fui-color-danger, $fui-color-danger);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.circularIndicatorIndeterminate {
|
|
138
|
+
animation: circularIndeterminate 1.5s ease-in-out infinite;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@keyframes circularIndeterminate {
|
|
142
|
+
0% {
|
|
143
|
+
stroke-dashoffset: 280;
|
|
144
|
+
transform: rotate(0deg);
|
|
145
|
+
}
|
|
146
|
+
50% {
|
|
147
|
+
stroke-dashoffset: 70;
|
|
148
|
+
}
|
|
149
|
+
100% {
|
|
150
|
+
stroke-dashoffset: 280;
|
|
151
|
+
transform: rotate(360deg);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.circularValue {
|
|
156
|
+
position: absolute;
|
|
157
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
158
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
159
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
160
|
+
font-variant-numeric: tabular-nums;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Size variants for circular
|
|
164
|
+
.circularSm {
|
|
165
|
+
width: 32px;
|
|
166
|
+
height: 32px;
|
|
167
|
+
|
|
168
|
+
.circularValue {
|
|
169
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.circularMd {
|
|
174
|
+
width: 48px;
|
|
175
|
+
height: 48px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.circularLg {
|
|
179
|
+
width: 64px;
|
|
180
|
+
height: 64px;
|
|
181
|
+
|
|
182
|
+
.circularValue {
|
|
183
|
+
font-size: var(--fui-font-size-base, $fui-font-size-base);
|
|
184
|
+
}
|
|
185
|
+
}
|