@xjur-ui/react 2.1.1 → 2.2.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/CHANGELOG.md +12 -0
- package/dist/components/steps/index.js +1 -1
- package/package.json +7 -4
- package/.turbo/turbo-build.log +0 -419
- package/eslint.config.js +0 -10
- package/postcss.config.js +0 -5
- package/prettier.config.js +0 -7
- package/src/components/avatar-group.tsx +0 -76
- package/src/components/avatar.tsx +0 -54
- package/src/components/badge.tsx +0 -40
- package/src/components/breadcrumb.tsx +0 -119
- package/src/components/chip.tsx +0 -117
- package/src/components/collapsible.tsx +0 -35
- package/src/components/controls/button.tsx +0 -107
- package/src/components/controls/calendar.tsx +0 -124
- package/src/components/controls/checkbox.tsx +0 -57
- package/src/components/controls/context/field.context.ts +0 -27
- package/src/components/controls/date-picker.tsx +0 -199
- package/src/components/controls/floating-label.tsx +0 -60
- package/src/components/controls/input.tsx +0 -237
- package/src/components/controls/otp-input.tsx +0 -45
- package/src/components/controls/radio.tsx +0 -74
- package/src/components/controls/rich-editor.tsx +0 -372
- package/src/components/controls/search-input.tsx +0 -154
- package/src/components/controls/search-selector.tsx +0 -98
- package/src/components/controls/select.tsx +0 -274
- package/src/components/controls/switch.tsx +0 -31
- package/src/components/custom/pessoa/index.ts +0 -9
- package/src/components/custom/pessoa/pessoa-add-button.tsx +0 -35
- package/src/components/custom/pessoa/pessoa-body.tsx +0 -22
- package/src/components/custom/pessoa/pessoa-close-button.tsx +0 -13
- package/src/components/custom/pessoa/pessoa-content.tsx +0 -25
- package/src/components/custom/pessoa/pessoa-footer.tsx +0 -15
- package/src/components/custom/pessoa/pessoa-header.tsx +0 -35
- package/src/components/custom/pessoa/pessoa-trigger.tsx +0 -25
- package/src/components/custom/pessoa/pessoa.context.tsx +0 -73
- package/src/components/custom/pessoa/pessoa.tsx +0 -35
- package/src/components/dialog.tsx +0 -242
- package/src/components/divider.tsx +0 -24
- package/src/components/drag-and-drop/drag-handle.tsx +0 -46
- package/src/components/drag-and-drop/drag-list-item.tsx +0 -91
- package/src/components/drag-and-drop/drag-list.tsx +0 -47
- package/src/components/drag-and-drop/drag-root.tsx +0 -30
- package/src/components/drag-and-drop/index.ts +0 -39
- package/src/components/drag-and-drop/types/drag-item.ts +0 -7
- package/src/components/dropdown-menu.tsx +0 -101
- package/src/components/full-calendar/calendar-add-event.tsx +0 -15
- package/src/components/full-calendar/calendar-body.tsx +0 -12
- package/src/components/full-calendar/calendar-cell.tsx +0 -120
- package/src/components/full-calendar/calendar-day-view.tsx +0 -76
- package/src/components/full-calendar/calendar-event.tsx +0 -178
- package/src/components/full-calendar/calendar-header.tsx +0 -14
- package/src/components/full-calendar/calendar-month-view.tsx +0 -69
- package/src/components/full-calendar/calendar-more-events-dropdown.tsx +0 -39
- package/src/components/full-calendar/calendar-navigator.tsx +0 -86
- package/src/components/full-calendar/calendar-root.tsx +0 -165
- package/src/components/full-calendar/calendar-today-button.tsx +0 -21
- package/src/components/full-calendar/calendar-toggle-view.tsx +0 -61
- package/src/components/full-calendar/calendar-view-renderer.tsx +0 -19
- package/src/components/full-calendar/calendar-week-day.tsx +0 -49
- package/src/components/full-calendar/calendar-week-view.tsx +0 -88
- package/src/components/full-calendar/helpers/calendar.ts +0 -158
- package/src/components/full-calendar/index.ts +0 -16
- package/src/components/full-calendar/types/calendar-cell.ts +0 -4
- package/src/components/full-calendar/types/calendar-event.ts +0 -7
- package/src/components/full-calendar/types/calendar-view.ts +0 -1
- package/src/components/full-calendar/types/calendar-week-hour.ts +0 -4
- package/src/components/horizontal-scroll.tsx +0 -159
- package/src/components/icon.tsx +0 -29
- package/src/components/list.tsx +0 -111
- package/src/components/logo.tsx +0 -44
- package/src/components/selection-bar.tsx +0 -273
- package/src/components/sheet.tsx +0 -150
- package/src/components/skeleton.tsx +0 -12
- package/src/components/status.tsx +0 -43
- package/src/components/steps/index.ts +0 -6
- package/src/components/steps/step.tsx +0 -23
- package/src/components/steps/steps-action.tsx +0 -65
- package/src/components/steps/steps-content.tsx +0 -12
- package/src/components/steps/steps-context.tsx +0 -121
- package/src/components/steps/steps-navigation.tsx +0 -80
- package/src/components/steps/steps-root.tsx +0 -9
- package/src/components/table/index.ts +0 -8
- package/src/components/table/table-body.tsx +0 -17
- package/src/components/table/table-cell.tsx +0 -14
- package/src/components/table/table-footer.tsx +0 -17
- package/src/components/table/table-head.tsx +0 -31
- package/src/components/table/table-header.tsx +0 -17
- package/src/components/table/table-row.tsx +0 -21
- package/src/components/table/table-sort.tsx +0 -34
- package/src/components/table/table.tsx +0 -16
- package/src/components/tabs.tsx +0 -72
- package/src/components/tag.tsx +0 -99
- package/src/components/tooltip.tsx +0 -63
- package/src/components/typography.tsx +0 -85
- package/src/components/upload.tsx +0 -647
- package/src/hooks/index.ts +0 -1
- package/src/hooks/use-os.ts +0 -32
- package/src/index.css +0 -5
- package/src/resources/icons.ts +0 -154
- package/src/resources/input-mask.ts +0 -91
- package/src/resources/logo/horizontal.tsx +0 -70
- package/src/resources/logo/icon.tsx +0 -54
- package/src/resources/logo/index.tsx +0 -33
- package/src/resources/logo/vertical.tsx +0 -70
- package/src/types.d.ts +0 -4
- package/tsconfig.json +0 -4
- package/tsup.config.ts +0 -30
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import type { ComponentProps, ReactNode } from 'react'
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
3
|
-
import { cx } from '@xjur-ui/util'
|
|
4
|
-
import { Button } from './controls/button'
|
|
5
|
-
import { Icon } from './icon'
|
|
6
|
-
|
|
7
|
-
export interface HorizontalScrollProps extends ComponentProps<'div'> {
|
|
8
|
-
children: ReactNode
|
|
9
|
-
gap?: 'small' | 'medium' | 'large'
|
|
10
|
-
scrollAmount?: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function HorizontalScroll({
|
|
14
|
-
children,
|
|
15
|
-
className,
|
|
16
|
-
gap = 'small',
|
|
17
|
-
scrollAmount = 200,
|
|
18
|
-
...props
|
|
19
|
-
}: HorizontalScrollProps) {
|
|
20
|
-
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
|
21
|
-
const [canScrollLeft, setCanScrollLeft] = useState(false)
|
|
22
|
-
const [canScrollRight, setCanScrollRight] = useState(false)
|
|
23
|
-
const [isScrolling, setIsScrolling] = useState(false)
|
|
24
|
-
|
|
25
|
-
const checkScrollable = useCallback(() => {
|
|
26
|
-
const container = scrollContainerRef.current
|
|
27
|
-
if (!container) return
|
|
28
|
-
|
|
29
|
-
const { scrollLeft, scrollWidth, clientWidth } = container
|
|
30
|
-
setCanScrollLeft(scrollLeft > 0)
|
|
31
|
-
setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 1)
|
|
32
|
-
}, [])
|
|
33
|
-
|
|
34
|
-
const handleScrollLeft = useCallback(() => {
|
|
35
|
-
const container = scrollContainerRef.current
|
|
36
|
-
if (!container) return
|
|
37
|
-
|
|
38
|
-
setIsScrolling(true)
|
|
39
|
-
container.scrollTo({
|
|
40
|
-
left: container.scrollLeft - scrollAmount,
|
|
41
|
-
behavior: 'smooth'
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
setTimeout(() => setIsScrolling(false), 300)
|
|
45
|
-
}, [scrollAmount])
|
|
46
|
-
|
|
47
|
-
const handleScrollRight = useCallback(() => {
|
|
48
|
-
const container = scrollContainerRef.current
|
|
49
|
-
if (!container) return
|
|
50
|
-
|
|
51
|
-
setIsScrolling(true)
|
|
52
|
-
container.scrollTo({
|
|
53
|
-
left: container.scrollLeft + scrollAmount,
|
|
54
|
-
behavior: 'smooth'
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
setTimeout(() => setIsScrolling(false), 300)
|
|
58
|
-
}, [scrollAmount])
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
const container = scrollContainerRef.current
|
|
62
|
-
if (!container) return
|
|
63
|
-
|
|
64
|
-
const handleScroll = () => {
|
|
65
|
-
checkScrollable()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const handleResize = () => {
|
|
69
|
-
checkScrollable()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
checkScrollable()
|
|
73
|
-
container.addEventListener('scroll', handleScroll, { passive: true })
|
|
74
|
-
window.addEventListener('resize', handleResize)
|
|
75
|
-
|
|
76
|
-
let resizeObserver: ResizeObserver | null = null
|
|
77
|
-
if (typeof ResizeObserver !== 'undefined') {
|
|
78
|
-
resizeObserver = new ResizeObserver(() => {
|
|
79
|
-
checkScrollable()
|
|
80
|
-
})
|
|
81
|
-
resizeObserver.observe(container)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return () => {
|
|
85
|
-
container.removeEventListener('scroll', handleScroll)
|
|
86
|
-
window.removeEventListener('resize', handleResize)
|
|
87
|
-
if (resizeObserver) {
|
|
88
|
-
resizeObserver.disconnect()
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}, [checkScrollable])
|
|
92
|
-
|
|
93
|
-
const gapClass = {
|
|
94
|
-
small: 'gap-small',
|
|
95
|
-
medium: 'gap-medium',
|
|
96
|
-
large: 'gap-large'
|
|
97
|
-
}[gap]
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div className={cx('relative isolate', className)} {...props}>
|
|
101
|
-
{/* Left Arrow */}
|
|
102
|
-
{canScrollLeft && (
|
|
103
|
-
<div
|
|
104
|
-
className="absolute top-1/2 left-0 z-[3] flex h-full w-[80px] -translate-y-1/2 items-center"
|
|
105
|
-
style={{
|
|
106
|
-
background: `linear-gradient(to right, white 40%, transparent)`
|
|
107
|
-
}}
|
|
108
|
-
>
|
|
109
|
-
<Button
|
|
110
|
-
appearance="ghost"
|
|
111
|
-
aria-label="Scroll left"
|
|
112
|
-
className="ml-0"
|
|
113
|
-
disabled={isScrolling}
|
|
114
|
-
size="icon_md"
|
|
115
|
-
variant="secondary"
|
|
116
|
-
onClick={handleScrollLeft}
|
|
117
|
-
>
|
|
118
|
-
<Icon name="chevron_left" />
|
|
119
|
-
</Button>
|
|
120
|
-
</div>
|
|
121
|
-
)}
|
|
122
|
-
|
|
123
|
-
{/* Right Arrow */}
|
|
124
|
-
{canScrollRight && (
|
|
125
|
-
<div
|
|
126
|
-
className="absolute top-1/2 right-0 z-[2] flex h-full w-[80px] -translate-y-1/2 items-center justify-end"
|
|
127
|
-
style={{
|
|
128
|
-
background: `linear-gradient(to left, white 40%, transparent)`
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
<Button
|
|
132
|
-
appearance="ghost"
|
|
133
|
-
aria-label="Scroll right"
|
|
134
|
-
className="mr-0"
|
|
135
|
-
disabled={isScrolling}
|
|
136
|
-
size="icon_md"
|
|
137
|
-
variant="secondary"
|
|
138
|
-
onClick={handleScrollRight}
|
|
139
|
-
>
|
|
140
|
-
<Icon name="chevron_right" />
|
|
141
|
-
</Button>
|
|
142
|
-
</div>
|
|
143
|
-
)}
|
|
144
|
-
|
|
145
|
-
{/* Scrollable Content */}
|
|
146
|
-
<div
|
|
147
|
-
className={cx(
|
|
148
|
-
'scrollbar-hide relative z-1 flex overflow-x-auto overflow-y-visible',
|
|
149
|
-
'min-w-0 [&>*]:flex-none [&>*]:whitespace-nowrap',
|
|
150
|
-
gapClass,
|
|
151
|
-
className
|
|
152
|
-
)}
|
|
153
|
-
ref={scrollContainerRef}
|
|
154
|
-
>
|
|
155
|
-
{children}
|
|
156
|
-
</div>
|
|
157
|
-
</div>
|
|
158
|
-
)
|
|
159
|
-
}
|
package/src/components/icon.tsx
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ComponentProps } from 'react'
|
|
2
|
-
import { cx } from '@xjur-ui/util'
|
|
3
|
-
import type { IconType } from '../resources/icons'
|
|
4
|
-
|
|
5
|
-
export type IconProps = {
|
|
6
|
-
name: IconType
|
|
7
|
-
filled?: boolean
|
|
8
|
-
} & ComponentProps<'span'>
|
|
9
|
-
|
|
10
|
-
export function Icon({ className, filled = false, name, ...props }: IconProps) {
|
|
11
|
-
return (
|
|
12
|
-
<span
|
|
13
|
-
{...props}
|
|
14
|
-
className={cx(
|
|
15
|
-
'material-symbols-outlined',
|
|
16
|
-
'text-size-body-lg select-none',
|
|
17
|
-
className
|
|
18
|
-
)}
|
|
19
|
-
style={{
|
|
20
|
-
fontVariationSettings: `'FILL' ${filled ? 1 : 0}, 'wght' 600, 'GRAD' 0, 'opsz' 20`,
|
|
21
|
-
...props.style
|
|
22
|
-
}}
|
|
23
|
-
>
|
|
24
|
-
{name}
|
|
25
|
-
</span>
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export { icons, type IconType } from '../resources/icons'
|
package/src/components/list.tsx
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { Slot } from '@radix-ui/react-slot'
|
|
2
|
-
import { cx, cva, type ClassVariantProps } from '@xjur-ui/util'
|
|
3
|
-
import type { ComponentProps } from 'react'
|
|
4
|
-
import { type TypographyProps, Typography } from './typography'
|
|
5
|
-
|
|
6
|
-
export function ListRoot({
|
|
7
|
-
className,
|
|
8
|
-
asChild,
|
|
9
|
-
...props
|
|
10
|
-
}: ComponentProps<'div'> & {
|
|
11
|
-
asChild?: boolean
|
|
12
|
-
}) {
|
|
13
|
-
const Comp = asChild ? Slot : 'div'
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<Comp
|
|
17
|
-
{...props}
|
|
18
|
-
className={cx(
|
|
19
|
-
className,
|
|
20
|
-
'rounded-small scrollbar-hide bg-layer-2-idle border-border-layer-2 shadow-elevation-3 z-1000 max-h-[400px] list-none overflow-y-auto border'
|
|
21
|
-
)}
|
|
22
|
-
/>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function ListHeader({
|
|
27
|
-
className,
|
|
28
|
-
asChild,
|
|
29
|
-
...props
|
|
30
|
-
}: ComponentProps<'div'> & {
|
|
31
|
-
asChild?: boolean
|
|
32
|
-
}) {
|
|
33
|
-
const Comp = asChild ? Slot : 'div'
|
|
34
|
-
return <Comp {...props} className={cx(className, 'px-small pt-small-1x')} />
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function ListHeaderTitle({
|
|
38
|
-
variant = 'label',
|
|
39
|
-
size = 'sm',
|
|
40
|
-
weight = 'medium',
|
|
41
|
-
className,
|
|
42
|
-
...props
|
|
43
|
-
}: TypographyProps) {
|
|
44
|
-
return (
|
|
45
|
-
<Typography
|
|
46
|
-
className={cx(className, 'text-neutral-placeholder')}
|
|
47
|
-
size={size}
|
|
48
|
-
variant={variant}
|
|
49
|
-
weight={weight}
|
|
50
|
-
{...props}
|
|
51
|
-
/>
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function ListContent({
|
|
56
|
-
className,
|
|
57
|
-
asChild,
|
|
58
|
-
...props
|
|
59
|
-
}: ComponentProps<'ul'> & {
|
|
60
|
-
asChild?: boolean
|
|
61
|
-
}) {
|
|
62
|
-
const Comp = asChild ? Slot : 'ul'
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<Comp
|
|
66
|
-
{...props}
|
|
67
|
-
className={cx('gap-small-1x p-small flex flex-col', className)}
|
|
68
|
-
/>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const variants = cva(
|
|
73
|
-
[
|
|
74
|
-
'py-medium px-small flex items-center gap-small rounded-small-1x',
|
|
75
|
-
'bg-layer-2-idle text-neutral-dark',
|
|
76
|
-
'hover:bg-layer-2-hover transition-colors duration-100 ease-out',
|
|
77
|
-
'active:bg-layer-2-pressed'
|
|
78
|
-
],
|
|
79
|
-
{
|
|
80
|
-
variants: {
|
|
81
|
-
size: {
|
|
82
|
-
md: 'h-large-4x',
|
|
83
|
-
lg: 'h-large-7x'
|
|
84
|
-
},
|
|
85
|
-
isActive: {
|
|
86
|
-
true: 'bg-primary-alt-active text-primary-label-active hover:bg-primary-alt-hover active:bg-primary-alt-pressed'
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
defaultVariants: {
|
|
90
|
-
size: 'md',
|
|
91
|
-
isActive: false
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
export function ListItem({
|
|
97
|
-
className,
|
|
98
|
-
size,
|
|
99
|
-
isActive,
|
|
100
|
-
asChild,
|
|
101
|
-
...props
|
|
102
|
-
}: ComponentProps<'li'> &
|
|
103
|
-
ClassVariantProps<typeof variants> & {
|
|
104
|
-
asChild?: boolean
|
|
105
|
-
}) {
|
|
106
|
-
const Comp = asChild ? Slot : 'li'
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<Comp {...props} className={cx(variants({ size, isActive }), className)} />
|
|
110
|
-
)
|
|
111
|
-
}
|
package/src/components/logo.tsx
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { memo, lazy, Suspense } from 'react'
|
|
2
|
-
|
|
3
|
-
export interface LogoProps {
|
|
4
|
-
mode?: 'horizontal' | 'vertical' | 'icon'
|
|
5
|
-
variant?: 'colorful' | 'orange' | 'purple' | 'white' | 'gray'
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// Lazy load logo components to reduce initial bundle size
|
|
9
|
-
const LazyLogos = lazy(() => import('../resources/logo'))
|
|
10
|
-
|
|
11
|
-
function LogoFallback({ mode }: { mode?: string }) {
|
|
12
|
-
const dimensions =
|
|
13
|
-
mode === 'icon' ? { width: 24, height: 24 } : { width: 95, height: 26 }
|
|
14
|
-
return (
|
|
15
|
-
<div
|
|
16
|
-
aria-label="Loading logo"
|
|
17
|
-
className="animate-pulse rounded bg-gray-200"
|
|
18
|
-
style={dimensions}
|
|
19
|
-
/>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const Logo = memo(
|
|
24
|
-
({ mode = 'horizontal', variant = 'colorful' }: LogoProps) => {
|
|
25
|
-
const colorToHex = {
|
|
26
|
-
orange: '#D68600',
|
|
27
|
-
purple: '#66127D',
|
|
28
|
-
white: '#FFFFFF',
|
|
29
|
-
gray: '#6B7280'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<Suspense fallback={<LogoFallback mode={mode} />}>
|
|
34
|
-
<LazyLogos
|
|
35
|
-
color={variant !== 'colorful' ? colorToHex[variant] : undefined}
|
|
36
|
-
mode={mode}
|
|
37
|
-
variant={variant}
|
|
38
|
-
/>
|
|
39
|
-
</Suspense>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
Logo.displayName = 'Logo'
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import type { ComponentProps, ReactNode } from 'react'
|
|
2
|
-
import { createContext, useContext, useState, useCallback } from 'react'
|
|
3
|
-
import { cva, cx, type ClassVariantProps } from '@xjur-ui/util'
|
|
4
|
-
import { Icon, type IconType } from './icon'
|
|
5
|
-
import { Button } from './controls/button'
|
|
6
|
-
|
|
7
|
-
const selectionBarVariants = cva(
|
|
8
|
-
[
|
|
9
|
-
'flex items-center gap-4 rounded-lg px-4 py-3 shadow-lg transition-colors duration-200 ease-in-out',
|
|
10
|
-
'fixed bottom-6 left-1/2 -translate-x-1/2 transform z-50',
|
|
11
|
-
'backdrop-blur-sm bg-white/95 border border-border-layer-2'
|
|
12
|
-
],
|
|
13
|
-
{
|
|
14
|
-
variants: {
|
|
15
|
-
size: {
|
|
16
|
-
sm: 'px-3 py-2 text-sm',
|
|
17
|
-
md: 'px-4 py-3 text-base',
|
|
18
|
-
lg: 'px-6 py-4 text-lg'
|
|
19
|
-
},
|
|
20
|
-
variant: {
|
|
21
|
-
primary:
|
|
22
|
-
'bg-primary-idle text-neutral-label hover:bg-primary-hover rounded-circle active:bg-primary-pressed',
|
|
23
|
-
secondary:
|
|
24
|
-
'bg-secondary-idle text-white border-secondary-idle hover:bg-secondary-hover rounded-circle',
|
|
25
|
-
success:
|
|
26
|
-
'bg-success-idle text-white border-success-idle hover:bg-success-hover rounded-circle',
|
|
27
|
-
warning:
|
|
28
|
-
'bg-warning-idle text-white border-warning-idle hover:bg-warning-hover rounded-circle',
|
|
29
|
-
destructive:
|
|
30
|
-
'bg-destructive-idle text-white border-destructive-idle hover:bg-destructive-hover rounded-circle',
|
|
31
|
-
neutral:
|
|
32
|
-
'bg-neutral-dark text-white border-neutral-dark hover:bg-neutral-hover rounded-circle',
|
|
33
|
-
outline:
|
|
34
|
-
'bg-white text-neutral-dark border-border-layer-2 hover:bg-layer-2-hover rounded-circle'
|
|
35
|
-
},
|
|
36
|
-
position: {
|
|
37
|
-
fixed: 'fixed bottom-6 left-1/2 -translate-x-1/2 transform',
|
|
38
|
-
absolute: 'absolute bottom-6 left-1/2 -translate-x-1/2 transform',
|
|
39
|
-
relative: 'relative',
|
|
40
|
-
sticky: 'sticky bottom-6'
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
defaultVariants: {
|
|
44
|
-
size: 'md',
|
|
45
|
-
variant: 'primary',
|
|
46
|
-
position: 'fixed'
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
export interface SelectionAction {
|
|
52
|
-
id: string
|
|
53
|
-
label?: string
|
|
54
|
-
icon: IconType
|
|
55
|
-
onClick: () => void
|
|
56
|
-
variant?: 'primary' | 'secondary' | 'destructive' | 'tertiary' | 'outlined'
|
|
57
|
-
disabled?: boolean
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface SelectionBarProps
|
|
61
|
-
extends Omit<ComponentProps<'div'>, 'onClose'>,
|
|
62
|
-
ClassVariantProps<typeof selectionBarVariants> {
|
|
63
|
-
selectedCount: number
|
|
64
|
-
onClose: () => void
|
|
65
|
-
actions?: SelectionAction[]
|
|
66
|
-
showCloseButton?: boolean
|
|
67
|
-
closeButtonText?: string
|
|
68
|
-
selectedText?: string
|
|
69
|
-
className?: string
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function SelectionBar({
|
|
73
|
-
selectedCount,
|
|
74
|
-
onClose,
|
|
75
|
-
actions = [],
|
|
76
|
-
showCloseButton = true,
|
|
77
|
-
selectedText,
|
|
78
|
-
size = 'md',
|
|
79
|
-
variant = 'primary',
|
|
80
|
-
position = 'fixed',
|
|
81
|
-
className,
|
|
82
|
-
...props
|
|
83
|
-
}: SelectionBarProps) {
|
|
84
|
-
if (selectedCount === 0) return null
|
|
85
|
-
|
|
86
|
-
const getSelectedText = () => {
|
|
87
|
-
if (selectedText) return selectedText
|
|
88
|
-
return selectedCount === 1
|
|
89
|
-
? '1 item selecionado'
|
|
90
|
-
: `${selectedCount} itens selecionados`
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const getIconSize = () => {
|
|
94
|
-
switch (size) {
|
|
95
|
-
case 'sm':
|
|
96
|
-
return 'text-sm'
|
|
97
|
-
case 'md':
|
|
98
|
-
return 'text-base'
|
|
99
|
-
case 'lg':
|
|
100
|
-
return 'text-lg'
|
|
101
|
-
default:
|
|
102
|
-
return 'text-base'
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<div
|
|
108
|
-
className={cx(
|
|
109
|
-
selectionBarVariants({ size, variant, position }),
|
|
110
|
-
className
|
|
111
|
-
)}
|
|
112
|
-
{...props}
|
|
113
|
-
>
|
|
114
|
-
{showCloseButton && (
|
|
115
|
-
<Button
|
|
116
|
-
appearance="ghost"
|
|
117
|
-
className="rounded-full text-current"
|
|
118
|
-
size={size === 'lg' ? 'md' : 'sm'}
|
|
119
|
-
variant="secondary"
|
|
120
|
-
onClick={() => onClose()}
|
|
121
|
-
>
|
|
122
|
-
<Icon className={getIconSize()} name="close" />
|
|
123
|
-
</Button>
|
|
124
|
-
)}
|
|
125
|
-
|
|
126
|
-
<span className="font-medium whitespace-nowrap">{getSelectedText()}</span>
|
|
127
|
-
|
|
128
|
-
{actions.map(action => (
|
|
129
|
-
<Button
|
|
130
|
-
appearance="ghost"
|
|
131
|
-
className="rounded-full text-current"
|
|
132
|
-
disabled={action.disabled}
|
|
133
|
-
key={action.id}
|
|
134
|
-
size={size === 'lg' ? 'md' : 'sm'}
|
|
135
|
-
variant={action.variant || 'secondary'}
|
|
136
|
-
onClick={() => action.onClick()}
|
|
137
|
-
>
|
|
138
|
-
<Icon className={getIconSize()} name={action.icon} />
|
|
139
|
-
{action.label && <span className="ml-2">{action.label}</span>}
|
|
140
|
-
</Button>
|
|
141
|
-
))}
|
|
142
|
-
</div>
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
interface SelectionBarContextType {
|
|
147
|
-
selectedItems: Set<string>
|
|
148
|
-
toggleSelection: (id: string) => void
|
|
149
|
-
clearSelection: () => void
|
|
150
|
-
selectAll: (ids: string[]) => void
|
|
151
|
-
isSelected: (id: string) => boolean
|
|
152
|
-
selectedCount: number
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const SelectionBarContext = createContext<SelectionBarContextType | null>(null)
|
|
156
|
-
|
|
157
|
-
interface SelectionBarProviderProps {
|
|
158
|
-
children: ReactNode
|
|
159
|
-
onSelectionChange?: (selectedIds: string[]) => void
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function SelectionBarProvider({
|
|
163
|
-
children,
|
|
164
|
-
onSelectionChange
|
|
165
|
-
}: SelectionBarProviderProps) {
|
|
166
|
-
const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set())
|
|
167
|
-
|
|
168
|
-
const toggleSelection = useCallback(
|
|
169
|
-
(id: string) => {
|
|
170
|
-
setSelectedItems(prev => {
|
|
171
|
-
const newSet = new Set(prev)
|
|
172
|
-
if (newSet.has(id)) {
|
|
173
|
-
newSet.delete(id)
|
|
174
|
-
} else {
|
|
175
|
-
newSet.add(id)
|
|
176
|
-
}
|
|
177
|
-
onSelectionChange?.(Array.from(newSet))
|
|
178
|
-
return newSet
|
|
179
|
-
})
|
|
180
|
-
},
|
|
181
|
-
[onSelectionChange]
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
const clearSelection = useCallback(() => {
|
|
185
|
-
setSelectedItems(new Set())
|
|
186
|
-
onSelectionChange?.([])
|
|
187
|
-
}, [onSelectionChange])
|
|
188
|
-
|
|
189
|
-
const selectAll = useCallback(
|
|
190
|
-
(ids: string[]) => {
|
|
191
|
-
setSelectedItems(new Set(ids))
|
|
192
|
-
onSelectionChange?.(ids)
|
|
193
|
-
},
|
|
194
|
-
[onSelectionChange]
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
const isSelected = useCallback(
|
|
198
|
-
(id: string) => {
|
|
199
|
-
return selectedItems.has(id)
|
|
200
|
-
},
|
|
201
|
-
[selectedItems]
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
const value: SelectionBarContextType = {
|
|
205
|
-
selectedItems,
|
|
206
|
-
toggleSelection,
|
|
207
|
-
clearSelection,
|
|
208
|
-
selectAll,
|
|
209
|
-
isSelected,
|
|
210
|
-
selectedCount: selectedItems.size
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return (
|
|
214
|
-
<SelectionBarContext.Provider value={value}>
|
|
215
|
-
{children}
|
|
216
|
-
</SelectionBarContext.Provider>
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function useSelectionBar() {
|
|
221
|
-
const context = useContext(SelectionBarContext)
|
|
222
|
-
if (!context) {
|
|
223
|
-
throw new Error(
|
|
224
|
-
'useSelectionBar must be used within a SelectionBarProvider'
|
|
225
|
-
)
|
|
226
|
-
}
|
|
227
|
-
return context
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
interface SelectionBarContainerProps
|
|
231
|
-
extends Omit<SelectionBarProps, 'selectedCount' | 'onClose'> {
|
|
232
|
-
showCloseButton?: boolean
|
|
233
|
-
closeButtonText?: string
|
|
234
|
-
selectedText?: string
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function SelectionBarContainer({
|
|
238
|
-
actions = [],
|
|
239
|
-
showCloseButton = true,
|
|
240
|
-
selectedText,
|
|
241
|
-
...props
|
|
242
|
-
}: SelectionBarContainerProps) {
|
|
243
|
-
const { selectedCount, clearSelection } = useSelectionBar()
|
|
244
|
-
|
|
245
|
-
return (
|
|
246
|
-
<SelectionBar
|
|
247
|
-
actions={actions}
|
|
248
|
-
selectedCount={selectedCount}
|
|
249
|
-
selectedText={selectedText}
|
|
250
|
-
showCloseButton={showCloseButton}
|
|
251
|
-
onClose={() => clearSelection()}
|
|
252
|
-
{...props}
|
|
253
|
-
/>
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export const SelectionBarComponents = {
|
|
258
|
-
Root: SelectionBar,
|
|
259
|
-
Provider: SelectionBarProvider,
|
|
260
|
-
Container: SelectionBarContainer,
|
|
261
|
-
useSelectionBar
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export { SelectionBar }
|
|
265
|
-
export { SelectionBarProvider }
|
|
266
|
-
export { SelectionBarContainer }
|
|
267
|
-
export { useSelectionBar }
|
|
268
|
-
export { selectionBarVariants }
|
|
269
|
-
export type {
|
|
270
|
-
SelectionBarProps,
|
|
271
|
-
SelectionBarProviderProps,
|
|
272
|
-
SelectionBarContainerProps
|
|
273
|
-
}
|