@xjur-ui/react 1.0.1 → 1.0.3

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.
@@ -0,0 +1,76 @@
1
+ import { cx } from '@xjur-ui/util'
2
+ import { AvatarRoot, AvatarImage, AvatarFallback } from './avatar'
3
+ import { Typography } from './typography'
4
+
5
+ export interface AvatarGroupProps {
6
+ /**
7
+ * Lista de avatares para exibir
8
+ */
9
+ avatars: Array<{
10
+ src?: string
11
+ alt?: string
12
+ fallback?: string
13
+ }>
14
+ /**
15
+ * Número máximo de avatares a exibir antes de mostrar o indicador de overflow
16
+ * @default 3
17
+ */
18
+ max?: number
19
+ /**
20
+ * Tamanho dos avatares
21
+ * @default 'md'
22
+ */
23
+ size?: 'xs' | 'sm' | 'md' | 'lg'
24
+ /**
25
+ * Classe CSS adicional
26
+ */
27
+ className?: string
28
+ /**
29
+ * Espaçamento entre os avatares (em pixels)
30
+ * @default -8
31
+ */
32
+ spacing?: number
33
+ }
34
+
35
+ export function AvatarGroup({
36
+ avatars,
37
+ max = 3,
38
+ size = 'md',
39
+ className
40
+ }: AvatarGroupProps) {
41
+ const visibleAvatars = avatars.slice(0, max)
42
+ const remainingCount = avatars.length - max
43
+
44
+ return (
45
+ <div
46
+ className={cx('-space-x-small-1x flex items-center', className)}
47
+ data-slot="avatar-group"
48
+ >
49
+ {visibleAvatars.map((avatar, index) => (
50
+ <AvatarRoot
51
+ className="border-icon-neutral-5 border-2"
52
+ key={index}
53
+ size={size}
54
+ >
55
+ {avatar.src && (
56
+ <AvatarImage
57
+ alt={avatar.alt || `Avatar ${index + 1}`}
58
+ src={avatar.src}
59
+ />
60
+ )}
61
+ <AvatarFallback>{avatar.fallback || `A${index + 1}`}</AvatarFallback>
62
+ </AvatarRoot>
63
+ ))}
64
+
65
+ {remainingCount > 0 && (
66
+ <AvatarRoot className="bg-layer-1-hover" size={size}>
67
+ <AvatarFallback className="bg-layer-1-hover font-medium">
68
+ <Typography className="text-neutral-dark" weight="medium">
69
+ +{remainingCount}
70
+ </Typography>
71
+ </AvatarFallback>
72
+ </AvatarRoot>
73
+ )}
74
+ </div>
75
+ )
76
+ }
@@ -14,6 +14,7 @@ export function AvatarRoot({
14
14
  className={cx(
15
15
  'rounded-circle relative flex shrink-0 overflow-hidden',
16
16
  'data-[size=lg]:size-large-8x data-[size=md]:size-large-4x data-[size=sm]:size-large-2x data-[size=xs]:size-large-1x',
17
+ 'data-[size=lg]:text-size-headline-md data-[size=md]:text-size-label-lg data-[size=sm]:text-size-label-sm data-[size=xs]:text-size-label-sm',
17
18
  className
18
19
  )}
19
20
  data-size={size}
@@ -0,0 +1,40 @@
1
+ import { cva, type ClassVariantProps, cx } from '@xjur-ui/util'
2
+ import { Typography } from './typography'
3
+
4
+ const badgeVariants = cva(
5
+ [
6
+ 'flex items-center justify-center size-large p-small-2x text-neutral-label rounded-circle'
7
+ ],
8
+ {
9
+ variants: {
10
+ variant: {
11
+ primary: 'bg-primary-idle',
12
+ secondary: 'bg-secondary-idle',
13
+ neutral: 'bg-alt-2-idle',
14
+ success: 'bg-success-idle',
15
+ warning: 'bg-warning-idle',
16
+ danger: 'bg-danger-idle'
17
+ }
18
+ },
19
+ defaultVariants: {
20
+ variant: 'neutral'
21
+ }
22
+ }
23
+ )
24
+
25
+ export function Badge({
26
+ variant = 'neutral',
27
+ children,
28
+ className
29
+ }: ClassVariantProps<typeof badgeVariants> & {
30
+ children: string
31
+ className?: string
32
+ }) {
33
+ return (
34
+ <span className={cx(badgeVariants({ variant }), className)}>
35
+ <Typography className="text-inherit" size="sm-2x" variant="label">
36
+ {children}
37
+ </Typography>
38
+ </span>
39
+ )
40
+ }
@@ -41,7 +41,7 @@ export function DatePickerContent({
41
41
  align={align}
42
42
  className={cx(
43
43
  className,
44
- 'group z-10 max-h-[var(--radix-popover-content-available-height)] max-w-[var(--radix-popover-trigger-width)]'
44
+ 'group z-10 max-h-[var(--radix-popover-content-available-height)] max-w-[var(--radix-popover-content-available-width)] min-w-[var(--radix-popover-trigger-width)]'
45
45
  )}
46
46
  side={side}
47
47
  sideOffset={sideOffset}
@@ -88,25 +88,27 @@ export function DatePickerLabel({
88
88
  <Typography
89
89
  asChild={true}
90
90
  className={cx(
91
- 'bg-layer-2-idle w-fit font-normal',
92
- 'pointer-events-none absolute top-[50%] left-auto -translate-y-1/2',
93
- 'ease pointer-events-none transition-all duration-150',
91
+ 'inline-block max-w-full truncate text-left font-normal',
92
+ 'left-small right-small pointer-events-none absolute top-1/2 -translate-y-1/2',
93
+ 'ease transition-all duration-250',
94
94
  // Content Open
95
95
  'group-data-[state=open]:text-primary-idle',
96
- 'group-data-[state=open]:bg-layer-2-idle',
97
96
  'group-data-[state=open]:-top-[3px]',
98
97
  'group-data-[state=open]:text-size-label-sm-1x',
99
98
  'group-data-[state=open]:font-medium',
100
99
  // Filled
101
100
  'group-has-[input:not(:placeholder-shown)]:text-primary-idle',
102
- 'group-has-[input:not(:placeholder-shown)]:bg-layer-2-idle',
103
101
  'group-has-[input:not(:placeholder-shown)]:-top-[3px]',
104
102
  'group-has-[input:not(:placeholder-shown)]:text-size-label-sm-1x',
105
103
  'group-has-[input:not(:placeholder-shown)]:font-medium',
106
104
  // If has left slot
107
105
  'group-has-[div[data-slot=date-picker-left-slot]]:left-large-4x',
108
- 'group-has-[div[data-slot=date-picker-left-slot]]:group-data-[state=open]:left-auto',
109
- 'group-has-[div[data-slot=date-picker-left-slot]]:group-has-[input:not(:placeholder-shown)]:left-auto',
106
+ 'group-has-[div[data-slot=date-picker-left-slot]]:group-data-[state=open]:left-small',
107
+ 'group-has-[div[data-slot=date-picker-left-slot]]:group-has-[input:not(:placeholder-shown)]:left-small',
108
+ // If has right slot
109
+ 'group-has-[div[data-slot=date-picker-right-slot]]:right-large-4x',
110
+ 'group-has-[div[data-slot=date-picker-right-slot]]:group-data-[state=open]:right-small',
111
+ 'group-has-[div[data-slot=date-picker-right-slot]]:group-has-[input:not(:placeholder-shown)]:right-small',
110
112
  // Animate
111
113
  'group-data-[state=open]:animate-in',
112
114
  'group-data-[state=closed]:animate-out',
@@ -116,7 +118,11 @@ export function DatePickerLabel({
116
118
  weight="medium"
117
119
  {...props}
118
120
  >
119
- <label htmlFor={htmlFor}>{children}</label>
121
+ <label htmlFor={htmlFor}>
122
+ <span className="bg-layer-2-idle rounded-small-1x max-w-full">
123
+ {children}
124
+ </span>
125
+ </label>
120
126
  </Typography>
121
127
  )
122
128
  }
@@ -52,7 +52,7 @@ export function InputContent({ className, ...props }: ComponentProps<'div'>) {
52
52
  variant: {
53
53
  default: '',
54
54
  outlined:
55
- 'bg-layer-2-idle border-border-layer-2 border hover:border-border-layer-3 has-[input:not(:placeholder-shown)]:border-primary-idle',
55
+ 'bg-layer-2-idle border-border-layer-2 border hover:group-has-[input:not(:focus-within)]:border-border-layer-3 has-[input:not(:placeholder-shown)]:border-primary-idle',
56
56
  filled:
57
57
  'bg-layer-2-hover group-hover:border-border-layer-1 group-hover:border-1'
58
58
  }
@@ -74,6 +74,7 @@ export function InputContent({ className, ...props }: ComponentProps<'div'>) {
74
74
  export function InputLabel({
75
75
  htmlFor,
76
76
  className,
77
+ children,
77
78
  ...props
78
79
  }: ComponentProps<'label'> & { htmlFor: string }) {
79
80
  return (
@@ -82,10 +83,10 @@ export function InputLabel({
82
83
  return (
83
84
  <label
84
85
  className={cx(
85
- 'rounded-small-1x text-size-body-md text-neutral-dark data-[variant=outlined]:bg-layer-2-idle bg-layer-2-hover w-fit font-normal',
86
- 'ease animate-in pointer-events-none absolute top-1/2 left-auto -translate-y-1/2 transition-all duration-250',
86
+ 'group text-size-body-md text-neutral-dark inline-block max-w-full truncate text-left',
87
+ 'ease animate-in left-small right-small pointer-events-none absolute top-1/2 -translate-y-1/2 transition-all duration-250',
87
88
  // Filled
88
- 'group-focus-within:text-primary-idle group-focus-within:bg-layer-2-hover data-[variant=outlined]:group-focus-within:bg-layer-2-idle group-focus-within:-top-[3px]',
89
+ 'group-focus-within:text-primary-idle group-focus-within:-top-[3px]',
89
90
  'group-focus-within:text-size-label-sm-1x group-focus-within:font-medium',
90
91
  'group-has-[input:not(:placeholder-shown)]:-top-[3px]',
91
92
  'group-has-[input:not(:placeholder-shown)]:text-size-label-sm-1x',
@@ -93,8 +94,12 @@ export function InputLabel({
93
94
  'data-[variant=outlined]:group-has-[input:not(:placeholder-shown)]:text-primary-idle',
94
95
  // If has left slot
95
96
  'group-has-[div[data-slot=input-left-slot]]:left-large-4x',
96
- 'group-has-[div[data-slot=input-left-slot]]:group-focus-within:left-auto',
97
- 'group-has-[div[data-slot=input-left-slot]]:group-has-[input:not(:placeholder-shown)]:left-auto',
97
+ 'group-has-[div[data-slot=input-left-slot]]:group-focus-within:left-small',
98
+ 'group-has-[div[data-slot=input-left-slot]]:group-has-[input:not(:placeholder-shown)]:left-small',
99
+ // If has right slot
100
+ 'group-has-[div[data-slot=input-right-slot]]:right-large-4x',
101
+ 'group-has-[div[data-slot=input-right-slot]]:group-focus-within:right-small',
102
+ 'group-has-[div[data-slot=input-right-slot]]:group-has-[input:not(:placeholder-shown)]:right-small',
98
103
  // Animate
99
104
  'group-focus-within:animate-in',
100
105
  'group-has-[input:not(:placeholder-shown)]:animate-out',
@@ -103,7 +108,17 @@ export function InputLabel({
103
108
  data-variant={variant}
104
109
  htmlFor={htmlFor}
105
110
  {...props}
106
- />
111
+ >
112
+ <span
113
+ className={cx(
114
+ 'rounded-small-1x max-w-full',
115
+ 'group-data-[variant=outlined]:group-focus-within:bg-layer-2-idle group-data-[variant=filled]:group-focus-within:bg-layer-2-hover',
116
+ 'group-data-[variant=outlined]:bg-layer-2-idle group-data-[variant=filled]:bg-layer-2-hover'
117
+ )}
118
+ >
119
+ {children}
120
+ </span>
121
+ </label>
107
122
  )
108
123
  }}
109
124
  </FieldContext.Consumer>
@@ -54,7 +54,7 @@ export function SelectContent({
54
54
  align={align}
55
55
  className={cx(
56
56
  className,
57
- 'group z-10 max-h-[var(--radix-popover-content-available-height)] w-[var(--radix-popover-trigger-width)]'
57
+ 'group z-10 max-h-[var(--radix-popover-content-available-height)] max-w-[var(--radix-popover-content-available-width)] min-w-[var(--radix-popover-trigger-width)]'
58
58
  )}
59
59
  side={side}
60
60
  sideOffset={sideOffset}
@@ -101,7 +101,7 @@ export function SelectMultiValue({
101
101
  <div
102
102
  className={cx(
103
103
  className,
104
- 'gap-small-1x scrollbar-hide group-has-[div[data-slot=select-left-slot]]:pl-large-1x flex overflow-x-auto'
104
+ 'gap-small-1x scrollbar-hide group-has-[div[data-slot=select-left-slot]]:pl-large-1x overflow-x-small flex'
105
105
  )}
106
106
  {...props}
107
107
  >
@@ -129,25 +129,27 @@ export function SelectLabel({
129
129
  <Typography
130
130
  asChild={true}
131
131
  className={cx(
132
- 'bg-layer-2-idle w-fit font-normal',
133
- 'pointer-events-none absolute top-1/2 left-auto -translate-y-1/2',
132
+ 'inline-block max-w-full truncate text-left font-normal',
133
+ 'left-small right-small pointer-events-none absolute top-1/2 -translate-y-1/2',
134
134
  'ease transition-all duration-250',
135
135
  // Content Open
136
136
  'group-data-[state=open]:text-primary-idle',
137
- 'group-data-[state=open]:bg-layer-2-idle',
138
137
  'group-data-[state=open]:-top-[3px]',
139
138
  'group-data-[state=open]:text-size-label-sm-1x',
140
139
  'group-data-[state=open]:font-medium',
141
140
  // Filled
142
141
  'group-has-[input:not(:placeholder-shown)]:text-primary-idle',
143
- 'group-has-[input:not(:placeholder-shown)]:bg-layer-2-idle',
144
142
  'group-has-[input:not(:placeholder-shown)]:-top-[3px]',
145
143
  'group-has-[input:not(:placeholder-shown)]:text-size-label-sm-1x',
146
144
  'group-has-[input:not(:placeholder-shown)]:font-medium',
147
145
  // If has left slot
148
146
  'group-has-[div[data-slot=select-left-slot]]:left-large-4x',
149
- 'group-has-[div[data-slot=select-left-slot]]:group-data-[state=open]:left-auto',
150
- 'group-has-[div[data-slot=select-left-slot]]:group-has-[input:not(:placeholder-shown)]:left-auto',
147
+ 'group-has-[div[data-slot=select-left-slot]]:group-data-[state=open]:left-small',
148
+ 'group-has-[div[data-slot=select-left-slot]]:group-has-[input:not(:placeholder-shown)]:left-small',
149
+ // If has right slot
150
+ 'group-has-[div[data-slot=select-right-slot]]:right-large-4x',
151
+ 'group-has-[div[data-slot=select-right-slot]]:group-data-[state=open]:right-small',
152
+ 'group-has-[div[data-slot=select-right-slot]]:group-has-[input:not(:placeholder-shown)]:right-small',
151
153
  // Animate
152
154
  'group-data-[state=open]:animate-in',
153
155
  'group-data-[state=closed]:animate-out',
@@ -157,7 +159,11 @@ export function SelectLabel({
157
159
  weight="medium"
158
160
  {...props}
159
161
  >
160
- <label htmlFor={htmlFor}>{children}</label>
162
+ <label htmlFor={htmlFor}>
163
+ <span className="bg-layer-2-idle rounded-small-1x max-w-full">
164
+ {children}
165
+ </span>
166
+ </label>
161
167
  </Typography>
162
168
  )
163
169
  }
@@ -0,0 +1,160 @@
1
+ import { cx } from '@xjur-ui/util'
2
+ import { Icon } from './icon'
3
+
4
+ export function Table({ className, ...props }: React.ComponentProps<'table'>) {
5
+ return (
6
+ <div
7
+ className="text-size-body-md scrollbar-thin relative w-full overflow-hidden transition-all hover:overflow-x-auto"
8
+ data-slot="table-container"
9
+ >
10
+ <table
11
+ className={cx('w-full caption-bottom', className)}
12
+ data-slot="table"
13
+ {...props}
14
+ />
15
+ </div>
16
+ )
17
+ }
18
+
19
+ export function TableHeader({
20
+ className,
21
+ ...props
22
+ }: React.ComponentProps<'thead'>) {
23
+ return (
24
+ <thead
25
+ className={cx(
26
+ 'group/table-header [&_tr]:border-border-layer-2 hover:bg-layer-2-hover [&_tr]:border-b',
27
+ className
28
+ )}
29
+ data-slot="table-header"
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+
35
+ export function TableBody({
36
+ className,
37
+ ...props
38
+ }: React.ComponentProps<'tbody'>) {
39
+ return (
40
+ <tbody
41
+ className={cx(
42
+ '[&_tr]:border-border-layer-2 [&_tr]:border-b [&_tr:last-child]:border-0',
43
+ className
44
+ )}
45
+ data-slot="table-body"
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ export function TableFooter({
52
+ className,
53
+ ...props
54
+ }: React.ComponentProps<'tfoot'>) {
55
+ return (
56
+ <tfoot
57
+ className={cx(
58
+ 'border-border-layer-2 border-t font-medium [&>tr]:last:border-b-0',
59
+ className
60
+ )}
61
+ data-slot="table-footer"
62
+ {...props}
63
+ />
64
+ )
65
+ }
66
+
67
+ export function TableRow({
68
+ className,
69
+ isSelected = false,
70
+ ...props
71
+ }: React.ComponentProps<'tr'> & { isSelected?: boolean }) {
72
+ return (
73
+ <tr
74
+ className={cx(
75
+ 'bg-layer-2-idle hover:bg-layer-2-hover ease border-b transition-colors duration-100',
76
+ 'active:bg-layer-2-pressed',
77
+ 'data-[selected=true]:bg-primary-alt-active',
78
+ className
79
+ )}
80
+ data-selected={isSelected}
81
+ data-slot="table-row"
82
+ {...props}
83
+ />
84
+ )
85
+ }
86
+
87
+ export function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
88
+ return (
89
+ <th
90
+ className={cx(
91
+ 'group/th text-neutral-placeholder text-size-label-sm h-large-4x py-small-1x px-small text-left align-middle font-medium whitespace-nowrap',
92
+ 'pr-large-2x relative',
93
+ 'animate-in fade-in duration-200 ease-in',
94
+ className
95
+ )}
96
+ data-slot="table-head"
97
+ {...props}
98
+ />
99
+ )
100
+ }
101
+
102
+ export function TableHeadContent({
103
+ className,
104
+ ...props
105
+ }: React.ComponentProps<'div'>) {
106
+ return (
107
+ <div
108
+ className={cx(
109
+ 'gap-small pr-large-3x relative flex w-fit items-center',
110
+ className
111
+ )}
112
+ {...props}
113
+ />
114
+ )
115
+ }
116
+
117
+ export function TableSort({
118
+ className,
119
+ sortedDirection,
120
+ ...props
121
+ }: React.ComponentProps<'button'> & { sortedDirection?: 'asc' | 'desc' }) {
122
+ return (
123
+ <button
124
+ className={cx(
125
+ 'size-large-1x rounded-small-1x',
126
+ 'flex cursor-pointer items-center justify-center',
127
+ 'hidden group-hover/th:flex',
128
+ 'data-[sorted=true]:flex',
129
+ 'transition-all duration-200 ease-out',
130
+ 'data-[sorted=true]:bg-primary-alt-active data-[sorted=true]:text-primary-label-active',
131
+ 'absolute top-1/2 right-0 -translate-y-1/2',
132
+ className
133
+ )}
134
+ data-sorted={sortedDirection !== undefined}
135
+ type="button"
136
+ {...props}
137
+ >
138
+ {sortedDirection !== undefined ? (
139
+ <Icon
140
+ name={sortedDirection === 'asc' ? 'arrow_downward' : 'arrow_upward'}
141
+ />
142
+ ) : (
143
+ <Icon name="swap_vert" />
144
+ )}
145
+ </button>
146
+ )
147
+ }
148
+
149
+ export function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
150
+ return (
151
+ <td
152
+ className={cx(
153
+ 'px-small py-none h-large-8x text-left align-middle whitespace-nowrap',
154
+ className
155
+ )}
156
+ data-slot="table-cell"
157
+ {...props}
158
+ />
159
+ )
160
+ }
@@ -138,7 +138,9 @@ export const icons = [
138
138
  'event',
139
139
  'account_circle',
140
140
  'history',
141
- 'security'
141
+ 'security',
142
+ 'bar_chart',
143
+ 'category'
142
144
  ] as const
143
145
 
144
146
  export type IconType = (typeof icons)[number]