rharuow-ds 1.9.0 → 1.10.1

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/README.md CHANGED
@@ -44,7 +44,7 @@ Um Design System moderno em React com integração completa ao React Hook Form,
44
44
  ## 🌟 Características
45
45
 
46
46
  - ⚛️ **React 18+** com TypeScript
47
- - 🧩 **15 componentes** prontos para uso (Input, Textarea, Select, AsyncSelect, MultiSelect, MultiAsyncSelect, RadioGroup, Button, Card, Table, Tooltip, Accordion, AsideSheet, ImageInput)
47
+ - 🧩 **17 componentes** prontos para uso (Input, Textarea, Select, AsyncSelect, MultiSelect, MultiAsyncSelect, RadioGroup, Button, Card, Table, Tooltip, Accordion, AsideSheet, Modal, Toaster, ImageInput)
48
48
  - 💡 **Filtro digitável** em componentes Select - Digite para encontrar opções rapidamente
49
49
  - 🔗 **Integração nativa** com React Hook Form
50
50
  - 🎨 **Customização via CSS Variables** - Mude o tema facilmente
@@ -112,6 +112,8 @@ npm install react-hook-form
112
112
  Tooltip,
113
113
  Accordion,
114
114
  AsideSheet,
115
+ Modal,
116
+ Toaster,
115
117
  ImageInput,
116
118
  } from "rharuow-ds";
117
119
 
@@ -199,6 +201,8 @@ npm install react-hook-form
199
201
  Tooltip,
200
202
  Accordion,
201
203
  AsideSheet,
204
+ Modal,
205
+ Toaster,
202
206
  ImageInput,
203
207
  } from "rharuow-ds";
204
208
 
@@ -582,7 +586,443 @@ Veja a story do componente no Storybook para demonstrações e variações (left
582
586
 
583
587
  [Storybook — AsideSheet](https://rharuow.github.io/rharuow-ds-docs/?path=/story/asidesheet--default)
584
588
 
585
- ### 📷 **ImageInput**
589
+ ### 🎭 **Modal**
590
+
591
+ Componente de diálogo modal para exibir conteúdo sobreposto à página principal.
592
+
593
+ - ✅ Overlay com transparência configurável
594
+ - ✅ Múltiplos tamanhos: sm, md, lg, xl, full
595
+ - ✅ Variantes de cor: default, primary, secondary (usando CSS Variables)
596
+ - ✅ Controle de fechamento via overlay, ESC ou botão X
597
+ - ✅ Prevenção de scroll do body quando aberto
598
+ - ✅ Animações suaves de entrada/saída
599
+ - ✅ Sub-componentes para estruturação: Header, Body, Footer
600
+ - ✅ Renderização via Portal (React Portal)
601
+ - ✅ Acessível: role="dialog", aria-modal
602
+
603
+ Props principais:
604
+
605
+ - `open: boolean` — controla visibilidade do modal
606
+ - `onClose: () => void` — callback chamado ao fechar
607
+ - `size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'` — tamanho do modal (padrão: 'md')
608
+ - `variant?: 'default' | 'primary' | 'secondary'` — variante de cor (padrão: 'default')
609
+ - `closeOnOverlayClick?: boolean` — fecha ao clicar fora (padrão: true)
610
+ - `closeOnEscape?: boolean` — fecha ao pressionar ESC (padrão: true)
611
+ - `showCloseButton?: boolean` — exibe botão X de fechar (padrão: true)
612
+ - `className?: string` — classes adicionais para o container do modal
613
+
614
+ Exemplo de uso básico:
615
+
616
+ ```tsx
617
+ import React from 'react';
618
+ import { Modal, Button } from 'rharuow-ds';
619
+
620
+ function Example() {
621
+ const [open, setOpen] = React.useState(false);
622
+
623
+ return (
624
+ <div>
625
+ <Button onClick={() => setOpen(true)}>Abrir Modal</Button>
626
+
627
+ <Modal open={open} onClose={() => setOpen(false)}>
628
+ <h2>Título do Modal</h2>
629
+ <p>Conteúdo do modal aqui.</p>
630
+ </Modal>
631
+ </div>
632
+ );
633
+ }
634
+ ```
635
+
636
+ Exemplo com estrutura completa:
637
+
638
+ ```tsx
639
+ import React from 'react';
640
+ import { Modal, Button } from 'rharuow-ds';
641
+
642
+ function Example() {
643
+ const [open, setOpen] = React.useState(false);
644
+
645
+ return (
646
+ <div>
647
+ <Button onClick={() => setOpen(true)}>Confirmar Ação</Button>
648
+
649
+ <Modal
650
+ open={open}
651
+ onClose={() => setOpen(false)}
652
+ size="md"
653
+ >
654
+ <Modal.Header>
655
+ <h2 className="text-2xl font-bold">Confirmar Exclusão</h2>
656
+ <p className="text-sm text-gray-500">Esta ação não pode ser desfeita</p>
657
+ </Modal.Header>
658
+
659
+ <Modal.Body>
660
+ <p className="text-gray-700">
661
+ Você tem certeza que deseja excluir este item?
662
+ Todos os dados associados serão removidos permanentemente.
663
+ </p>
664
+ </Modal.Body>
665
+
666
+ <Modal.Footer>
667
+ <Button variant="outline" onClick={() => setOpen(false)}>
668
+ Cancelar
669
+ </Button>
670
+ <Button onClick={() => {
671
+ // Executar ação
672
+ setOpen(false);
673
+ }}>
674
+ Confirmar
675
+ </Button>
676
+ </Modal.Footer>
677
+ </Modal>
678
+ </div>
679
+ );
680
+ }
681
+ ```
682
+
683
+ Exemplo com formulário integrado:
684
+
685
+ ```tsx
686
+ import React from 'react';
687
+ import { Modal, Button, Input } from 'rharuow-ds';
688
+ import { FormProvider, useForm } from 'react-hook-form';
689
+
690
+ function FormModal() {
691
+ const [open, setOpen] = React.useState(false);
692
+ const methods = useForm();
693
+
694
+ const onSubmit = (data: any) => {
695
+ console.log('Form data:', data);
696
+ setOpen(false);
697
+ methods.reset();
698
+ };
699
+
700
+ return (
701
+ <div>
702
+ <Button onClick={() => setOpen(true)}>Novo Cadastro</Button>
703
+
704
+ <Modal
705
+ open={open}
706
+ onClose={() => setOpen(false)}
707
+ size="lg"
708
+ closeOnOverlayClick={false}
709
+ >
710
+ <FormProvider {...methods}>
711
+ <form onSubmit={methods.handleSubmit(onSubmit)}>
712
+ <Modal.Header>
713
+ <h2 className="text-2xl font-bold">Cadastrar Usuário</h2>
714
+ </Modal.Header>
715
+
716
+ <Modal.Body>
717
+ <div className="space-y-4">
718
+ <Input label="Nome completo" name="name" required />
719
+ <Input label="E-mail" name="email" type="email" required />
720
+ <Input label="Telefone" name="phone" />
721
+ </div>
722
+ </Modal.Body>
723
+
724
+ <Modal.Footer>
725
+ <Button
726
+ type="button"
727
+ variant="outline"
728
+ onClick={() => setOpen(false)}
729
+ >
730
+ Cancelar
731
+ </Button>
732
+ <Button type="submit">Salvar</Button>
733
+ </Modal.Footer>
734
+ </form>
735
+ </FormProvider>
736
+ </Modal>
737
+ </div>
738
+ );
739
+ }
740
+ ```
741
+
742
+ Exemplo com variantes de cor:
743
+
744
+ ```tsx
745
+ import React from 'react';
746
+ import { Modal, Button } from 'rharuow-ds';
747
+
748
+ function ColorVariants() {
749
+ const [openPrimary, setOpenPrimary] = React.useState(false);
750
+ const [openSecondary, setOpenSecondary] = React.useState(false);
751
+
752
+ return (
753
+ <div>
754
+ {/* Modal com cor primária */}
755
+ <Button onClick={() => setOpenPrimary(true)}>
756
+ Modal Primary
757
+ </Button>
758
+
759
+ <Modal
760
+ open={openPrimary}
761
+ onClose={() => setOpenPrimary(false)}
762
+ variant="primary"
763
+ >
764
+ <Modal.Header>
765
+ <h2 className="text-2xl font-bold">Ação Importante</h2>
766
+ </Modal.Header>
767
+ <Modal.Body>
768
+ <p className="opacity-95">
769
+ Este modal usa as cores primárias da aplicação,
770
+ ideal para destacar ações principais.
771
+ </p>
772
+ </Modal.Body>
773
+ <Modal.Footer>
774
+ <Button variant="outline" onClick={() => setOpenPrimary(false)}>
775
+ Fechar
776
+ </Button>
777
+ </Modal.Footer>
778
+ </Modal>
779
+
780
+ {/* Modal com cor secundária */}
781
+ <Button onClick={() => setOpenSecondary(true)} variant="secondary">
782
+ Modal Secondary
783
+ </Button>
784
+
785
+ <Modal
786
+ open={openSecondary}
787
+ onClose={() => setOpenSecondary(false)}
788
+ variant="secondary"
789
+ >
790
+ <Modal.Header>
791
+ <h2 className="text-2xl font-bold">Aviso</h2>
792
+ </Modal.Header>
793
+ <Modal.Body>
794
+ <p className="opacity-95">
795
+ Este modal usa as cores secundárias da aplicação,
796
+ ideal para avisos e ações alternativas.
797
+ </p>
798
+ </Modal.Body>
799
+ <Modal.Footer>
800
+ <Button variant="secondary" onClick={() => setOpenSecondary(false)}>
801
+ Fechar
802
+ </Button>
803
+ </Modal.Footer>
804
+ </Modal>
805
+ </div>
806
+ );
807
+ }
808
+ ```
809
+
810
+ Veja a story do componente no Storybook para mais exemplos e variações:
811
+
812
+ [Storybook — Modal](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-modal--basic)
813
+
814
+ ### � **Toaster**
815
+
816
+ Sistema completo de notificações toast para feedback ao usuário com múltiplas variantes e posicionamento flexível.
817
+
818
+ - ✅ 5 variantes de toast: success, error, warning, info, default
819
+ - ✅ 6 posições configuráveis na tela
820
+ - ✅ Auto-dismiss com duração customizável
821
+ - ✅ Toast permanente (duration: 0)
822
+ - ✅ Ícones automáticos por variante
823
+ - ✅ Animações suaves de entrada e saída
824
+ - ✅ Limite de toasts simultâneos (padrão: 5)
825
+ - ✅ Callbacks ao fechar
826
+ - ✅ Hook `useToast` para uso simplificado
827
+ - ✅ Gerenciamento via Context API
828
+
829
+ **Configuração inicial:**
830
+
831
+ O Toaster precisa ser configurado uma única vez no nível superior da aplicação:
832
+
833
+ ```tsx
834
+ import React from 'react';
835
+ import { ToasterProvider } from 'rharuow-ds';
836
+
837
+ function App() {
838
+ return (
839
+ <ToasterProvider position="top-right" maxToasts={5}>
840
+ {/* Sua aplicação aqui */}
841
+ <YourApp />
842
+ </ToasterProvider>
843
+ );
844
+ }
845
+ ```
846
+
847
+ Props do `ToasterProvider`:
848
+
849
+ - `position?: ToastPosition` - Posição dos toasts na tela (padrão: 'top-right')
850
+ - Opções: 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'
851
+ - `maxToasts?: number` - Número máximo de toasts simultâneos (padrão: 5)
852
+
853
+ **Uso básico com hook `useToast`:**
854
+
855
+ ```tsx
856
+ import React from 'react';
857
+ import { useToast, Button } from 'rharuow-ds';
858
+
859
+ function MyComponent() {
860
+ const toast = useToast();
861
+
862
+ return (
863
+ <div>
864
+ <Button onClick={() => toast.success('Operação realizada com sucesso!')}>
865
+ Success
866
+ </Button>
867
+
868
+ <Button onClick={() => toast.error('Erro ao processar requisição')}>
869
+ Error
870
+ </Button>
871
+
872
+ <Button onClick={() => toast.warning('Atenção: verifique os dados')}>
873
+ Warning
874
+ </Button>
875
+
876
+ <Button onClick={() => toast.info('Você tem 3 novas mensagens')}>
877
+ Info
878
+ </Button>
879
+ </div>
880
+ );
881
+ }
882
+ ```
883
+
884
+ **Toasts com duração customizada:**
885
+
886
+ ```tsx
887
+ import React from 'react';
888
+ import { useToast, Button } from 'rharuow-ds';
889
+
890
+ function CustomDuration() {
891
+ const toast = useToast();
892
+
893
+ return (
894
+ <div>
895
+ {/* Toast rápido - 2 segundos */}
896
+ <Button onClick={() => toast.success('Toast rápido', 2000)}>
897
+ 2 Segundos
898
+ </Button>
899
+
900
+ {/* Toast longo - 10 segundos */}
901
+ <Button onClick={() => toast.info('Toast longo', 10000)}>
902
+ 10 Segundos
903
+ </Button>
904
+
905
+ {/* Toast permanente - não fecha automaticamente */}
906
+ <Button
907
+ onClick={() => toast.toast('Toast permanente', { duration: 0 })}
908
+ >
909
+ Permanente
910
+ </Button>
911
+ </div>
912
+ );
913
+ }
914
+ ```
915
+
916
+ **Toast com callback ao fechar:**
917
+
918
+ ```tsx
919
+ import React from 'react';
920
+ import { useToaster, Button } from 'rharuow-ds';
921
+
922
+ function WithCallback() {
923
+ const { addToast } = useToaster();
924
+
925
+ const handleAction = () => {
926
+ addToast({
927
+ message: 'Processando dados...',
928
+ variant: 'info',
929
+ duration: 3000,
930
+ onClose: () => {
931
+ console.log('Toast fechado!');
932
+ // Executar ação após fechamento
933
+ performNextAction();
934
+ },
935
+ });
936
+ };
937
+
938
+ return <Button onClick={handleAction}>Iniciar Processo</Button>;
939
+ }
940
+ ```
941
+
942
+ **Exemplo completo em um formulário:**
943
+
944
+ ```tsx
945
+ import React from 'react';
946
+ import { useForm, FormProvider } from 'react-hook-form';
947
+ import { Input, Button, useToast } from 'rharuow-ds';
948
+
949
+ function FormWithToast() {
950
+ const methods = useForm();
951
+ const toast = useToast();
952
+
953
+ const onSubmit = async (data: any) => {
954
+ try {
955
+ // Simular chamada à API
956
+ await saveData(data);
957
+
958
+ toast.success('Dados salvos com sucesso!');
959
+ methods.reset();
960
+ } catch (error) {
961
+ toast.error('Erro ao salvar dados. Tente novamente.');
962
+ console.error(error);
963
+ }
964
+ };
965
+
966
+ return (
967
+ <FormProvider {...methods}>
968
+ <form onSubmit={methods.handleSubmit(onSubmit)}>
969
+ <Input label="Nome" name="name" required />
970
+ <Input label="E-mail" name="email" type="email" required />
971
+
972
+ <Button type="submit">Salvar</Button>
973
+ </form>
974
+ </FormProvider>
975
+ );
976
+ }
977
+ ```
978
+
979
+ **API do hook `useToast`:**
980
+
981
+ ```typescript
982
+ const toast = useToast();
983
+
984
+ // Métodos disponíveis:
985
+ toast.success(message: string, duration?: number)
986
+ toast.error(message: string, duration?: number)
987
+ toast.warning(message: string, duration?: number)
988
+ toast.info(message: string, duration?: number)
989
+ toast.toast(message: string, options?: ToastOptions)
990
+ ```
991
+
992
+ **API avançada com `useToaster`:**
993
+
994
+ ```typescript
995
+ const { addToast, removeToast, clearAll, toasts } = useToaster();
996
+
997
+ // Adicionar toast com controle total
998
+ const id = addToast({
999
+ message: 'Mensagem personalizada',
1000
+ variant: 'success',
1001
+ duration: 5000,
1002
+ onClose: () => console.log('Fechado'),
1003
+ });
1004
+
1005
+ // Remover toast específico
1006
+ removeToast(id);
1007
+
1008
+ // Limpar todos os toasts
1009
+ clearAll();
1010
+ ```
1011
+
1012
+ **Dicas de uso:**
1013
+
1014
+ - Use `success` para operações bem-sucedidas (save, delete, update)
1015
+ - Use `error` para falhas e erros
1016
+ - Use `warning` para avisos que requerem atenção
1017
+ - Use `info` para informações gerais
1018
+ - Configure `duration: 0` para toasts que precisam de ação manual do usuário
1019
+ - Posicione toasts conforme o contexto: top para notificações gerais, bottom para ações específicas
1020
+
1021
+ Veja a story do componente no Storybook para demonstrações interativas:
1022
+
1023
+ [Storybook — Toaster](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-toaster--top-right)
1024
+
1025
+ ### �📷 **ImageInput**
586
1026
 
587
1027
  Componente para seleção e upload de imagens com preview e ações de confirmação/remoção:
588
1028