diff --git a/packages/volto-govrs-ds/news/23.feature b/packages/volto-govrs-ds/news/23.feature new file mode 100644 index 0000000..0d15831 --- /dev/null +++ b/packages/volto-govrs-ds/news/23.feature @@ -0,0 +1,2 @@ +Implementação do componente Tag dispensáveis ( Default) e Variantes: +Tag Texto, Tag Persistente, Tag Status, Tag Icone e Tag de Contagem(@Gabriel-SAbreu) \ No newline at end of file diff --git a/packages/volto-govrs-ds/src/components/Tag/Tag.stories.jsx b/packages/volto-govrs-ds/src/components/Tag/Tag.stories.jsx new file mode 100644 index 0000000..4d276ea --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/Tag.stories.jsx @@ -0,0 +1,238 @@ +import Tag from './index'; +import Persistente from './variants/Persistente'; +export default { + title: 'Components/Tag', + component: Tag, +}; + +export const TagDocs = () => ( + <> +
+

Tag - Documentação

+

+ O componente Tag permite escolher a variante através da + prop variant. Isso facilita a utilização quando se deseja + alternar entre diferentes tipos de tags dinamicamente. +

+
+

Tag de Interação

+

+ Possuem uma ação ao serem clicadas/tocadas. De acordo com o tipo de + interação podemos ter tags interativas dispensáveis e persistentes. +
+ Tags aceitam as props content, disabled e{' '} + showIcon +

+

Dispensáveis

+
+ + + + +
+
+      {``} 
+ {``} +
+ +

+ Ao utilizar a prop showIcon, a tag não exibe o ícone. +

+ +
+ + +
+ +
+      {``}{' '}
+      
+ {``} +
+ +

Texto

+

+ A tag pode não possuir nenhum tipo de interação, serrvindo apenas para + exibição de um texto, nesse caso, pode possuir icone e texto ou somente + texto. Optando pela segunda opção utilizasse a prop showClose{' '} + como false. +

+ +
+ + + + +
+ +
+      {``}{' '}
+      
+ {``} +
+ {``} +
+ {` `} +
+ +

Persistentes

+

+ Tags persistentes não podem ser dispensadas, apenas indicam um estado ou + categoria. +

+

+ As props content, disabled e{' '} + showIcon são aceitas. +

+ +

+ {' '} + ATENÇÃO: nas tags persistentes que serão selecionadas por padrão, + utilize a prop defaultChecked juntamente com a prop{' '} + disabled. +

+
+ + + +
+ +
+      {``} 
+ {``} +
+ {` +
+ +

Persistente — Grupo (radio)

+

+ Variação para seleção única entre várias tags. Cada tag precisa ter um{' '} + id único. +

+

+ Para selecionar uma tag por padrão, utilize a prop{' '} + defaultSelected com o valor do id da tag + desejada. +

+ +
+ + + + + +
+
+      {``}{' '}
+      
+ {` `} +
+ {` `} +
+ {` `} +
+ {``} +
+ +

Status

+

+ Tags de status são utilizadas para indicar estados ou categorias + específicas. Elas não possuem interação de clique. +

+
+ + + +
+
+      {``}{' '}
+      
+ {``} +
+ {``} +
+ +

+ Podem ser usadas com Label ou apenas com a superfície circular.
{' '} + Para realizar a escolha utilizar a propLabel vazia ou com o + mesmo conteúdo da prop status. +

+
+ + + +
+
+      {``} 
+ {``} +
+ {``} +
+ +

Icone

+

+ Tags de icone possuem a mesma ideia das tags texto, porém utilizando + apenas icone +

+

+ As props ico pode ser utilizada para especificar qual icone + será exibido. +

+
+ + +
+
+      {``} 
+ {``} +
+ +

Contagem

+

+ Tags de contagem são utilizadas para exibir um número ou valor associado a + uma tag. +

+

+ Se o valor for maior que 999, será exibido como "999+". Os valores são + atribuidos através da prop label. +

+
+ + + + + +
+
+      {``} 
+ {``}
+ {``}
+ {``} + {``} +
+ +); diff --git a/packages/volto-govrs-ds/src/components/Tag/index.jsx b/packages/volto-govrs-ds/src/components/Tag/index.jsx new file mode 100644 index 0000000..bbdb0f5 --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/index.jsx @@ -0,0 +1,28 @@ +import Default from './variants/Default.jsx'; +import Persistente from './variants/Persistente'; +import Status from './variants/Status'; +import Icone from './variants/Icone'; +import Contagem from './variants/Contagem'; + +const VARIANTS = { + default: Default, + persistente: Persistente, + persistenteGroup: Persistente.Group, + status: Status, + icone: Icone, + contagem: Contagem, +}; + +export default function Tag({ + items = [], + variant, + itemKey = (i) => i.id, + className = '', + ...rest +}) { + const v = variant || 'default'; + const Variant = VARIANTS[v] || Default; + return ( + + ); +} diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Contagem.jsx b/packages/volto-govrs-ds/src/components/Tag/variants/Contagem.jsx new file mode 100644 index 0000000..162d94a --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Contagem.jsx @@ -0,0 +1,19 @@ +import './Contagem.scss'; + +const Contagem = ({ label }) => { + const n = Number(label); + const displayLabel = Number.isFinite(n) + ? n > 999 + ? '999+' + : String(n) + : label; + return ( + <> +
+ {displayLabel} +
+ + ); +}; + +export default Contagem; diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Contagem.scss b/packages/volto-govrs-ds/src/components/Tag/variants/Contagem.scss new file mode 100644 index 0000000..61beba2 --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Contagem.scss @@ -0,0 +1,14 @@ +.govrs-tag-contagem { + display: inline-flex; + min-width: 24px; + min-height: 24px; + align-items: center; + justify-content: center; + padding: 1px 7px 4px 7px; + border: 1px solid #fff; + border-radius: 999px; + background-color: #d54309; + color: #fff; + font-size: 0.875rem; + gap: 8px; +} diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Default.jsx b/packages/volto-govrs-ds/src/components/Tag/variants/Default.jsx new file mode 100644 index 0000000..42991d8 --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Default.jsx @@ -0,0 +1,87 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import './Default.scss'; + +function CarIcon() { + return ( + + + + ); +} + +function CloseIcon() { + return ( + + + + ); +} +const Default = ({ + disabled = false, + showClose = true, + content = 'Tag Default', + showIcon = true, +}) => { + const [visible, setVisible] = useState(true); + + const handleClose = () => { + if (disabled) return; + setVisible(false); + }; + + if (!visible) return null; + + return ( + <> + + {showIcon && } + {content} + + {showClose && ( + + )} + + + ); +}; + +Default.propTypes = { + disabled: PropTypes.bool, + content: PropTypes.node, + showClose: PropTypes.bool, + showIcon: PropTypes.bool, +}; + +Default.defaultProps = { + disabled: false, + content: 'Tag Default', + showClose: true, + showIcon: true, +}; + +export default Default; diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Default.scss b/packages/volto-govrs-ds/src/components/Tag/variants/Default.scss new file mode 100644 index 0000000..a54c1c3 --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Default.scss @@ -0,0 +1,86 @@ +.govrs-tag { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 4px 8px; + border: none; + border-radius: 4px; + background-color: #1a7235; + color: #fff; + gap: 8px; + + &:not(.disable):hover { + background: linear-gradient( + 0deg, + rgba(255, 255, 255, 0.3) 0%, + rgba(255, 255, 255, 0.3) 100% + ), + #1a7235; + } + &:not(.disable):active { + background: linear-gradient( + 0deg, + rgba(255, 255, 255, 0.65) 0%, + rgba(255, 255, 255, 0.65) 100% + ), + #1a7235; + } + + > button { + padding: 0; + border: none; + background-color: transparent; + color: #fff; + + &:hover { + cursor: pointer; + } + } + + &-group { + display: flex; + align-items: center; + gap: 12px; + } +} + +.disable { + cursor: not-allowed; + opacity: 0.45; + > button { + pointer-events: none; + + &:hover, + &:active { + background-color: #1a7235; + cursor: not-allowed; + } + } +} + +/* Layout para grupos de tags (ex.: Persistente.Group) */ +.govrs-tag-group { + display: flex; + align-items: center; + gap: 12px; +} + +/* Foco visível para acessibilidade */ +.govrs-tag:focus-visible { + border-color: rgba(10, 97, 41, 0.9); + box-shadow: 0 0 0 6px rgba(10, 97, 41, 0.12); + outline: none; +} + +.checked { + background-color: #279b4a; +} + +// Estilo para exibir utilização dos blocos de código nos stories +.showCode { + padding: 12px; + border-radius: 4px; + margin-top: 8px; + background: #f7f7f7; + overflow-x: auto; +} diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Icone.jsx b/packages/volto-govrs-ds/src/components/Tag/variants/Icone.jsx new file mode 100644 index 0000000..b00f3f7 --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Icone.jsx @@ -0,0 +1,68 @@ +import PropTypes from 'prop-types'; +import './Icone.scss'; + +function SearchIcon() { + return ( + + + + ); +} + +function CarIcon() { + return ( + + + + ); +} + +const Icone = ({ ico = 'search' }) => { + const renderIcon = () => { + switch (ico) { + case 'search': + return ; + case 'car': + return ; + default: + return null; + } + }; + + return ( + <> + {renderIcon()} + + ); +}; +Icone.propTypes = { + ico: PropTypes.oneOf(['search', 'car']), + message: PropTypes.string, + children: PropTypes.node, + outline: PropTypes.bool, +}; + +Icone.defaultProps = { + message: null, + children: null, + outline: false, +}; + +export default Icone; diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Icone.scss b/packages/volto-govrs-ds/src/components/Tag/variants/Icone.scss new file mode 100644 index 0000000..23fe11f --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Icone.scss @@ -0,0 +1,15 @@ +@import '../../../theme/variables.scss'; + +.govrs-tag-icon { + display: inline-flex; + max-width: 40px; + min-height: 40px; + align-items: center; + justify-content: center; + padding: 12px; + border: 1px solid #fff; + border-radius: 50%; + background-color: Magenta; + color: #fff; + gap: 12px; +} diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Persistente.jsx b/packages/volto-govrs-ds/src/components/Tag/variants/Persistente.jsx new file mode 100644 index 0000000..0728a2c --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Persistente.jsx @@ -0,0 +1,187 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import './Persistente.scss'; + +function CarIcon() { + return ( + + + + ); +} + +function CheckIcon() { + return ( + + + + ); +} + +const Persistente = ({ + disabled = false, + content = 'Tag Persistente', + checkIcon = true, + showIcon = true, + checked: checkedProp, + defaultChecked = false, + onChange, +}) => { + const [internalChecked, setInternalChecked] = useState(defaultChecked); + const isControlled = checkedProp !== undefined; + const checked = isControlled ? checkedProp : internalChecked; + + const handleCheck = () => { + const newChecked = !checked; + if (!isControlled) setInternalChecked(newChecked); + if (typeof onChange === 'function') onChange(newChecked); + }; + + return ( + <> + + + ); +}; + +Persistente.propTypes = { + disabled: PropTypes.bool, + content: PropTypes.node, + checkIcon: PropTypes.bool, + showIcon: PropTypes.bool, + checked: PropTypes.bool, + defaultChecked: PropTypes.bool, + onChange: PropTypes.func, +}; + +Persistente.defaultProps = { + disabled: false, + content: 'Tag Persistente', + checkIcon: true, + showIcon: true, + defaultChecked: false, + onChange: undefined, +}; + +// Group variant: quando radio=true, gerencia seleção única (radio-like). Se radio=false, renderiza filhos sem gerenciar. +function Group({ + children, + radio = false, + defaultSelected = null, + selected: selectedProp, + onChange, + allowUnselect = true, + ariaLabel, +}) { + const [selected, setSelected] = useState(defaultSelected); + if (!radio) { + return ( +
+ {children} +
+ ); + } + + // const [selected, setSelected] = useState(defaultSelected); + const isControlled = selectedProp !== undefined; + const current = isControlled ? selectedProp : selected; + + const handleChildChange = (id, newChecked) => { + if (!newChecked && allowUnselect) { + if (!isControlled) setSelected(null); + if (typeof onChange === 'function') onChange(null); + return; + } + if (newChecked) { + if (!isControlled) setSelected(id); + if (typeof onChange === 'function') onChange(id); + } + }; + + return ( +
+ {React.Children.map(children, (child) => { + // se o child não tiver id, não alteramos + const id = child.props && child.props.id; + if (!id) return child; + return React.cloneElement(child, { + checkIcon: true, + checked: id === current, + onChange: (newChecked) => handleChildChange(id, newChecked), + role: 'radio', + 'aria-checked': id === current, + className: 'govrs-tag-group-active', + }); + })} +
+ ); +} + +Group.propTypes = { + children: PropTypes.node, + radio: PropTypes.bool, + defaultSelected: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + selected: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + onChange: PropTypes.func, + allowUnselect: PropTypes.bool, + ariaLabel: PropTypes.string, +}; + +Group.defaultProps = { + radio: false, + defaultSelected: null, + onChange: undefined, + allowUnselect: true, + ariaLabel: 'Tag group', +}; + +Persistente.Group = Group; + +export default Persistente; diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Persistente.scss b/packages/volto-govrs-ds/src/components/Tag/variants/Persistente.scss new file mode 100644 index 0000000..4d5b7c9 --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Persistente.scss @@ -0,0 +1,20 @@ +/* Styles específicos para a variante Persistente */ + +/* Layout para grupos de tags (ex.: Persistente.Group) */ +.govrs-tag-group { + display: flex; + align-items: center; + gap: 12px; +} + +/* Estado visual quando a tag está marcada */ +.govrs-tag.checked { + background-color: #279b4a; +} + +/* Foco visível para acessibilidade (aplicável a tags persistentes) */ +.govrs-tag:focus-visible { + border-color: rgba(10, 97, 41, 0.9); + box-shadow: 0 0 0 6px rgba(10, 97, 41, 0.12); + outline: none; +} diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Status.jsx b/packages/volto-govrs-ds/src/components/Tag/variants/Status.jsx new file mode 100644 index 0000000..4a6d25a --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Status.jsx @@ -0,0 +1,23 @@ +import PropTypes from 'prop-types'; +import './Status.scss'; +const Status = ({ status = 'online', Label }) => { + const classes = ['govrs-tag-status', `govrs-tag-status--${status}`]; + return ( + <> +
+ + {Label} +
+ + ); +}; + +Status.propTypes = { + status: PropTypes.oneOf(['Online', 'Offline', 'Ausente']), +}; + +Status.defaultProps = { + status: 'online', +}; + +export default Status; diff --git a/packages/volto-govrs-ds/src/components/Tag/variants/Status.scss b/packages/volto-govrs-ds/src/components/Tag/variants/Status.scss new file mode 100644 index 0000000..8cb2efc --- /dev/null +++ b/packages/volto-govrs-ds/src/components/Tag/variants/Status.scss @@ -0,0 +1,27 @@ +@import '../../../theme/variables.scss'; + +.govrs-tag-status { + display: inline-flex; + width: 24px; + height: 24px; + align-items: center; + justify-content: center; + border: 1px solid var(--gray-2); + border-radius: 50%; + font-family: $font-family-base; + gap: 8px; +} +.govrs-tag-status--Online { + background-color: var(--green-cool-vivid-50); + color: #fcfcfc; +} + +.govrs-tag-status--Offline { + background-color: var(--red-vivid-50); + color: #fcfcfc; +} + +.govrs-tag-status--Ausente { + background-color: var(--gold-vivid-20); + color: #fcfcfc; +}