@granto-umbrella/umbrella-components 3.0.55 → 3.0.57

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@granto-umbrella/umbrella-components",
3
- "version": "3.0.55",
3
+ "version": "3.0.57",
4
4
  "description": "Umbrella Components for React",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -79,7 +79,6 @@
79
79
  "@storybook/addon-docs": "10.1.6",
80
80
  "@storybook/addon-essentials": "^8.6.14",
81
81
  "@storybook/addon-onboarding": "10.1.6",
82
- "@storybook/cli": "^10.1.10",
83
82
  "@storybook/react-vite": "^10.1.6",
84
83
  "@storybook/test": "^8.6.14",
85
84
  "@testing-library/dom": "^10.4.1",
@@ -1,27 +1,27 @@
1
- export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
2
- size?: 'sm' | 'md' | 'lg';
3
- variant?:
4
- | 'primary'
5
- | 'outline'
6
- | 'outline_ghost'
7
- | 'outline_ghost_disabled'
8
- | 'outline_danger'
9
- | 'danger'
10
- | 'ghost'
11
- | 'ghost_alert'
12
- | 'alert'
13
- | 'danger-no-fill'
14
- | 'accent-outline'
15
- | 'accent-fill'
16
- | 'secondary-outline'
17
- | 'danger-fill';
18
- radius?: string;
19
- isLoading?: boolean;
20
- leftIcon?: string;
21
- rightIcon?: string;
22
- iconSize?: string;
23
- fontSize?: string;
24
- width?: string;
25
- height?: string;
26
- testId?: string;
27
- }
1
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
2
+ size?: 'sm' | 'md' | 'lg';
3
+ variant?:
4
+ | 'primary'
5
+ | 'outline'
6
+ | 'outline_ghost'
7
+ | 'outline_ghost_disabled'
8
+ | 'outline_danger'
9
+ | 'danger'
10
+ | 'ghost'
11
+ | 'ghost_alert'
12
+ | 'alert'
13
+ | 'danger-no-fill'
14
+ | 'accent-outline'
15
+ | 'accent-fill'
16
+ | 'secondary-outline'
17
+ | 'danger-fill';
18
+ radius?: string;
19
+ isLoading?: boolean;
20
+ leftIcon?: string;
21
+ rightIcon?: string;
22
+ iconSize?: string;
23
+ fontSize?: string;
24
+ width?: string;
25
+ height?: string;
26
+ testId?: string;
27
+ }
@@ -1,13 +1,13 @@
1
- export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
2
- label?: string;
3
- checked?: boolean;
4
- onChange?: () => void;
5
- disabled?: boolean;
6
- }
7
-
8
- export interface CheckboxGroupProps {
9
- options: { label: string; value: string }[];
10
- selected: string[];
11
- setSelected: (values: string[]) => void;
12
- direction?: 'vertical' | 'horizontal';
13
- }
1
+ export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
2
+ label?: string;
3
+ checked?: boolean;
4
+ onChange?: () => void;
5
+ disabled?: boolean;
6
+ }
7
+
8
+ export interface CheckboxGroupProps {
9
+ options: { label: string; value: string }[];
10
+ selected: string[];
11
+ setSelected: (values: string[]) => void;
12
+ direction?: 'vertical' | 'horizontal';
13
+ }
@@ -0,0 +1,50 @@
1
+ import styled from 'styled-components';
2
+ import {
3
+ primitiveSizes,
4
+ semanticBorders,
5
+ semanticColors,
6
+ semanticRadius,
7
+ semanticShadows,
8
+ typographyTokens,
9
+ } from '../../../styles/tokens';
10
+ import CreatableSelect from 'react-select/creatable';
11
+
12
+ export const Container = styled.div`
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: ${primitiveSizes.size.x1};
16
+ width: 100%;
17
+ `;
18
+
19
+ export const Label = styled.label`
20
+ font-size: ${typographyTokens.fontSizes.labelS};
21
+ font-weight: ${typographyTokens.fontWeights.medium};
22
+ color: ${semanticColors.global.text.subtitle.enabled};
23
+ `;
24
+
25
+ export const SupportingText = styled.span<{ $error?: boolean }>`
26
+ font-size: ${typographyTokens.fontSizes.captionM};
27
+ color: ${({ $error }) =>
28
+ $error
29
+ ? semanticColors.global.text.feedback.strong.error
30
+ : semanticColors.global.text.subtitle.enabled};
31
+ `;
32
+
33
+ export const CustomSelect = styled(CreatableSelect)<{
34
+ $error?: boolean;
35
+ $size?: 'sm' | 'md' | 'lg';
36
+ }>`
37
+ .react-select__control {
38
+ min-height: ${primitiveSizes.size.x12};
39
+ border-radius: ${semanticRadius.global.radius.md};
40
+ border: ${semanticBorders.global.sm} solid
41
+ ${({ $error }) =>
42
+ $error
43
+ ? semanticColors.global.border.danger.enabled
44
+ : semanticColors.global.border.medium};
45
+
46
+ &:focus-within {
47
+ box-shadow: ${semanticShadows.shadow.md};
48
+ }
49
+ }
50
+ `;
@@ -0,0 +1,136 @@
1
+ 'use client';
2
+
3
+ import { useMemo, useState } from 'react';
4
+ import type { InputActionMeta } from 'react-select';
5
+ import {
6
+ Container,
7
+ Label,
8
+ SupportingText,
9
+ CustomSelect,
10
+ } from './EditableDurationSelect.styles';
11
+ import {
12
+ DurationOption,
13
+ EditableDurationSelectProps,
14
+ } from './EditableDurationSelect.types';
15
+
16
+ const DAYS_IN_YEAR = 365;
17
+ const BASE_LIMIT = 5;
18
+
19
+ function parseDuration(input: string): DurationOption | null {
20
+ if (!input) return null;
21
+
22
+ const normalized = input.trim().toLowerCase();
23
+
24
+ if (/^\d+$/.test(normalized)) {
25
+ const amount = Number(normalized);
26
+ if (amount <= 0) return null;
27
+
28
+ const isYear = amount <= BASE_LIMIT;
29
+
30
+ return {
31
+ label: isYear
32
+ ? `${amount} ${amount > 1 ? 'anos' : 'ano'}`
33
+ : `${amount} ${amount > 1 ? 'dias' : 'dia'}`,
34
+ value: normalized,
35
+ days: isYear ? amount * DAYS_IN_YEAR : amount,
36
+ };
37
+ }
38
+
39
+ const match = normalized.match(/^(\d+)\s*(dia|dias|ano|anos)$/);
40
+ if (!match) return null;
41
+
42
+ const amount = Number(match[1]);
43
+ const unit = match[2];
44
+
45
+ if (amount <= 0) return null;
46
+
47
+ const isYear = unit.startsWith('ano');
48
+
49
+ return {
50
+ label: `${amount} ${isYear ? (amount > 1 ? 'anos' : 'ano') : amount > 1 ? 'dias' : 'dia'}`,
51
+ value: normalized,
52
+ days: isYear ? amount * DAYS_IN_YEAR : amount,
53
+ };
54
+ }
55
+
56
+ export default function EditableDurationSelect({
57
+ label,
58
+ supportingText,
59
+ value,
60
+ onChange,
61
+ options = [],
62
+ disabled,
63
+ error,
64
+ size = 'md',
65
+ testId,
66
+ baseLimit = BASE_LIMIT,
67
+ }: EditableDurationSelectProps) {
68
+ const [inputValue, setInputValue] = useState('');
69
+
70
+ const parsedInput = useMemo(() => parseDuration(inputValue), [inputValue]);
71
+
72
+ const dynamicOption = useMemo(() => {
73
+ if (!parsedInput) return [];
74
+ return [
75
+ {
76
+ label: parsedInput.label,
77
+ value: parsedInput.value,
78
+ days: parsedInput.days,
79
+ },
80
+ ];
81
+ }, [parsedInput]);
82
+
83
+ const mergedOptions = useMemo(() => {
84
+ return [
85
+ ...dynamicOption.filter((d) => !options.some((o) => o.days === d.days)),
86
+ ...options,
87
+ ];
88
+ }, [dynamicOption, options]);
89
+
90
+ const helperText = useMemo(() => {
91
+ if (!inputValue) return supportingText;
92
+
93
+ if (!parsedInput) return 'Valor inválido';
94
+
95
+ return parsedInput.days > baseLimit * DAYS_IN_YEAR
96
+ ? `Valores acima de ${baseLimit} são considerados dias`
97
+ : `Valores até ${baseLimit} são considerados anos`;
98
+ }, [baseLimit, inputValue, parsedInput, supportingText]);
99
+
100
+ function handleInputChange(value: string, meta: InputActionMeta) {
101
+ if (meta.action === 'input-change') {
102
+ setInputValue(value);
103
+ }
104
+ }
105
+
106
+ function handleChange(option: DurationOption | null) {
107
+ if (!option) return;
108
+ onChange?.(option);
109
+ setInputValue('');
110
+ }
111
+
112
+ return (
113
+ <Container data-testid={testId}>
114
+ {label && <Label>{label}</Label>}
115
+
116
+ <CustomSelect
117
+ isSearchable
118
+ isDisabled={disabled}
119
+ $size={size}
120
+ $error={error}
121
+ options={mergedOptions}
122
+ value={value}
123
+ placeholder="Digite ou selecione"
124
+ onInputChange={handleInputChange}
125
+ onChange={(opt) => handleChange(opt as DurationOption | null)}
126
+ formatCreateLabel={() => null}
127
+ />
128
+
129
+ {helperText && (
130
+ <SupportingText $error={error || helperText === 'Valor inválido'}>
131
+ {helperText}
132
+ </SupportingText>
133
+ )}
134
+ </Container>
135
+ );
136
+ }
@@ -0,0 +1,18 @@
1
+ export type DurationOption = {
2
+ label: string;
3
+ value: string;
4
+ days: number;
5
+ };
6
+
7
+ export interface EditableDurationSelectProps {
8
+ label?: string;
9
+ supportingText?: string;
10
+ value?: DurationOption | null;
11
+ onChange?: (value: DurationOption | null) => void;
12
+ options?: DurationOption[];
13
+ disabled?: boolean;
14
+ error?: boolean;
15
+ size?: 'sm' | 'md' | 'lg';
16
+ testId?: string;
17
+ baseLimit?: number;
18
+ }
@@ -0,0 +1 @@
1
+ export { default } from './EditableDurationSelect';
@@ -1,6 +1,6 @@
1
- export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
2
- label?: string;
3
- supportingText?: string;
4
- testId?: string;
5
- info?: string;
6
- }
1
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
2
+ label?: string;
3
+ supportingText?: string;
4
+ testId?: string;
5
+ info?: string;
6
+ }
@@ -1,60 +1,60 @@
1
- import React, { useState } from 'react';
2
- import {
3
- TooltipContainer,
4
- TooltipTrigger,
5
- TooltipContent,
6
- TooltipText,
7
- TooltipLink,
8
- } from '../Tooltip/Tootip.styles';
9
- import { TooltipProps } from './tooltip.types';
10
- import { ArrowSquareOut } from '@phosphor-icons/react';
11
- import info from '../../../assets/info.png';
12
-
13
- const Tooltip: React.FC<TooltipProps> = ({
14
- children,
15
- content,
16
- position = 'top',
17
- helpLink,
18
- }) => {
19
- const [visible, setVisible] = useState(false);
20
-
21
- const handleMouseEnter = () => {
22
- setVisible(true);
23
- };
24
-
25
- const handleMouseLeave = () => {
26
- setVisible(false);
27
- };
28
-
29
- return (
30
- <TooltipContainer>
31
- {children}
32
- <TooltipTrigger
33
- onMouseEnter={handleMouseEnter}
34
- onMouseLeave={handleMouseLeave}
35
- >
36
- <img src={info} alt="Informação" />
37
- </TooltipTrigger>
38
- <TooltipContent
39
- $position={position}
40
- $visible={visible}
41
- onMouseEnter={handleMouseEnter}
42
- onMouseLeave={handleMouseLeave}
43
- >
44
- <TooltipText>{content}</TooltipText>
45
- {helpLink && (
46
- <TooltipLink
47
- href={helpLink}
48
- target="_blank"
49
- rel="noopener noreferrer"
50
- >
51
- Saiba mais
52
- <ArrowSquareOut size={12} weight="bold" />
53
- </TooltipLink>
54
- )}
55
- </TooltipContent>
56
- </TooltipContainer>
57
- );
58
- };
59
-
60
- export default Tooltip;
1
+ import React, { useState } from 'react';
2
+ import {
3
+ TooltipContainer,
4
+ TooltipTrigger,
5
+ TooltipContent,
6
+ TooltipText,
7
+ TooltipLink,
8
+ } from '../Tooltip/Tootip.styles';
9
+ import { TooltipProps } from './tooltip.types';
10
+ import { ArrowSquareOut } from '@phosphor-icons/react';
11
+ import info from '../../../assets/info.png';
12
+
13
+ const Tooltip: React.FC<TooltipProps> = ({
14
+ children,
15
+ content,
16
+ position = 'top',
17
+ helpLink,
18
+ }) => {
19
+ const [visible, setVisible] = useState(false);
20
+
21
+ const handleMouseEnter = () => {
22
+ setVisible(true);
23
+ };
24
+
25
+ const handleMouseLeave = () => {
26
+ setVisible(false);
27
+ };
28
+
29
+ return (
30
+ <TooltipContainer>
31
+ {children}
32
+ <TooltipTrigger
33
+ onMouseEnter={handleMouseEnter}
34
+ onMouseLeave={handleMouseLeave}
35
+ >
36
+ <img src={info} alt="Informação" />
37
+ </TooltipTrigger>
38
+ <TooltipContent
39
+ $position={position}
40
+ $visible={visible}
41
+ onMouseEnter={handleMouseEnter}
42
+ onMouseLeave={handleMouseLeave}
43
+ >
44
+ <TooltipText>{content}</TooltipText>
45
+ {helpLink && (
46
+ <TooltipLink
47
+ href={helpLink}
48
+ target="_blank"
49
+ rel="noopener noreferrer"
50
+ >
51
+ Saiba mais
52
+ <ArrowSquareOut size={12} weight="bold" />
53
+ </TooltipLink>
54
+ )}
55
+ </TooltipContent>
56
+ </TooltipContainer>
57
+ );
58
+ };
59
+
60
+ export default Tooltip;