) => (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 | );
21 |
22 | export default SvgPaymentVisa;
23 |
--------------------------------------------------------------------------------
/internal/MobileNavigation/MobileNavigation.js:
--------------------------------------------------------------------------------
1 | import { Dropdown, DropdownItem, Button, MacPawLogo } from "../../src/ui";
2 | import styles from "./mobileNavigation.module.css";
3 | import Link from "next/link";
4 | import { pages } from "../config/pages";
5 | import ActiveLink from "../ActiveLink/ActiveLink";
6 |
7 | const MobileNavigation = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 | }
21 | position="right"
22 | className={styles.dropdown}
23 | >
24 |
30 | Installation
31 |
32 | {[...pages].sort().map((link) => (
33 |
40 | {link.replaceAll("-", " ")}
41 |
42 | ))}
43 |
44 |
45 | );
46 | };
47 |
48 | export default MobileNavigation;
49 |
--------------------------------------------------------------------------------
/src/Table/Table.scss:
--------------------------------------------------------------------------------
1 | .table {
2 | border-collapse: separate;
3 | border-spacing: 0;
4 | width: 100%;
5 | font-size: 14px;
6 |
7 | th, td {
8 | padding: 32px 18px;
9 |
10 | &:first-child,
11 | &:last-child {
12 | border-bottom: none;
13 | width: 36px;
14 | }
15 |
16 | &:nth-child(2) {
17 | padding-left: 0;
18 | }
19 |
20 | &:nth-last-child(2) {
21 | padding-right: 0;
22 | }
23 | }
24 |
25 | th {
26 | padding-bottom: 16px;
27 | font-size: 12px;
28 | font-weight: 400;
29 | color: var(--color-black-48);
30 | text-align: left;
31 | }
32 |
33 | td {
34 | border-bottom: 2px solid var(--color-black-08);
35 | }
36 |
37 | tr:last-child {
38 | td {
39 | border-bottom: none;
40 | }
41 | }
42 |
43 | tbody {
44 | td {
45 | &:first-child {
46 | border-left: 2px solid var(--color-black-08);
47 | }
48 | &:last-child {
49 | border-right: 2px solid var(--color-black-08);
50 | }
51 | }
52 | tr {
53 | &:first-child td {
54 | border-top: 2px solid var(--color-black-08);
55 | &:first-child {
56 | border-top-left-radius: 50px;
57 | }
58 | &:last-child {
59 | border-top-right-radius: 50px;
60 | }
61 | }
62 | &:last-child td {
63 | border-bottom: 2px solid var(--color-black-08);
64 | &:first-child {
65 | border-bottom-left-radius: 50px;
66 | }
67 | &:last-child {
68 | border-bottom-right-radius: 50px;
69 | }
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/Icons/svg/help_icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/pages/docs/typography.mdx:
--------------------------------------------------------------------------------
1 | import { Label } from '../../src/ui';
2 |
3 | # Typography
4 |
5 | H1 Title 40px
6 | H2 Title 32px
7 | H3 Title 24px
8 | H4 Title 20px
9 | H5 Title 16px
10 | H6 Title 14px
11 |
12 | ```
13 | H1 Title 40px
14 | H2 Title 32px
15 | H3 Title 24px
16 | H4 Title 20px
17 | H5 Title 16px
18 | H6 Title 14px
19 | ```
20 |
21 | ## Subtitle
22 |
23 | H2 Subtitle 32px
24 | H3 Subtitle 24px
25 | H4 Subtitle 20px
26 |
27 | ```
28 | H2 Subtitle 32px
29 | H3 Subtitle 24px
30 | H4 Subtitle 20px
31 | ```
32 |
33 | ## Paragraph
34 |
35 | p1 The quick brown fox jumps over the lazy dog. 1234567890 18px
36 | p2 The quick brown fox jumps over the lazy dog. 1234567890 16px
37 | p3 The quick brown fox jumps over the lazy dog. 1234567890 14px
38 | p4 The quick brown fox jumps over the lazy dog. 1234567890 12px
39 |
40 | ```
41 | p1 The quick brown fox jumps over the lazy dog. 1234567890 18px
42 | p2 The quick brown fox jumps over the lazy dog. 1234567890 16px
43 | p3 The quick brown fox jumps over the lazy dog. 1234567890 14px
44 | p4 The quick brown fox jumps over the lazy dog. 1234567890 12px
45 | ```
46 |
47 | ## Label
48 |
49 | Date
50 |
51 | ```
52 | Date
53 | ```
54 |
--------------------------------------------------------------------------------
/internal/Palette/Palette.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './Palette.module.css';
3 |
4 | const getTextColor = (value) => {
5 | const darkText = [
6 | '--color-white',
7 | '--color-attention',
8 | ];
9 | return darkText.includes(value) ? '#000' : '#fff';
10 | };
11 |
12 | export default function Palette ({ items }) {
13 | const withGradients = items.find((item) => item.gradient);
14 |
15 | return (
16 |
17 | {items.map((item) => {
18 | const { name, color, gradient } = item;
19 | const colorStyle = {
20 | backgroundColor: `var(${color[0]})`,
21 | color: getTextColor(color[0]),
22 | };
23 | let gradientStyle;
24 | if (gradient) {
25 | gradientStyle = {
26 | backgroundImage: `var(${gradient[0]})`,
27 | };
28 | }
29 | return (
30 |
31 |
32 |
33 | {color[1] ?? ''}
34 |
35 | {gradient && (
36 |
37 |
{gradient[1]}
38 |
{gradient[2]}
39 |
40 | )}
41 |
42 |
{name}
43 |
44 | );
45 | })}
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/src/Switch/Switch.scss:
--------------------------------------------------------------------------------
1 | .switch {
2 | display: inline-flex;
3 | position: relative;
4 | user-select: none;
5 | align-items: center;
6 |
7 | &.-error {
8 | color: var(--color-error);
9 |
10 | .switch-track {
11 | box-shadow: 0 0 0 2px var(--color-error);
12 | }
13 | }
14 |
15 | input {
16 | position: absolute;
17 | opacity: 0;
18 | z-index: 1;
19 | cursor: pointer;
20 | width: 106%;
21 | height: 106%;
22 | margin: 0;
23 | left: -3%;
24 | top: -3%;
25 |
26 | &:checked + .switch-track {
27 | background-color: var(--color-primary);
28 | }
29 |
30 | &:checked + .switch-track .switch-thumb {
31 | transform: translateX(16px);
32 | }
33 |
34 | &:focus + .switch-track {
35 | border-color: var(--color-secondary);
36 | box-shadow: 0 0 0 3px var(--color-outline);
37 | }
38 |
39 | &[disabled] {
40 | cursor: not-allowed;
41 | }
42 |
43 | &[disabled] + .switch-track {
44 | background-color: var(--color-black-16);
45 | }
46 | }
47 |
48 | .switch-track {
49 | width: 32px;
50 | height: 16px;
51 | border-radius: 10px;
52 | background-color: var(--color-black-16);
53 | position: relative;
54 | display: inline-flex;
55 | transition: 0.1s background-color ease-in-out;
56 |
57 | & + * {
58 | margin-left: 8px;
59 | }
60 | }
61 |
62 | .switch-thumb {
63 | width: 12px;
64 | height: 12px;
65 | border-radius: 50%;
66 | background-color: var(--color-white);
67 | display: inline-flex;
68 | margin: 2px;
69 | transform: translateX(0);
70 | transition: 0.1s transform ease-in-out;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "@macpaw/eslint-config-react",
4 | "@macpaw/eslint-config-typescript",
5 | "@macpaw/eslint-config-prettier"
6 | ],
7 | "rules": {
8 | "no-param-reassign": [2, { "props": true, "ignorePropertyModificationsForRegex": ["(R|r)ef"] }],
9 | "no-multiple-empty-lines": [2, { "max": 1, "maxBOF": 0, "maxEOF": 0 }],
10 | "import/order": [2, {
11 | "alphabetize": {
12 | "order": "asc",
13 | "caseInsensitive": true
14 | },
15 | "pathGroups": [
16 | {
17 | "pattern": "react",
18 | "group": "builtin",
19 | "position": "before"
20 | },
21 | {
22 | "pattern": "react*",
23 | "group": "builtin"
24 | },
25 | {
26 | "pattern": "@macpaw/**",
27 | "group": "internal",
28 | "position": "before"
29 | },
30 | {
31 | "pattern": "*.sass",
32 | "group": "index",
33 | "position": "after"
34 | }
35 | ],
36 | "pathGroupsExcludedImportTypes": ["react"],
37 | "groups": [
38 | "builtin",
39 | "external",
40 | "internal",
41 | "parent",
42 | "sibling",
43 | "index",
44 | "object",
45 | "type"
46 | ]
47 | }],
48 | "@typescript-eslint/no-explicit-any": 2,
49 | "no-magic-numbers": 0,
50 | "@typescript-eslint/no-magic-numbers": [
51 | 2,
52 | {
53 | "ignore": [0, 1],
54 | "ignoreEnums": true,
55 | "ignoreNumericLiteralTypes": true,
56 | "ignoreTypeIndexes": true
57 | }
58 | ],
59 | "import/no-cycle": 2,
60 | "react/react-in-jsx-scope": 0
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Pagination/Pagination.scss:
--------------------------------------------------------------------------------
1 | .pagination {
2 | display: flex;
3 | width: fit-content;
4 | margin: 36px auto;
5 | padding-top: 14px;
6 | line-height: 1;
7 | font-size: 16px;
8 |
9 | &-page {
10 | display: inline-flex;
11 | margin: 0 2px;
12 | width: 48px;
13 | height: 48px;
14 | align-items: center;
15 | justify-content: center;
16 | border-radius: 50%;
17 | color: var(--color-black);
18 | text-decoration: none;
19 | box-shadow: inset 0 0 0 2px transparent;
20 | transition: .1s ease-in-out box-shadow;
21 |
22 | &:not(.-disabled):hover {
23 | box-shadow: inset 0 0 0 2px var(--color-black-16);
24 | }
25 |
26 | &.-active {
27 | background: var(--color-black);
28 | color: var(--color-white);
29 | font-weight: 900;
30 | }
31 | }
32 |
33 | &-nav {
34 | display: inline-flex;
35 | align-items: center;
36 | color: var(--color-black);
37 | text-decoration: none;
38 |
39 | &:first-child {
40 | margin-right: 16px;
41 | }
42 |
43 | &:last-child {
44 | margin-left: 16px;
45 | }
46 |
47 | svg {
48 | transition: .2s ease-in-out transform;
49 | }
50 |
51 | &.-prev {
52 | &:not(.-disabled):hover svg {
53 | transform: translateX(-4px);
54 | }
55 |
56 | svg {
57 | margin-right: 12px;
58 | }
59 | }
60 |
61 | &.-next {
62 | &:not(.-disabled):hover svg {
63 | transform: rotate(180deg) translateX(-4px);
64 | }
65 |
66 | svg {
67 | margin-left: 12px;
68 | transform: rotate(180deg);
69 | }
70 | }
71 |
72 | &.-disabled {
73 | pointer-events: none;
74 | opacity: .5;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Grid/Grid.scss:
--------------------------------------------------------------------------------
1 | .grid {
2 | @include mobile {
3 | padding: 0 2px 2px;
4 | }
5 |
6 | &-layout {
7 | position: relative;
8 | display: flex;
9 | @include mobile {
10 | padding-top: 48px;
11 | flex-direction: column;
12 | align-items: stretch;
13 | }
14 | }
15 |
16 | &-rows {
17 | flex: 1;
18 | display: flex;
19 | flex-direction: column;
20 | }
21 |
22 | &-icon {
23 | margin-right: 32px;
24 | display: inline-flex;
25 | align-self: flex-start;
26 | @include mobile {
27 | position: absolute;
28 | top: 8px;
29 | left: 12px;
30 | }
31 |
32 | picture, img, svg {
33 | width: 80px;
34 | height: 80px;
35 | vertical-align: top;
36 | @include mobile {
37 | width: 32px;
38 | height: 32px;
39 | }
40 | }
41 | }
42 |
43 | &-action {
44 | margin-top: 16px;
45 | margin-left: 32px;
46 | width: 48px;
47 | height: 48px;
48 | align-self: flex-start;
49 | display: flex;
50 | justify-content: center;
51 | align-items: center;
52 | @include mobile {
53 | position: absolute;
54 | top: 0;
55 | right: 0;
56 | margin: 0;
57 | }
58 | }
59 |
60 | &-notification {
61 | &:not(:empty) {
62 | margin-top: 24px;
63 | @include mobile {
64 | margin-top: 2px;
65 | }
66 | }
67 | }
68 |
69 | .label {
70 | @include mobile {
71 | width: 30%;
72 | margin-bottom: 0;
73 | margin-right: 8px;
74 | flex-shrink: 0;
75 | }
76 | }
77 |
78 | .banner {
79 | @include mobile {
80 | margin-left: -2px;
81 | margin-right: -2px;
82 | margin-bottom: -2px;
83 | border-radius: 0 0 24px 24px;
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/LanguageSwitcher/LanguageSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, HTMLAttributes } from 'react';
2 | import cx from 'clsx';
3 | import DropdownIcon from '../Icons/jsx/DropdownIcon';
4 | import LanguageIcon from '../LanguageIcon/LanguageIcon';
5 |
6 | const LocaleNames = {
7 | en: 'English',
8 | de: 'Deutsch',
9 | es: 'Español',
10 | fr: 'Français',
11 | it: 'Italiano',
12 | ja: '日本語',
13 | ko: '한국어',
14 | nl: 'Dutch',
15 | pl: 'Polski',
16 | pt: 'Português do Brasil',
17 | tr: 'Turkish',
18 | uk: 'Українська',
19 | zh: '繁體中文',
20 | };
21 |
22 | export type SupportedLocale = keyof typeof LocaleNames;
23 |
24 | const getLocaleName = (locale: SupportedLocale) => {
25 | return LocaleNames[locale] || '';
26 | };
27 |
28 | export interface LanguageSwitcherProps extends HTMLAttributes {
29 | currentLanguage: SupportedLocale;
30 | availableLanguages: SupportedLocale[];
31 | }
32 |
33 | const LanguageSwitcher: FC> = (props) => {
34 | const { currentLanguage, availableLanguages, className, ...other } = props;
35 |
36 | return (
37 |
38 |
39 |
{getLocaleName(currentLanguage)}
40 |
41 |
42 |
43 | {availableLanguages.map((lang) => (
44 |
45 | {getLocaleName(lang)}
46 |
47 | ))}
48 |
49 |
50 |
51 | );
52 | };
53 |
54 | export default LanguageSwitcher;
55 |
--------------------------------------------------------------------------------
/src/Typography/Typography.scss:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Montserrat', 'Helvetica Neue', sans-serif;
3 | font-size: 16px;
4 | line-height: 1.75;
5 | color: var(--color-black);
6 | }
7 |
8 | a {
9 | color: var(--color-secondary);
10 | text-decoration: none;
11 | }
12 |
13 | b, strong {
14 | font-weight: 600;
15 | }
16 |
17 | /* heading */
18 |
19 | h1, .h1,
20 | h2, .h2,
21 | h3, .h3,
22 | h4, .h4,
23 | h5, .h5,
24 | h6, .h6 {
25 | line-height: 1.5;
26 | margin: 0 0 0.5em;
27 | }
28 |
29 | h1, .h1 {
30 | font-size: 40px;
31 | font-weight: 700;
32 | }
33 |
34 | h2, .h2 {
35 | font-size: 32px;
36 | font-weight: 700;
37 | margin-bottom: 0.25em;
38 | }
39 |
40 | h3, .h3 {
41 | font-size: 24px;
42 | font-weight: 700;
43 | }
44 |
45 | h4, .h4 {
46 | font-size: 20px;
47 | font-weight: 700;
48 | }
49 |
50 | h5, .h5 {
51 | font-size: 16px;
52 | font-weight: 600;
53 | }
54 |
55 | h6, .h6 {
56 | font-size: 14px;
57 | font-weight: 600;
58 | }
59 |
60 | /* sub-title */
61 |
62 | .subtitle-h2,
63 | .subtitle-h3,
64 | .subtitle-h4 {
65 | line-height: 1.5;
66 | font-weight: 400;
67 | margin: 0 0 0.5em;
68 | }
69 |
70 | .subtitle-h2 {
71 | font-size: 32px;
72 | }
73 |
74 | .subtitle-h3 {
75 | font-size: 24px;
76 | }
77 |
78 | .subtitle-h4 {
79 | font-size: 20px;
80 | }
81 |
82 | /* paragraph */
83 |
84 | .p1, .p2, .p3, .p4 {
85 | line-height: 1.5;
86 | font-weight: 400;
87 | }
88 |
89 | .p1 {
90 | font-size: 18px;
91 | }
92 |
93 | .p2 {
94 | font-size: 16px;
95 | }
96 |
97 | .p3 {
98 | font-size: 14px;
99 | }
100 |
101 | .p4 {
102 | font-size: 12px;
103 | }
104 |
--------------------------------------------------------------------------------
/src/ui.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap');
2 | @import '~react-toastify/dist/ReactToastify.min.css';
3 | @import './helpers.scss';
4 | @import './Normalize/Normalize.scss';
5 | @import './Breadcrumbs/Breadcrumbs.scss';
6 | @import './Banner/Banner.scss';
7 | @import './Button/Button.scss';
8 | @import './CardMask/CardMask.scss';
9 | @import './Colors/Colors.scss';
10 | @import './Checkbox/Checkbox.scss';
11 | @import './Dialog/Dialog.scss';
12 | @import './DialogActions/DialogActions.scss';
13 | @import './DialogContent/DialogContent.scss';
14 | @import './DialogIcon/DialogIcon.scss';
15 | @import './Dropdown/Dropdown.scss';
16 | @import './DropdownItem/DropdownItem.scss';
17 | @import './FormRow/FormRow.scss';
18 | @import './Grid/Grid.scss';
19 | @import './MacPawLogo/MacPawLogo.scss';
20 | @import './GridCell/GridCell.scss';
21 | @import './GridRow/GridRow.scss';
22 | @import './Input/Input.scss';
23 | @import './Hint/Hint.scss';
24 | @import './Label/Label.scss';
25 | @import './LanguageSwitcher/LanguageSwitcher.scss';
26 | @import './Layout/Layout.scss';
27 | @import './Loader/Loader.scss';
28 | @import './Multiselect/Multiselect.scss';
29 | @import './Notification/Notification.scss';
30 | @import './Pagination/Pagination.scss';
31 | @import './Panel/Panel.scss';
32 | @import './Radio/Radio.scss';
33 | @import './Select/Select.scss';
34 | @import './Tag/Tag.scss';
35 | @import './TagList/TagList.scss';
36 | @import './Table/Table.scss';
37 | @import './Tooltip/Tooltip.scss';
38 | @import './Typography/Typography.scss';
39 | @import './TagInput/TagInput.scss';
40 | @import './Switch/Switch.scss';
41 | @import './Clipboard/Clipboard.scss';
42 | @import './LanguageIcon/LanguageIcon.scss';
43 | @import './DatePicker/DatePicker.scss';
44 |
--------------------------------------------------------------------------------
/src/Icons/svg/payment_ideal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/ModalLauncher/ModalLauncher.tsx:
--------------------------------------------------------------------------------
1 | import React, { LazyExoticComponent, Suspense } from 'react';
2 | import isEmpty from 'lodash.isempty';
3 | import Dialog from '../Dialog/Dialog';
4 | import { Maybe } from '../types';
5 | import { useModalLauncherContext } from './ModalLauncherContext';
6 | import { ModalItem } from './types';
7 |
8 | type ModalBodyComponent =
9 | | Maybe>
10 | | LazyExoticComponent>;
11 |
12 | const ModalLauncher = () => {
13 | const { modals, modalComponents, closeModal, ModalFallback } = useModalLauncherContext();
14 |
15 | return (
16 | <>
17 | {!isEmpty(modals) &&
18 | modals.map((modal: ModalItem) => {
19 | if (!modal.name) {
20 | throw new Error('ModalLauncher: Modal object should have "name" property');
21 | }
22 |
23 | const ModalBodyComponent = modalComponents?.[
24 | modal.name as keyof typeof modalComponents
25 | ] as ModalBodyComponent;
26 |
27 | const handleClose = () => {
28 | if (modal?.onCloseModal) modal?.onCloseModal();
29 |
30 | closeModal(modal.name!);
31 | };
32 |
33 | if (!ModalBodyComponent) return null;
34 |
35 | return (
36 |
43 | : ''}>
44 |
45 |
46 |
47 | );
48 | })}
49 | >
50 | );
51 | };
52 |
53 | export default ModalLauncher;
54 |
--------------------------------------------------------------------------------
/src/Icons/jsx/HelpIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | const SvgHelpIcon = (props: React.SVGProps) => (
4 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
32 | export default SvgHelpIcon;
33 |
--------------------------------------------------------------------------------
/src/Dialog/Dialog.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, HTMLAttributes, useEffect } from 'react';
2 | import cx from 'clsx';
3 | import CloseIcon from '../Icons/jsx/CloseIcon';
4 |
5 | export interface DialogProps extends HTMLAttributes {
6 | isOpen: boolean;
7 | shouldCloseOnEsc?: boolean;
8 | shouldCloseOnOverlayClick?: boolean;
9 | withCloseButton?: boolean;
10 | onRequestClose: () => void;
11 | }
12 |
13 | const Dialog: FC> = (props) => {
14 | const {
15 | isOpen,
16 | shouldCloseOnEsc = true,
17 | shouldCloseOnOverlayClick = true,
18 | onRequestClose,
19 | withCloseButton = true,
20 | className,
21 | children,
22 | ...other
23 | } = props;
24 |
25 | const dialogClassName = cx('dialog', className);
26 |
27 | const keyListener = (event: KeyboardEvent) => {
28 | if (event.key === 'Escape') onRequestClose();
29 | };
30 |
31 | const overlayClickListener = () => {
32 | if (!shouldCloseOnOverlayClick) return;
33 |
34 | onRequestClose();
35 | };
36 |
37 | useEffect(() => {
38 | if (shouldCloseOnEsc) document.addEventListener('keydown', keyListener);
39 |
40 | return () => {
41 | document.removeEventListener('keydown', keyListener);
42 | };
43 | }, [shouldCloseOnEsc]); // eslint-disable-line react-hooks/exhaustive-deps
44 |
45 | if (!isOpen) return null;
46 |
47 | return (
48 |
49 |
50 |
51 | {children}
52 | {withCloseButton && (
53 |
54 |
55 |
56 | )}
57 |
58 |
59 | );
60 | };
61 |
62 | export default Dialog;
63 |
--------------------------------------------------------------------------------
/src/Clipboard/Clipboard.tsx:
--------------------------------------------------------------------------------
1 | import React, { MutableRefObject, useEffect, useRef, useState } from 'react';
2 | import { CheckCircleIcon, CopyIcon as CopyIconUi } from '../Icons/jsx';
3 | import Tooltip from '../Tooltip/Tooltip';
4 | import { InputValueType } from '../types';
5 |
6 | interface ClipboardProps {
7 | copy?: React.ReactElement | string;
8 | element: MutableRefObject;
9 | onCopyEvent?: (value: InputValueType) => void;
10 | }
11 |
12 | const TIMEOUT = 1600;
13 |
14 | const Clipboard: React.FC = ({ copy, element, onCopyEvent }) => {
15 | const [canBeCopied, setCanBeCopied] = useState(true);
16 | // eslint-disable-next-line no-undefined
17 | const timerRef = useRef(undefined);
18 |
19 | useEffect(() => {
20 | if (!canBeCopied) timerRef.current = setTimeout(() => setCanBeCopied(true), TIMEOUT);
21 |
22 | return () => {
23 | if (timerRef.current) clearTimeout(timerRef.current);
24 | };
25 | }, [canBeCopied]);
26 |
27 | const iconHandler = () => {
28 | if (element.current?.value) {
29 | onCopyEvent?.(element.current?.value as string);
30 | navigator.clipboard.writeText(element.current?.value as string);
31 | setCanBeCopied(false);
32 | }
33 | };
34 |
35 | const CopyButton = () => (
36 |
37 |
38 |
39 | );
40 |
41 | const CopyIcon = () =>
42 | copy ? (
43 |
44 |
45 |
46 | ) : (
47 |
48 | );
49 |
50 | return (
51 | {canBeCopied ? : }
52 | );
53 | };
54 |
55 | export default Clipboard;
56 |
--------------------------------------------------------------------------------
/src/Notification/Notification.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { ToastContainer, toast, cssTransition } from 'react-toastify';
3 | import Button from '../Button/Button';
4 | import BlockIcon from '../Icons/jsx/BlockIcon';
5 | import CheckIcon from '../Icons/jsx/CheckIcon';
6 | import CloseIcon from '../Icons/jsx/CloseIcon';
7 |
8 | type NotificationType = 'success' | 'error';
9 |
10 | interface Notification {
11 | type: NotificationType;
12 | }
13 |
14 | const transition = cssTransition({
15 | enter: 'notificationIn',
16 | exit: 'notificationOut',
17 | collapseDuration: 750,
18 | });
19 |
20 | const CloseButton = ({ closeToast }: { closeToast?: () => void }) => (
21 |
22 |
23 |
24 | );
25 |
26 | const Notification: FC> = ({ type, children }) => (
27 | <>
28 |
29 | {type === 'success' && }
30 | {type === 'error' && }
31 |
32 | {children}
33 | >
34 | );
35 |
36 | const NotificationsContainer: FC> = () => (
37 |
38 | );
39 |
40 | export default NotificationsContainer;
41 |
42 | export const notify = (text: string | React.ReactNode, type: NotificationType) => {
43 | toast({text} , {
44 | draggable: true,
45 | draggablePercent: 60,
46 | className: `notification -${type}`,
47 | progressClassName: 'notification-progress',
48 | closeButton: ,
49 | });
50 |
51 | return null;
52 | };
53 |
--------------------------------------------------------------------------------
/src/Tabs/TabContext.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import React, { useState, useEffect, createContext, MutableRefObject, PropsWithChildren, useContext } from 'react';
3 | import { ButtonColor } from '../Button/Button';
4 |
5 | export interface TabsContextValue {
6 | activeTab: string;
7 | initialTab?: string;
8 | color: ButtonColor;
9 | activeColor: ButtonColor;
10 | scale?: 'medium' | 'small';
11 | outline?: boolean;
12 | onSelectTab: (tab: string) => void;
13 | }
14 |
15 | const initialContextValue = {
16 | activeTab: '',
17 | initialTab: '',
18 | color: 'transparent',
19 | activeColor: 'secondary',
20 | onSelectTab: (): void => {},
21 | };
22 |
23 | export const TabsContext = createContext(initialContextValue as TabsContextValue);
24 |
25 | export const useTabsContext = (): TabsContextValue => useContext(TabsContext);
26 |
27 | interface TabProviderProps extends Omit, 'activeTab'> {
28 | innerRef?: MutableRefObject;
29 | }
30 |
31 | export const TabsProvider: React.FC> = ({
32 | children,
33 | initialTab,
34 | color = 'transparent',
35 | activeColor = 'secondary',
36 | scale,
37 | outline,
38 | innerRef,
39 | onSelectTab = (): void => {},
40 | }) => {
41 | const [activeTab, setActiveTab] = useState(initialTab || '');
42 |
43 | const handleSelectTab = (tab: string): void => {
44 | onSelectTab?.(tab);
45 | if (tab !== activeTab) setActiveTab(tab);
46 | };
47 |
48 | const providerValue = {
49 | activeTab,
50 | onSelectTab: handleSelectTab,
51 | color,
52 | activeColor,
53 | scale,
54 | outline,
55 | };
56 |
57 | useEffect(() => {
58 | if (!innerRef) return;
59 |
60 | innerRef.current = providerValue;
61 | }, [providerValue]);
62 |
63 | return {children} ;
64 | };
65 |
--------------------------------------------------------------------------------
/src/Tag/Tag.scss:
--------------------------------------------------------------------------------
1 | $spectrum: 'warning', 'primary', 'secondary';
2 |
3 | $main-color-names: (
4 | warning: '--color-error',
5 | primary: '--color-primary',
6 | secondary: '--color-secondary',
7 | );
8 |
9 | $hover-colors: (
10 | warning: #E55D5D,
11 | primary: #2ecf80,
12 | secondary: #3FADFF,
13 | );
14 |
15 | $active-colors: (
16 | warning: #BC3434,
17 | primary: #05a657,
18 | secondary: #1684D6,
19 | );
20 |
21 | .tag {
22 | position: relative;
23 | display: inline-flex;
24 | align-items: center;
25 | font-size: 12px;
26 | font-weight: 600;
27 | line-height: 1.2;
28 | border-radius: 5px;
29 | padding: 5px 15px;
30 | white-space: nowrap;
31 | overflow: hidden;
32 |
33 | @each $color in $spectrum {
34 | &.-#{$color} {
35 | color: var(--color-white);
36 | background-color: var(#{map-get($main-color-names, $color)});
37 |
38 | &.tag-action {
39 | button {
40 | &:not(:disabled):hover {
41 | background-color: map-get($hover-colors, $color);
42 | }
43 |
44 | &:not(:disabled):focus {
45 | background-color: map-get($hover-colors, $color);
46 | }
47 |
48 | &:not(:disabled):active {
49 | background-color: map-get($active-colors, $color);
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
56 | &-action {
57 | padding-left: 29px;
58 |
59 | button {
60 | position: absolute;
61 | left: 0;
62 | top: 0;
63 | display: inline-flex;
64 | align-items: center;
65 | justify-content: center;
66 | color: inherit;
67 | width: 24px;
68 | height: 24px;
69 | padding: 0;
70 | cursor: pointer;
71 | border: none;
72 | box-shadow: none;
73 | outline: none;
74 | background: none;
75 | }
76 |
77 | svg {
78 | width: 16px;
79 | height: 16px;
80 | fill: currentColor;
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Select/Select.scss:
--------------------------------------------------------------------------------
1 | .select {
2 | font-size: 14px;
3 | text-align: left;
4 | select {
5 | appearance: none;
6 | -webkit-appearance: none;
7 | -moz-appearance: none;
8 | display: flex;
9 | font-size: 16px;
10 | text-overflow: ellipsis;
11 | width: 100%;
12 | padding: 12px 36px 13px 18px;
13 | border-radius: 8px;
14 | border: 2px solid var(--color-black-08);
15 | background-color: #fff;
16 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSI3IiB2aWV3Qm94PSIwIDAgMTIgNyI+ICA8cG9seWxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIiIHBvaW50cz0iMyA2IDggMTAgMTMgNiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTIgLTUpIi8+PC9zdmc+);
17 | background-repeat: no-repeat;
18 | background-size: 12px 6px;
19 | background-position: calc(100% - 16px) 50%;
20 | color: #333;
21 | font-family: 'Montserrat', 'Helvetica Neue', sans-serif;
22 | cursor: pointer;
23 | outline: none;
24 | transition: 0.1s border-color;
25 |
26 | &:focus,
27 | &:active {
28 | border-color: var(--color-secondary);
29 | box-shadow: 0 0 0 3px var(--color-outline);
30 | }
31 |
32 | &:not(:focus):not([disabled]):not(.-error):hover {
33 | border-color: var(--color-black);
34 | }
35 |
36 | &[disabled] {
37 | cursor: not-allowed;
38 | background-color: rgba(111, 114, 121, 0.16);
39 | }
40 |
41 | &:not(:valid) {
42 | color: rgba(51, 51, 51, 0.32);
43 | }
44 |
45 | &::-ms-expand {
46 | display: none;
47 | }
48 | }
49 |
50 | &.-medium select {
51 | padding: 8px 15px 9px;
52 | }
53 |
54 | &.-small select {
55 | padding: 7px 14px;
56 | font-size: 12px;
57 | }
58 |
59 | &.-error select {
60 | border-color: var(--color-error);
61 | }
62 |
63 | .hint {
64 | margin-top: 4px;
65 | }
66 |
67 | .h6 {
68 | margin-bottom: 8px;
69 | display: block;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Checkbox/Checkbox.scss:
--------------------------------------------------------------------------------
1 | .checkbox {
2 | display: inline-flex;
3 | align-items: center;
4 | font-size: 12px;
5 | cursor: pointer;
6 | user-select: none;
7 |
8 | &.-error {
9 | color: var(--color-error);
10 |
11 | span {
12 | border-color: var(--color-error);
13 | }
14 | }
15 |
16 | input {
17 | position: absolute;
18 | opacity: 0;
19 | z-index: -1;
20 | margin: 0;
21 |
22 | &:checked + span {
23 | background-color: var(--color-secondary);
24 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSI4IiB2aWV3Qm94PSIwIDAgMTAgOCI+ICA8cGF0aCBmaWxsPSIjRkZGIiBkPSJNOC4yNDA3NTIzMywwLjM0OTE5ODIwNiBDOC42MDAxODAyNCwtMC4wNzAxMjI3MDM5IDkuMjMxNDgwODgsLTAuMTE4Njc1NTc2IDkuNjUwODAxNzksMC4yNDA3NTIzMyBDMTAuMDcwMTIyNywwLjYwMDE4MDIzNiAxMC4xMTg2NzU2LDEuMjMxNDgwODggOS43NTkyNDc2NywxLjY1MDgwMTc5IEw0LjYxNjI0NzY3LDcuNjUwODAxNzkgQzQuMjQxMTg2MjgsOC4wODgzNjEyNiAzLjU3NDg3MjUyLDguMTE5MTY2MzkgMy4xNjEwMzg2OCw3LjcxODA3OTI3IEwwLjMwNDAzODY3OCw0Ljk0OTA3OTI3IEMtMC4wOTI1NDU1NTIzLDQuNTY0NzEwNDQgLTAuMTAyNDQ4MDk1LDMuOTMxNjIyOTEgMC4yODE5MjA3MywzLjUzNTAzODY4IEMwLjY2NjI4OTU1NSwzLjEzODQ1NDQ1IDEuMjk5Mzc3MDksMy4xMjg1NTE5MSAxLjY5NTk2MTMyLDMuNTEyOTIwNzMgTDMuNzg5NTk2NjgsNS41NDIwNjg4OSBMOC4yNDA3NTIzMywwLjM0OTE5ODIwNiBaIi8+PC9zdmc+);
25 | background-repeat: no-repeat;
26 | background-position: center center;
27 | }
28 |
29 | &:focus + span {
30 | border-color: var(--color-secondary);
31 | box-shadow: 0 0 0 3px var(--color-outline);
32 | }
33 |
34 | &[disabled] + span {
35 | background: var(--color-black-04);
36 | cursor: not-allowed;
37 | }
38 | }
39 |
40 | span {
41 | display: block;
42 | position: relative;
43 | top: -1px;
44 | flex-shrink: 0;
45 | margin-right: 8px;
46 | width: 16px;
47 | height: 16px;
48 | border: 2px solid rgba(111, 114, 121, 0.16);
49 | border-radius: 4px;
50 | background: #fff;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/TagInput/TagInput.scss:
--------------------------------------------------------------------------------
1 | .tag-input {
2 | display: block;
3 | position: relative;
4 | text-align: left;
5 |
6 | &-label {
7 | display: block;
8 |
9 | &:hover + .tagList {
10 | border-color: var(--color-black);
11 | }
12 | }
13 |
14 | &.-error {
15 | .tagList {
16 | border-color: var(--color-error);
17 | }
18 | }
19 |
20 | &.-readonly {
21 | .tag-input-label,
22 | .tagList {
23 | cursor: inherit;
24 | }
25 | .tagList:hover {
26 | border-color: var(--color-black-04);
27 | }
28 | .tag-input-label {
29 | &:hover + .tagList {
30 | border-color: var(--color-black-04);
31 | }
32 | }
33 | }
34 |
35 | &.-disabled {
36 | cursor: not-allowed;
37 | .tagList {
38 | border: 2px solid transparent;
39 | background: var(--color-black-08);
40 | }
41 | .tagList:hover {
42 | border-color: transparent;
43 | }
44 | .tag-input-label {
45 | &:hover + .tagList {
46 | border-color: transparent;
47 | }
48 | }
49 | }
50 |
51 | .tagList {
52 | display: flex;
53 | overflow: auto;
54 | margin: 0 auto;
55 | padding: 8px 10px;
56 | cursor: text;
57 | transition: border-color 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
58 | border: 2px solid var(--color-black-08);
59 | border-radius: 8px;
60 | flex-wrap: wrap;
61 |
62 | &:hover {
63 | border-color: var(--color-black);
64 | }
65 |
66 | &:focus-within {
67 | border-color: var(--color-secondary);
68 | }
69 |
70 | input {
71 | width: 100%;
72 | border: none;
73 | font-size: 16px;
74 | font-family: 'Montserrat', 'Helvetica Neue', sans-serif;
75 | line-height: 1.2;
76 | padding: 4px 8px 5px;
77 | box-sizing: border-box;
78 | background-color: transparent;
79 |
80 | &:focus {
81 | outline: none;
82 | }
83 | &::placeholder {
84 | color: rgba(0, 0, 0, 0.32);
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Icons/svg/payment_alipay.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/Tooltip/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import { FloatingArrow, Placement } from '@floating-ui/react';
3 | import cx from 'clsx';
4 | import useTooltip from './useTooltip.hook';
5 |
6 | interface Tooltip {
7 | content: React.ReactNode;
8 | position: Placement;
9 | maxWidth?: number | string;
10 | forceShow?: boolean;
11 | forceHide?: boolean;
12 | openOnClick?: boolean;
13 | }
14 |
15 | const Tooltip: React.FC> = ({
16 | children,
17 | content,
18 | position,
19 | maxWidth,
20 | forceShow,
21 | forceHide,
22 | openOnClick = false,
23 | }) => {
24 | const messageStyles = maxWidth ? ({ width: maxWidth, maxWidth } as React.CSSProperties) : {};
25 |
26 | const arrowRef = useRef(null);
27 | const [isOpen, setIsOpen] = useState(false);
28 |
29 | const { setReference, setFloating, getReferenceProps, getFloatingProps, getArrowPosition, floatingStyles, context } =
30 | useTooltip({ isForce: Boolean(forceShow || forceHide), arrowRef, openOnClick, position, isOpen, setIsOpen });
31 |
32 | useEffect(() => {
33 | setIsOpen(Boolean(forceShow && !forceHide));
34 | }, [forceShow, forceHide]);
35 |
36 | return (
37 | <>
38 |
39 | {children}
40 |
41 | {isOpen && (
42 |
43 |
51 |
58 | {content}
59 |
60 |
61 | )}
62 | >
63 | );
64 | };
65 |
66 | export default Tooltip;
67 |
--------------------------------------------------------------------------------
/.github/actions/publish/action.yml:
--------------------------------------------------------------------------------
1 | name: "Publish package to registry"
2 | description: "Publishes the package to the registry"
3 | inputs:
4 | node-version:
5 | description: "The version of Node.js to use"
6 | required: true
7 | node-cache:
8 | description: "The cache key to use for caching Node.js"
9 | required: false
10 | package-manager:
11 | description: "The package manager to use"
12 | required: false
13 | default: "npm"
14 | registry-url:
15 | description: "The registry URL to use"
16 | required: false
17 | artifact-name:
18 | description: "The name of the artifact to download"
19 | required: false
20 | default: "package-artifact"
21 | scope:
22 | description: "The scope to use"
23 | required: false
24 | default: ""
25 | auth-token:
26 | description: "The auth token to use (for private dependencies only, not required for OIDC publishing)"
27 | required: false
28 | use-public-flag:
29 | description: "Whether to use the public flag"
30 | required: false
31 | default: false
32 |
33 | runs:
34 | using: composite
35 | steps:
36 | - name: Prepare node
37 | uses: ./.github/actions/prepare-node
38 | with:
39 | node-version: ${{ inputs.node-version }}
40 | cache: ${{ inputs.node-cache }}
41 | registry-url: ${{ inputs.registry-url }}
42 | install-dependencies: false
43 | scope: ${{ inputs.scope }}
44 |
45 | - name: Download artifact
46 | uses: actions/download-artifact@v4
47 | with:
48 | name: ${{ inputs.artifact-name }}
49 |
50 | - name: Unpack artifact
51 | shell: bash
52 | run: tar xf artifact.tar.gz
53 |
54 | - name: Update npm for trusted publishing support
55 | shell: bash
56 | run: npm install -g npm@latest
57 |
58 | - name: Publish
59 | shell: bash
60 | run: |
61 | if [ "${{ inputs.use-public-flag }}" = "true" ]; then
62 | npm publish --access public
63 | else
64 | npm publish
65 | fi
66 | env:
67 | NODE_AUTH_TOKEN: ${{ inputs.auth-token }}
68 |
--------------------------------------------------------------------------------
/.github/actions/prepare-packages/action.yml:
--------------------------------------------------------------------------------
1 | name: "Prepare packages"
2 | description: "Builds and packs the packages for release"
3 | inputs:
4 | node-version:
5 | description: 'The version of Node.js to use'
6 | required: true
7 | node-cache:
8 | description: 'The cache key to use for caching Node.js'
9 | required: false
10 | package-manager:
11 | description: 'The package manager to use'
12 | required: false
13 | default: 'npm'
14 | registry-url:
15 | description: 'The registry URL to use'
16 | required: false
17 | artifact-name:
18 | description: 'The name of the artifact to upload'
19 | required: false
20 | default: 'package-artifact'
21 | artifact-retention-days:
22 | description: 'The number of days to retain the artifact'
23 | required: false
24 | default: 1
25 | build-command:
26 | description: 'The command to use to build the package'
27 | required: false
28 | default: 'build'
29 |
30 | runs:
31 | using: composite
32 | steps:
33 | - name: Prepare node
34 | uses: ./.github/actions/prepare-node
35 | id: prepare-node
36 | with:
37 | node-version: ${{ inputs.node-version }}
38 | node-cache: ${{ inputs.node-cache }}
39 | package-manager: ${{ inputs.package-manager }}
40 | registry-url: ${{ inputs.registry-url }}
41 |
42 | - name: Install dependencies
43 | shell: bash
44 | run: ${{ inputs.package-manager }} install
45 |
46 | - name: Build
47 | shell: bash
48 | run: |
49 | if [ "${{ inputs.package-manager }}" = "npm" ]; then
50 | npm run ${{ inputs.build-command }}
51 | else
52 | ${{ inputs.package-manager }} ${{ inputs.build-command }}
53 | fi
54 |
55 | - name: Pack artifact
56 | shell: bash
57 | run: tar -czf /tmp/artifact.tar.gz .
58 |
59 | - name: Upload artifact
60 | uses: actions/upload-artifact@v4
61 | with:
62 | name: ${{ inputs.artifact-name }}
63 | path: /tmp/artifact.tar.gz
64 | retention-days: ${{ inputs.artifact-retention-days }}
65 |
--------------------------------------------------------------------------------
/pages/docs/tag.mdx:
--------------------------------------------------------------------------------
1 | import { Tag, TagList } from '../../src/ui';
2 |
3 | # Tag
4 |
5 | Affiliate Manager
6 |
7 | ```
8 | Affiliate Manager
9 | ```
10 |
11 | ## With Remove Button
12 |
13 | alert('remove event')}>Support
14 |
15 | ```
16 | alert('remove event')}>Support
17 | ```
18 |
19 | ## TagList
20 |
21 |
22 | Kyiv
23 | Kharkiv
24 | Odesa
25 | Dnipro
26 | Donetsk
27 | Zaporizhzhia
28 | Lviv
29 | Kryvyi Rih
30 | Mykolaiv
31 | Mariupol
32 |
33 |
34 | ```
35 |
36 | Kyiv
37 | Kharkiv
38 | Odesa
39 | Dnipro
40 | Donetsk
41 | Zaporizhzhia
42 | Lviv
43 | Kryvyi Rih
44 | Mykolaiv
45 | Mariupol
46 |
47 | ```
48 |
49 | ## Colorful Tags
50 |
51 |
52 | primary
53 | secondary
54 | warning
55 |
56 |
57 | ```
58 |
59 | primary
60 | secondary
61 | warning
62 |
63 | ```
64 |
65 | ## With different border radius
66 |
67 |
68 | Kyiv
69 | Kharkiv
70 | Odesa
71 | Dnipro
72 | Donetsk
73 | Zaporizhzhia
74 |
75 |
76 | ```
77 |
78 | Kyiv
79 | Kharkiv
80 | Odesa
81 | Dnipro
82 | Donetsk
83 | Zaporizhzhia
84 |
85 | ```
--------------------------------------------------------------------------------
/src/Icons/jsx/PaymentIdeal.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { SVGProps } from 'react';
3 |
4 | const SvgPaymentIdeal = (props: SVGProps) => (
5 |
6 |
7 |
11 |
15 |
19 |
23 |
27 |
28 | );
29 |
30 | export default SvgPaymentIdeal;
31 |
--------------------------------------------------------------------------------
/pages/docs/tabs.mdx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TabsProvider, useTabsContext, Tab, TabList, TabPanel, Panel } from '../../src/ui';
3 |
4 | # Tabs
5 |
6 | ## Import
7 |
8 | ```
9 | import { TabsProvider, useTabsContext, Tab, TabList, TabPanel } from '@macpaw/macaw-ui';
10 | ```
11 |
12 | ## Usage
13 |
14 |
15 |
16 |
17 |
18 | Tab 1
19 |
20 |
21 | Tab 2
22 |
23 |
24 |
25 |
26 | Tab 1 content
27 |
28 |
29 | Tab 2 content
30 |
31 |
32 |
33 |
34 |
35 |
36 | ```
37 |
38 |
39 |
40 |
41 | Tab 1
42 |
43 |
44 | Tab 2
45 |
46 |
47 |
48 |
49 | Tab 1 content
50 |
51 |
52 | Tab 2 content
53 |
54 |
55 |
56 |
57 | ```
58 |
59 | ## Hooks
60 |
61 | ### useTabsContext
62 |
63 | You can use **useTabsContext** hook to get access to the tabs context.
64 | You should wrap your components with **TabsProvider** to use this hook.
65 |
66 | ```
67 | const MyComponent = () => {
68 | const { activeTab, setActiveTab } = useTabsContext();
69 |
70 | return (
71 | // ...
72 | );
73 | };
74 |
75 | const ComponentWithTabs = () => (
76 |
77 |
78 | //...tabs
79 |
80 | );
81 | ```
82 |
--------------------------------------------------------------------------------
/src/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, ReactNode, ElementType, ButtonHTMLAttributes } from 'react';
2 | import cx from 'clsx';
3 | import PawIcon from '../Icons/jsx/PawIcon';
4 |
5 | export type ButtonColor = 'primary' | 'secondary' | 'warning' | 'contrast' | 'transparent';
6 |
7 | export interface ButtonProps extends Omit, 'color'> {
8 | color?: ButtonColor;
9 | scale?: 'medium' | 'small';
10 | wide?: boolean;
11 | loading?: boolean;
12 | outline?: boolean;
13 | iconLeft?: ReactNode;
14 | iconRight?: ReactNode;
15 | href?: string;
16 | component?: ReactNode;
17 | asLink?: boolean;
18 | icon?: boolean;
19 | to?: string;
20 | }
21 |
22 | const Button = forwardRef((props, ref) => {
23 | const {
24 | children,
25 | className,
26 | type = 'button',
27 | color = 'primary',
28 | component = 'button',
29 | scale,
30 | wide,
31 | disabled,
32 | loading,
33 | outline,
34 | iconLeft,
35 | iconRight,
36 | asLink,
37 | icon,
38 | ...other
39 | } = props;
40 |
41 | const classNames = cx(className, 'button', `-${color}`, {
42 | '-wide': wide,
43 | '-medium': scale === 'medium',
44 | '-small': scale === 'small',
45 | '-loading': loading,
46 | '-outline': outline,
47 | '-asLink': asLink,
48 | '-icon': icon,
49 | });
50 |
51 | const componentProps: ObjectLiteral = {};
52 |
53 | let Component = component as ElementType;
54 |
55 | if (Component === 'button' && other.href) Component = 'a';
56 |
57 | if (Component === 'button') {
58 | componentProps.type = type;
59 | componentProps.disabled = disabled || loading;
60 | } else if (Component !== 'a' || !other.href) {
61 | componentProps.role = 'button';
62 | }
63 |
64 | return (
65 |
66 | {iconLeft && {iconLeft} }
67 | {loading && }
68 | {children}
69 | {iconRight && {iconRight} }
70 |
71 | );
72 | });
73 |
74 | export default Button;
75 |
--------------------------------------------------------------------------------