@liguelead/design-system 0.0.37 → 0.0.39
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/components/Button/Button.types.ts +1 -1
- package/components/Combobox/Combobox.styles.ts +1 -1
- package/components/Combobox/Combobox.tsx +1 -1
- package/components/RadioCardGroup/RadioCardGroup.stories.tsx +203 -0
- package/components/RadioCardGroup/RadioCardGroup.styles.ts +199 -0
- package/components/RadioCardGroup/RadioCardGroup.tsx +159 -0
- package/components/RadioCardGroup/RadioCardGroup.types.ts +29 -0
- package/components/RadioCardGroup/index.ts +2 -0
- package/components/Stepper/Stepper.appearance.ts +57 -0
- package/components/Stepper/Stepper.stories.tsx +300 -0
- package/components/Stepper/Stepper.styles.ts +179 -0
- package/components/Stepper/Stepper.tsx +118 -0
- package/components/Stepper/Stepper.types.ts +27 -0
- package/components/Stepper/index.ts +7 -0
- package/components/TextField/TextField.stories.tsx +109 -2
- package/components/TextField/TextField.styles.ts +44 -0
- package/components/TextField/TextField.tsx +119 -1
- package/components/TextField/TextField.types.ts +11 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { colorType } from '../../types'
|
|
|
3
3
|
export type ButtonSizeTypes = 'sm' | 'md' | 'lg'
|
|
4
4
|
export type ButtonVariantTypes = 'solid' | 'outline' | 'ghost' | 'neutralOutline' | 'neutralGhost'
|
|
5
5
|
|
|
6
|
-
export interface ButtonProps {
|
|
6
|
+
export interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'color' | 'onClick'> {
|
|
7
7
|
variant?: ButtonVariantTypes
|
|
8
8
|
children: React.ReactNode
|
|
9
9
|
className?: string
|
|
@@ -80,7 +80,7 @@ export const PopoverContent = styled(Popover.Content)<{
|
|
|
80
80
|
$width: number | string
|
|
81
81
|
}>`
|
|
82
82
|
width: ${({ $width }) =>
|
|
83
|
-
typeof $width === 'number' ? `${$width}px` : $width};
|
|
83
|
+
typeof $width === 'number' ? `${$width}px` : $width === '100%' ? 'var(--radix-popover-trigger-width)' : $width};
|
|
84
84
|
background: ${({ theme }) => parseColor(theme.colors.white)};
|
|
85
85
|
border: 1px solid ${({ theme }) => parseColor(theme.colors.neutral400)};
|
|
86
86
|
border-radius: 4px;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
|
+
import { ArticleNyTimesIcon, ChatCircleIcon, EnvelopeSimpleIcon } from '@phosphor-icons/react'
|
|
4
|
+
import RadioCardGroup from './RadioCardGroup'
|
|
5
|
+
|
|
6
|
+
const LONG_DESC = 'Ideal para comunicações diretas com alta taxa de entrega. Suporta texto simples, links e variáveis personalizadas. Amplamente compatível com todos os dispositivos móveis, sem necessidade de internet ou aplicativo instalado.'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof RadioCardGroup> = {
|
|
9
|
+
title: 'Form/RadioCardGroup',
|
|
10
|
+
component: RadioCardGroup,
|
|
11
|
+
parameters: { layout: 'centered' },
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default meta
|
|
16
|
+
type Story = StoryObj<typeof meta>
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
render: () => {
|
|
20
|
+
const [selected, setSelected] = useState('sms')
|
|
21
|
+
return (
|
|
22
|
+
<div style={{ width: 320 }}>
|
|
23
|
+
<RadioCardGroup
|
|
24
|
+
name="channel"
|
|
25
|
+
value={selected}
|
|
26
|
+
onChange={setSelected}
|
|
27
|
+
options={[
|
|
28
|
+
{
|
|
29
|
+
value: 'sms',
|
|
30
|
+
label: 'SMS',
|
|
31
|
+
icon: <ChatCircleIcon size={20} />,
|
|
32
|
+
description: LONG_DESC,
|
|
33
|
+
descriptionMaxLines: 2,
|
|
34
|
+
badgesLabel: 'O que contém:',
|
|
35
|
+
badges: ['Texto até 1.600 caracteres'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
value: 'email',
|
|
39
|
+
label: 'E-mail',
|
|
40
|
+
icon: <EnvelopeSimpleIcon size={20} />,
|
|
41
|
+
description: 'Envio de mensagens por e-mail.',
|
|
42
|
+
badgesLabel: 'O que contém:',
|
|
43
|
+
badges: ['Texto', 'Imagens', 'Links'],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
value: 'rcs',
|
|
47
|
+
label: 'RCS',
|
|
48
|
+
icon: <ArticleNyTimesIcon size={20} />,
|
|
49
|
+
description: 'Mensagens ricas com mídia.',
|
|
50
|
+
badgesLabel: 'O que contém:',
|
|
51
|
+
badges: ['Texto', 'Imagens', 'Botões', 'Carrossel'],
|
|
52
|
+
},
|
|
53
|
+
]}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const WithSeeMore: Story = {
|
|
61
|
+
name: 'Ver mais (descrição longa)',
|
|
62
|
+
render: () => {
|
|
63
|
+
const [selected, setSelected] = useState('a')
|
|
64
|
+
return (
|
|
65
|
+
<div style={{ width: 320 }}>
|
|
66
|
+
<RadioCardGroup
|
|
67
|
+
name="see-more"
|
|
68
|
+
value={selected}
|
|
69
|
+
onChange={setSelected}
|
|
70
|
+
options={[
|
|
71
|
+
{
|
|
72
|
+
value: 'a',
|
|
73
|
+
label: 'Canal A',
|
|
74
|
+
icon: <ChatCircleIcon size={20} />,
|
|
75
|
+
description: LONG_DESC,
|
|
76
|
+
descriptionMaxLines: 2,
|
|
77
|
+
badges: ['Feature 1', 'Feature 2'],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
value: 'b',
|
|
81
|
+
label: 'Canal B',
|
|
82
|
+
icon: <EnvelopeSimpleIcon size={20} />,
|
|
83
|
+
description: 'Descrição curta, sem botão de ver mais.',
|
|
84
|
+
badges: ['Feature 3'],
|
|
85
|
+
},
|
|
86
|
+
]}
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const Scrollable: Story = {
|
|
94
|
+
name: 'Com scroll thin',
|
|
95
|
+
render: () => {
|
|
96
|
+
const [selected, setSelected] = useState('a')
|
|
97
|
+
return (
|
|
98
|
+
<div style={{ width: 320 }}>
|
|
99
|
+
<RadioCardGroup
|
|
100
|
+
name="scroll"
|
|
101
|
+
value={selected}
|
|
102
|
+
onChange={setSelected}
|
|
103
|
+
scrollable
|
|
104
|
+
maxHeight="300px"
|
|
105
|
+
options={Array.from({ length: 6 }, (_, i) => ({
|
|
106
|
+
value: String(i),
|
|
107
|
+
label: `Opção ${i + 1}`,
|
|
108
|
+
icon: <ChatCircleIcon size={20} />,
|
|
109
|
+
description: `Descrição da opção ${i + 1}.`,
|
|
110
|
+
badges: [`Badge ${i + 1}`],
|
|
111
|
+
}))}
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const TwoColumns: Story = {
|
|
119
|
+
name: '2 colunas',
|
|
120
|
+
render: () => {
|
|
121
|
+
const [selected, setSelected] = useState('a')
|
|
122
|
+
return (
|
|
123
|
+
<div style={{ width: 600 }}>
|
|
124
|
+
<RadioCardGroup
|
|
125
|
+
name="grid"
|
|
126
|
+
value={selected}
|
|
127
|
+
onChange={setSelected}
|
|
128
|
+
columns={2}
|
|
129
|
+
options={[
|
|
130
|
+
{ value: 'a', label: 'SMS', icon: <ChatCircleIcon size={20} />, description: 'Texto simples.', badges: ['160 chars'] },
|
|
131
|
+
{ value: 'b', label: 'E-mail', icon: <EnvelopeSimpleIcon size={20} />, description: 'HTML completo.', badges: ['Ilimitado'] },
|
|
132
|
+
{ value: 'c', label: 'RCS', icon: <ArticleNyTimesIcon size={20} />, description: 'Mídia rica.', badges: ['Botões', 'Carrossel'] },
|
|
133
|
+
{ value: 'd', label: 'Push', icon: <ChatCircleIcon size={20} />, description: 'Notificação push.', badges: ['Deep link'] },
|
|
134
|
+
]}
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
)
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const WithoutIcon: Story = {
|
|
142
|
+
render: () => {
|
|
143
|
+
const [selected, setSelected] = useState('a')
|
|
144
|
+
return (
|
|
145
|
+
<div style={{ width: 320 }}>
|
|
146
|
+
<RadioCardGroup
|
|
147
|
+
name="plan"
|
|
148
|
+
value={selected}
|
|
149
|
+
onChange={setSelected}
|
|
150
|
+
options={[
|
|
151
|
+
{ value: 'a', label: 'Plano Básico', description: 'Funcionalidades essenciais.', badgesLabel: 'Inclui:', badges: ['5 usuários', '10GB'] },
|
|
152
|
+
{ value: 'b', label: 'Plano Pro', description: 'Para times maiores.', badgesLabel: 'Inclui:', badges: ['Ilimitado', '100GB', 'Suporte'] },
|
|
153
|
+
]}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
)
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const Disabled: Story = {
|
|
161
|
+
render: () => (
|
|
162
|
+
<div style={{ width: 320 }}>
|
|
163
|
+
<RadioCardGroup
|
|
164
|
+
name="disabled"
|
|
165
|
+
value="a"
|
|
166
|
+
disabled
|
|
167
|
+
options={[
|
|
168
|
+
{
|
|
169
|
+
value: 'a',
|
|
170
|
+
label: 'Opção desabilitada',
|
|
171
|
+
icon: <ChatCircleIcon size={20} />,
|
|
172
|
+
description: 'Este grupo está desabilitado.',
|
|
173
|
+
badges: ['Indisponível'],
|
|
174
|
+
},
|
|
175
|
+
]}
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const CardWidth: Story = {
|
|
182
|
+
name: 'Largura dos cards (min/max)',
|
|
183
|
+
render: () => {
|
|
184
|
+
const [selected, setSelected] = useState('a')
|
|
185
|
+
return (
|
|
186
|
+
<div style={{ width: 700 }}>
|
|
187
|
+
<RadioCardGroup
|
|
188
|
+
name="width"
|
|
189
|
+
value={selected}
|
|
190
|
+
onChange={setSelected}
|
|
191
|
+
columns={3}
|
|
192
|
+
minCardWidth="160px"
|
|
193
|
+
maxCardWidth="240px"
|
|
194
|
+
options={[
|
|
195
|
+
{ value: 'a', label: 'SMS', icon: <ChatCircleIcon size={20} />, description: 'Texto simples.', badges: ['160 chars'] },
|
|
196
|
+
{ value: 'b', label: 'E-mail', icon: <EnvelopeSimpleIcon size={20} />, description: 'HTML completo.', badges: ['Ilimitado'] },
|
|
197
|
+
{ value: 'c', label: 'RCS', icon: <ArticleNyTimesIcon size={20} />, description: 'Mídia rica.', badges: ['Botões'] },
|
|
198
|
+
]}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
)
|
|
202
|
+
},
|
|
203
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import { fontSize, fontWeight, lineHeight, radius, spacing } from '@liguelead/foundation'
|
|
3
|
+
import { parseColor } from '../../utils'
|
|
4
|
+
|
|
5
|
+
export const CardWrapper = styled.label<{
|
|
6
|
+
$checked?: boolean
|
|
7
|
+
$disabled?: boolean
|
|
8
|
+
$error?: boolean
|
|
9
|
+
$columns?: number
|
|
10
|
+
$minCardWidth?: string
|
|
11
|
+
$maxCardWidth?: string
|
|
12
|
+
}>`
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: ${spacing.spacing16}px;
|
|
16
|
+
padding: ${spacing.spacing24}px;
|
|
17
|
+
border-radius: ${radius.radius8}px;
|
|
18
|
+
border: 1px solid ${({ theme, $checked, $error }) =>
|
|
19
|
+
$error
|
|
20
|
+
? parseColor(theme.colors.danger200)
|
|
21
|
+
: $checked
|
|
22
|
+
? parseColor(theme.colors.primary)
|
|
23
|
+
: parseColor(theme.colors.neutral400)};
|
|
24
|
+
background: ${({ theme, $checked, $error }) =>
|
|
25
|
+
$checked && !$error
|
|
26
|
+
? parseColor(theme.colors.primaryLight)
|
|
27
|
+
: parseColor(theme.colors.white)};
|
|
28
|
+
cursor: ${({ $disabled }) => ($disabled ? 'not-allowed' : 'pointer')};
|
|
29
|
+
opacity: ${({ $disabled }) => ($disabled ? 0.5 : 1)};
|
|
30
|
+
transition: border-color 0.2s ease, background 0.2s ease;
|
|
31
|
+
flex: ${({ $columns }) => ($columns ? `1 1 calc(${100 / $columns}% - ${spacing.spacing12}px)` : '1 1 100%')};
|
|
32
|
+
min-width: ${({ $minCardWidth }) => $minCardWidth ?? '0'};
|
|
33
|
+
max-width: ${({ $maxCardWidth }) => $maxCardWidth ?? 'none'};
|
|
34
|
+
|
|
35
|
+
&:hover:not([data-disabled='true']) {
|
|
36
|
+
border-color: ${({ theme, $error }) =>
|
|
37
|
+
$error
|
|
38
|
+
? parseColor(theme.colors.danger200)
|
|
39
|
+
: parseColor(theme.colors.primary)};
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
export const CardRadioRow = styled.div`
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: flex-start;
|
|
46
|
+
gap: ${spacing.spacing12}px;
|
|
47
|
+
`
|
|
48
|
+
|
|
49
|
+
export const RadioCircle = styled.input`
|
|
50
|
+
appearance: none;
|
|
51
|
+
width: ${spacing.spacing16}px;
|
|
52
|
+
height: ${spacing.spacing16}px;
|
|
53
|
+
min-width: ${spacing.spacing16}px;
|
|
54
|
+
border: 1px solid ${({ theme }) => parseColor(theme.colors.neutral400)};
|
|
55
|
+
border-radius: 50%;
|
|
56
|
+
background: ${({ theme }) => parseColor(theme.colors.white)};
|
|
57
|
+
position: relative;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
transition: all 0.2s ease;
|
|
60
|
+
margin-top: 2px;
|
|
61
|
+
|
|
62
|
+
&:checked {
|
|
63
|
+
border-color: ${({ theme }) => parseColor(theme.colors.neutral300)};
|
|
64
|
+
|
|
65
|
+
&::after {
|
|
66
|
+
content: '';
|
|
67
|
+
position: absolute;
|
|
68
|
+
top: 50%;
|
|
69
|
+
left: 50%;
|
|
70
|
+
transform: translate(-50%, -50%);
|
|
71
|
+
width: 8px;
|
|
72
|
+
height: 8px;
|
|
73
|
+
border-radius: 50%;
|
|
74
|
+
background: ${({ theme }) => parseColor(theme.colors.primary)};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&:disabled {
|
|
79
|
+
cursor: not-allowed;
|
|
80
|
+
border-color: ${({ theme }) => parseColor(theme.colors.neutral400)};
|
|
81
|
+
background: ${({ theme }) => parseColor(theme.colors.neutral100)};
|
|
82
|
+
}
|
|
83
|
+
`
|
|
84
|
+
|
|
85
|
+
export const CardTitleRow = styled.div`
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 6px;
|
|
89
|
+
`
|
|
90
|
+
|
|
91
|
+
export const CardIconWrapper = styled.div`
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
width: 20px;
|
|
96
|
+
height: 20px;
|
|
97
|
+
flex-shrink: 0;
|
|
98
|
+
|
|
99
|
+
svg {
|
|
100
|
+
width: 20px;
|
|
101
|
+
height: 20px;
|
|
102
|
+
color: ${({ theme }) => parseColor(theme.colors.neutral1000)};
|
|
103
|
+
}
|
|
104
|
+
`
|
|
105
|
+
|
|
106
|
+
export const Divider = styled.div`
|
|
107
|
+
width: 100%;
|
|
108
|
+
height: 1px;
|
|
109
|
+
background: ${({ theme }) => parseColor(theme.colors.neutral300)};
|
|
110
|
+
`
|
|
111
|
+
|
|
112
|
+
export const CardContent = styled.div`
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
gap: ${spacing.spacing8}px;
|
|
116
|
+
`
|
|
117
|
+
|
|
118
|
+
export const DescriptionWrapper = styled.div<{ $expanded: boolean; $maxLines: number }>`
|
|
119
|
+
position: relative;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
max-height: ${({ $expanded, $maxLines }) =>
|
|
122
|
+
$expanded ? 'none' : `calc(${lineHeight.lineHeight16}px * ${$maxLines})`};
|
|
123
|
+
`
|
|
124
|
+
|
|
125
|
+
export const DescriptionText = styled.p`
|
|
126
|
+
font-size: ${fontSize.fontSize12}px;
|
|
127
|
+
font-weight: ${fontWeight.fontWeight400};
|
|
128
|
+
line-height: ${lineHeight.lineHeight16}px;
|
|
129
|
+
color: ${({ theme }) => parseColor(theme.colors.textDark)};
|
|
130
|
+
margin: 0;
|
|
131
|
+
`
|
|
132
|
+
|
|
133
|
+
export const SeeMoreButton = styled.button`
|
|
134
|
+
background: none;
|
|
135
|
+
border: none;
|
|
136
|
+
padding: 0;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
font-size: ${fontSize.fontSize12}px;
|
|
139
|
+
font-weight: ${fontWeight.fontWeight500};
|
|
140
|
+
line-height: ${lineHeight.lineHeight16}px;
|
|
141
|
+
color: ${({ theme }) => parseColor(theme.colors.primary)};
|
|
142
|
+
text-align: left;
|
|
143
|
+
margin-top: 2px;
|
|
144
|
+
|
|
145
|
+
&:hover {
|
|
146
|
+
text-decoration: underline;
|
|
147
|
+
}
|
|
148
|
+
`
|
|
149
|
+
|
|
150
|
+
export const BadgesSection = styled.div`
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
gap: ${spacing.spacing8}px;
|
|
154
|
+
`
|
|
155
|
+
|
|
156
|
+
export const BadgesLabel = styled.p`
|
|
157
|
+
font-size: ${fontSize.fontSize12}px;
|
|
158
|
+
font-weight: ${fontWeight.fontWeight500};
|
|
159
|
+
line-height: ${lineHeight.lineHeight16}px;
|
|
160
|
+
color: ${({ theme }) => parseColor(theme.colors.textMedium)};
|
|
161
|
+
margin: 0;
|
|
162
|
+
white-space: nowrap;
|
|
163
|
+
`
|
|
164
|
+
|
|
165
|
+
export const BadgesList = styled.div`
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-wrap: wrap;
|
|
168
|
+
gap: ${spacing.spacing4}px;
|
|
169
|
+
align-items: center;
|
|
170
|
+
`
|
|
171
|
+
|
|
172
|
+
export const GroupWrapper = styled.div<{ $columns?: number; $minCardWidth?: string; $scrollable?: boolean; $maxHeight?: string }>`
|
|
173
|
+
display: flex;
|
|
174
|
+
flex-wrap: wrap;
|
|
175
|
+
flex-direction: ${({ $columns }) => ($columns ? 'row' : 'column')};
|
|
176
|
+
gap: ${spacing.spacing12}px;
|
|
177
|
+
width: 100%;
|
|
178
|
+
|
|
179
|
+
${({ $scrollable, $maxHeight }) => $scrollable && `
|
|
180
|
+
overflow-y: auto;
|
|
181
|
+
max-height: ${$maxHeight ?? '400px'};
|
|
182
|
+
padding-right: ${spacing.spacing4}px;
|
|
183
|
+
|
|
184
|
+
scrollbar-width: thin;
|
|
185
|
+
scrollbar-color: #7c7c83 #cfcfd1;
|
|
186
|
+
|
|
187
|
+
&::-webkit-scrollbar {
|
|
188
|
+
width: 8px;
|
|
189
|
+
}
|
|
190
|
+
&::-webkit-scrollbar-track {
|
|
191
|
+
background: #cfcfd1;
|
|
192
|
+
border-radius: 100px;
|
|
193
|
+
}
|
|
194
|
+
&::-webkit-scrollbar-thumb {
|
|
195
|
+
background: #7c7c83;
|
|
196
|
+
border-radius: 100px;
|
|
197
|
+
}
|
|
198
|
+
`}
|
|
199
|
+
`
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react'
|
|
2
|
+
import { Badge } from '../Badge'
|
|
3
|
+
import { RadioCardGroupProps } from './RadioCardGroup.types'
|
|
4
|
+
import {
|
|
5
|
+
GroupWrapper,
|
|
6
|
+
CardWrapper,
|
|
7
|
+
CardRadioRow,
|
|
8
|
+
RadioCircle,
|
|
9
|
+
CardTitleRow,
|
|
10
|
+
CardIconWrapper,
|
|
11
|
+
Divider,
|
|
12
|
+
CardContent,
|
|
13
|
+
DescriptionWrapper,
|
|
14
|
+
DescriptionText,
|
|
15
|
+
SeeMoreButton,
|
|
16
|
+
BadgesSection,
|
|
17
|
+
BadgesLabel,
|
|
18
|
+
BadgesList,
|
|
19
|
+
} from './RadioCardGroup.styles'
|
|
20
|
+
import Text from '../Text'
|
|
21
|
+
|
|
22
|
+
const DEFAULT_MAX_LINES = 3
|
|
23
|
+
|
|
24
|
+
interface DescriptionWithToggleProps {
|
|
25
|
+
text: string
|
|
26
|
+
maxLines?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DescriptionWithToggle = ({ text, maxLines = DEFAULT_MAX_LINES }: DescriptionWithToggleProps) => {
|
|
30
|
+
const [expanded, setExpanded] = useState(false)
|
|
31
|
+
const [isClamped, setIsClamped] = useState(false)
|
|
32
|
+
const ref = useRef<HTMLParagraphElement>(null)
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const el = ref.current
|
|
36
|
+
if (!el) return
|
|
37
|
+
setIsClamped(el.scrollHeight > el.clientHeight + 1)
|
|
38
|
+
}, [text, maxLines])
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<DescriptionWrapper $expanded={expanded} $maxLines={maxLines}>
|
|
43
|
+
<DescriptionText ref={ref}>{text}</DescriptionText>
|
|
44
|
+
</DescriptionWrapper>
|
|
45
|
+
{(isClamped || expanded) && (
|
|
46
|
+
<SeeMoreButton
|
|
47
|
+
type="button"
|
|
48
|
+
onClick={(e) => {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
e.stopPropagation()
|
|
51
|
+
setExpanded(v => !v)
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{expanded ? 'Ver menos' : 'Ver mais'}
|
|
55
|
+
</SeeMoreButton>
|
|
56
|
+
)}
|
|
57
|
+
</>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const RadioCardGroup = <TFieldValues extends object = object>({
|
|
62
|
+
name,
|
|
63
|
+
options,
|
|
64
|
+
value,
|
|
65
|
+
onChange,
|
|
66
|
+
disabled = false,
|
|
67
|
+
className,
|
|
68
|
+
error,
|
|
69
|
+
columns,
|
|
70
|
+
minCardWidth,
|
|
71
|
+
maxCardWidth,
|
|
72
|
+
scrollable,
|
|
73
|
+
maxHeight,
|
|
74
|
+
...rest
|
|
75
|
+
}: RadioCardGroupProps<TFieldValues>) => {
|
|
76
|
+
const hasError = !!error
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<GroupWrapper
|
|
80
|
+
className={className}
|
|
81
|
+
$columns={columns}
|
|
82
|
+
$minCardWidth={minCardWidth}
|
|
83
|
+
$scrollable={scrollable}
|
|
84
|
+
$maxHeight={maxHeight}
|
|
85
|
+
{...rest}
|
|
86
|
+
>
|
|
87
|
+
{options.map((option) => {
|
|
88
|
+
const isChecked = value === option.value
|
|
89
|
+
const isDisabled = disabled || !!option.disabled
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<CardWrapper
|
|
93
|
+
key={option.value}
|
|
94
|
+
$checked={isChecked}
|
|
95
|
+
$disabled={isDisabled}
|
|
96
|
+
$error={hasError}
|
|
97
|
+
$columns={columns}
|
|
98
|
+
$minCardWidth={minCardWidth}
|
|
99
|
+
$maxCardWidth={maxCardWidth}
|
|
100
|
+
data-disabled={isDisabled}
|
|
101
|
+
>
|
|
102
|
+
<CardRadioRow>
|
|
103
|
+
<RadioCircle
|
|
104
|
+
type="radio"
|
|
105
|
+
name={name}
|
|
106
|
+
value={option.value}
|
|
107
|
+
checked={isChecked}
|
|
108
|
+
disabled={isDisabled}
|
|
109
|
+
onChange={(e) => {
|
|
110
|
+
option.register?.onChange(e)
|
|
111
|
+
onChange?.(option.value)
|
|
112
|
+
}}
|
|
113
|
+
{...option.register}
|
|
114
|
+
/>
|
|
115
|
+
<CardTitleRow>
|
|
116
|
+
{option.icon && (
|
|
117
|
+
<CardIconWrapper>{option.icon}</CardIconWrapper>
|
|
118
|
+
)}
|
|
119
|
+
<Text color="textDark" weight="fontWeight500" size="body02" tag="p">
|
|
120
|
+
{option.label}
|
|
121
|
+
</Text>
|
|
122
|
+
</CardTitleRow>
|
|
123
|
+
</CardRadioRow>
|
|
124
|
+
|
|
125
|
+
{(option.description || (option.badges && option.badges.length > 0)) && (
|
|
126
|
+
<>
|
|
127
|
+
<Divider />
|
|
128
|
+
<CardContent>
|
|
129
|
+
{option.description && (
|
|
130
|
+
<DescriptionWithToggle
|
|
131
|
+
text={option.description}
|
|
132
|
+
maxLines={option.descriptionMaxLines}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
{option.badges && option.badges.length > 0 && (
|
|
136
|
+
<BadgesSection>
|
|
137
|
+
{option.badgesLabel && (
|
|
138
|
+
<BadgesLabel>{option.badgesLabel}</BadgesLabel>
|
|
139
|
+
)}
|
|
140
|
+
<BadgesList>
|
|
141
|
+
{option.badges.map((badge, i) => (
|
|
142
|
+
<Badge key={i} color={isChecked ? 'primary' : 'primaryLight'}>
|
|
143
|
+
{badge}
|
|
144
|
+
</Badge>
|
|
145
|
+
))}
|
|
146
|
+
</BadgesList>
|
|
147
|
+
</BadgesSection>
|
|
148
|
+
)}
|
|
149
|
+
</CardContent>
|
|
150
|
+
</>
|
|
151
|
+
)}
|
|
152
|
+
</CardWrapper>
|
|
153
|
+
)
|
|
154
|
+
})}
|
|
155
|
+
</GroupWrapper>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default RadioCardGroup
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { FieldValues, UseFormRegisterReturn } from 'react-hook-form'
|
|
3
|
+
|
|
4
|
+
export interface RadioCardOption {
|
|
5
|
+
value: string
|
|
6
|
+
label: string
|
|
7
|
+
icon?: React.ReactNode
|
|
8
|
+
description?: string
|
|
9
|
+
descriptionMaxLines?: number
|
|
10
|
+
badges?: string[]
|
|
11
|
+
badgesLabel?: string
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
register?: UseFormRegisterReturn<string>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RadioCardGroupProps<TFieldValues extends FieldValues = FieldValues>
|
|
17
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
18
|
+
name: string
|
|
19
|
+
options: RadioCardOption[]
|
|
20
|
+
value?: string
|
|
21
|
+
onChange?: (value: string) => void
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
error?: TFieldValues
|
|
24
|
+
columns?: number
|
|
25
|
+
minCardWidth?: string
|
|
26
|
+
maxCardWidth?: string
|
|
27
|
+
scrollable?: boolean
|
|
28
|
+
maxHeight?: string
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { css } from 'styled-components'
|
|
2
|
+
import { useTheme } from 'styled-components'
|
|
3
|
+
import { parseColor } from '../../utils'
|
|
4
|
+
import { StepState } from './Stepper.types'
|
|
5
|
+
|
|
6
|
+
export const StepIndicatorAppearance = ($state: StepState) => {
|
|
7
|
+
const theme = useTheme()
|
|
8
|
+
const colors = theme.colors
|
|
9
|
+
|
|
10
|
+
if ($state === 'completed') {
|
|
11
|
+
return css`
|
|
12
|
+
width: 28px;
|
|
13
|
+
height: 28px;
|
|
14
|
+
background-color: ${parseColor(colors.primary)};
|
|
15
|
+
border: 2px solid ${parseColor(colors.primary)};
|
|
16
|
+
color: ${parseColor(colors.white)};
|
|
17
|
+
`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if ($state === 'active') {
|
|
21
|
+
return css`
|
|
22
|
+
width: 28px;
|
|
23
|
+
height: 28px;
|
|
24
|
+
background-color: transparent;
|
|
25
|
+
border: 2.5px solid ${parseColor(colors.primary)};
|
|
26
|
+
color: ${parseColor(colors.primary)};
|
|
27
|
+
box-shadow: 0 0 0 4px ${parseColor(colors.primary)}22;
|
|
28
|
+
`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if ($state === 'error') {
|
|
32
|
+
return css`
|
|
33
|
+
width: 28px;
|
|
34
|
+
height: 28px;
|
|
35
|
+
background-color: transparent;
|
|
36
|
+
border: 2px solid ${parseColor(colors.danger200)};
|
|
37
|
+
color: ${parseColor(colors.danger200)};
|
|
38
|
+
`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return css`
|
|
42
|
+
width: 28px;
|
|
43
|
+
height: 28px;
|
|
44
|
+
background-color: transparent;
|
|
45
|
+
border: none;
|
|
46
|
+
color: transparent;
|
|
47
|
+
|
|
48
|
+
&::before {
|
|
49
|
+
content: '';
|
|
50
|
+
display: block;
|
|
51
|
+
width: 10px;
|
|
52
|
+
height: 10px;
|
|
53
|
+
border-radius: 50%;
|
|
54
|
+
background-color: ${parseColor(colors.neutral400)};
|
|
55
|
+
}
|
|
56
|
+
`
|
|
57
|
+
}
|