├── public ├── robots.txt ├── example │ ├── cmm.png │ └── warning.png ├── menu.svg └── github.svg ├── .npmrc ├── src ├── LanguageIcon │ ├── LanguageIcon.scss │ └── LanguageIcon.tsx ├── TagList │ ├── TagList.scss │ └── TagList.tsx ├── Label │ ├── Label.scss │ └── Label.tsx ├── global.d.ts ├── ModalLauncher │ ├── index.ts │ ├── types.ts │ └── ModalLauncher.tsx ├── GridRow │ ├── GridRow.tsx │ └── GridRow.scss ├── Tabs │ ├── index.ts │ ├── TabPanel.tsx │ ├── TabList.tsx │ ├── Tab.tsx │ └── TabContext.tsx ├── helpers.scss ├── types.ts ├── Normalize │ └── Normalize.scss ├── CardMask │ ├── CardMask.scss │ └── CardMask.tsx ├── Icons │ ├── svg │ │ ├── language_pl.svg │ │ ├── language_uk.svg │ │ ├── pause_icon.svg │ │ ├── language_es.svg │ │ ├── language_ja.svg │ │ ├── language_nl.svg │ │ ├── language_ru.svg │ │ ├── language_zh.svg │ │ ├── dropdown_icon.svg │ │ ├── language_fr.svg │ │ ├── check_icon.svg │ │ ├── language_de.svg │ │ ├── language_it.svg │ │ ├── more_icon.svg │ │ ├── block_icon.svg │ │ ├── cancel_icon.svg │ │ ├── close_icon.svg │ │ ├── device_icon.svg │ │ ├── plus_icon.svg │ │ ├── light_bulb_icon.svg │ │ ├── search_icon.svg │ │ ├── business_icon.svg │ │ ├── device_active_icon.svg │ │ ├── notification_icon.svg │ │ ├── warning_icon.svg │ │ ├── check_circle_icon.svg │ │ ├── refreshing_icon.svg │ │ ├── error_icon.svg │ │ ├── external_icon.svg │ │ ├── refresh.svg │ │ ├── info_icon.svg │ │ ├── download_icon.svg │ │ ├── back_icon.svg │ │ ├── settings_icon.svg │ │ ├── language_tr.svg │ │ ├── user_icon.svg │ │ ├── play_icon.svg │ │ ├── menu_icon.svg │ │ ├── store_icon.svg │ │ ├── copy_icon.svg │ │ ├── account_icon.svg │ │ ├── payment_visa.svg │ │ ├── payment_card.svg │ │ ├── help_icon.svg │ │ ├── payment_ideal.svg │ │ └── payment_alipay.svg │ └── jsx │ │ ├── LanguagePl.tsx │ │ ├── PauseIcon.tsx │ │ ├── LanguageEs.tsx │ │ ├── LanguageUk.tsx │ │ ├── Refresh.tsx │ │ ├── LanguageJa.tsx │ │ ├── LanguageNl.tsx │ │ ├── DropdownIcon.tsx │ │ ├── MoreIcon.tsx │ │ ├── LanguageDe.tsx │ │ ├── LanguageFr.tsx │ │ ├── LanguageIt.tsx │ │ ├── LightBulbIcon.tsx │ │ ├── CheckIcon.tsx │ │ ├── PlusIcon.tsx │ │ ├── LanguageZh.tsx │ │ ├── PlayIcon.tsx │ │ ├── BlockIcon.tsx │ │ ├── ExternalIcon.tsx │ │ ├── CancelIcon.tsx │ │ ├── CloseIcon.tsx │ │ ├── BusinessIcon.tsx │ │ ├── CopyIcon.tsx │ │ ├── DownloadIcon.tsx │ │ ├── DeviceIcon.tsx │ │ ├── AccountIcon.tsx │ │ ├── DeviceActiveIcon.tsx │ │ ├── RefreshingIcon.tsx │ │ ├── MenuIcon.tsx │ │ ├── NotificationIcon.tsx │ │ ├── SearchIcon.tsx │ │ ├── CheckCircle.tsx │ │ ├── LanguageTr.tsx │ │ ├── StoreIcon.tsx │ │ ├── WarningIcon.tsx │ │ ├── InfoIcon.tsx │ │ ├── ErrorIcon.tsx │ │ ├── BackIcon.tsx │ │ ├── SettingsIcon.tsx │ │ ├── UserIcon.tsx │ │ ├── AppStoreIcon.tsx │ │ ├── PaymentVisa.tsx │ │ ├── HelpIcon.tsx │ │ └── PaymentIdeal.tsx ├── DialogIcon │ ├── DialogIcon.scss │ └── DialogIcon.tsx ├── Layout │ └── Layout.scss ├── Accordion │ ├── AccordionContext.ts │ ├── Accordion.tsx │ └── AccordionTrigger.tsx ├── DialogContent │ ├── DialogContent.scss │ └── DialogContent.tsx ├── Loader │ ├── Loader.scss │ └── Loader.tsx ├── MacPawLogo │ ├── MacPawLogo.scss │ └── MacPawLogo.tsx ├── Hint │ ├── Hint.scss │ └── Hint.tsx ├── Panel │ ├── Panel.scss │ └── Panel.tsx ├── TableRow │ └── TableRow.tsx ├── FormRow │ ├── FormRow.scss │ └── FormRow.tsx ├── Table │ ├── Table.tsx │ └── Table.scss ├── ExternalLink │ └── ExternalLink.tsx ├── GridCell │ ├── GridCell.tsx │ └── GridCell.scss ├── Breadcrumbs │ ├── Breadcrumbs.tsx │ └── Breadcrumbs.scss ├── Clipboard │ ├── Clipboard.scss │ └── Clipboard.tsx ├── DialogActions │ ├── DialogActions.tsx │ └── DialogActions.scss ├── Tooltip │ ├── Tooltip.scss │ └── Tooltip.tsx ├── Radio │ ├── Radio.tsx │ └── Radio.scss ├── Checkbox │ ├── Checkbox.tsx │ └── Checkbox.scss ├── LanguageSwitcher │ ├── LanguageSwitcher.scss │ └── LanguageSwitcher.tsx ├── Switch │ ├── Switch.tsx │ └── Switch.scss ├── helpers.ts ├── Banner │ ├── Banner.tsx │ └── Banner.scss ├── Pagination │ ├── NextComponent.tsx │ ├── PrevComponent.tsx │ └── Pagination.scss ├── Dropdown │ └── Dropdown.scss ├── DropdownItem │ ├── DropdownItem.scss │ └── DropdownItem.tsx ├── Tag │ ├── Tag.tsx │ └── Tag.scss ├── Grid │ ├── Grid.tsx │ └── Grid.scss ├── Dialog │ ├── Dialog.scss │ └── Dialog.tsx ├── Select │ ├── Select.tsx │ └── Select.scss ├── Multiselect │ └── Multiselect.scss ├── Password │ └── Password.tsx ├── .eslintrc.json ├── Typography │ └── Typography.scss ├── ui.scss ├── Notification │ └── Notification.tsx ├── TagInput │ └── TagInput.scss └── Button │ └── Button.tsx ├── test └── jest.setup.ts ├── .svgorc.json ├── pages ├── index.js ├── _app.js └── docs │ ├── card-mask.mdx │ ├── logo.mdx │ ├── breadcrumbs.mdx │ ├── loader.mdx │ ├── radio.mdx │ ├── icons.mdx │ ├── checkbox.mdx │ ├── panel.mdx │ ├── index.mdx │ ├── language-switcher.mdx │ ├── notification.mdx │ ├── switch.mdx │ ├── pagination.mdx │ ├── payment.mdx │ ├── banner.mdx │ ├── table.mdx │ ├── typography.mdx │ ├── tag.mdx │ └── tabs.mdx ├── internal ├── IconsGrid │ ├── IconsGrid.module.css │ └── IconsGrid.js ├── Layout │ ├── Layout.module.css │ └── Layout.js ├── MobileNavigation │ ├── mobileNavigation.module.css │ └── MobileNavigation.js ├── config │ └── pages.js ├── Navigation │ ├── Navigation.module.css │ └── Navigation.js ├── CodeBlock │ ├── CodeBlock.module.css │ └── CodeBlock.js ├── HomePage │ ├── homePage.module.css │ └── HomePage.js ├── ActiveLink │ └── ActiveLink.js └── Palette │ ├── Palette.module.css │ └── Palette.js ├── next-env.d.ts ├── next.config.js ├── jest.config.js ├── .changeset ├── config.json └── README.md ├── .gitignore ├── .github ├── dependabot.yml ├── workflows │ └── codeql-analysis.yml └── actions │ ├── github-config │ └── action.yml │ ├── prepare-node │ └── action.yml │ ├── publish │ └── action.yml │ └── prepare-packages │ └── action.yml ├── .infrastructure └── docker │ └── ui │ └── Dockerfile ├── tsconfig.json ├── tsconfig.lib.json └── CHANGELOG.md /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | provenance=false 3 | -------------------------------------------------------------------------------- /src/LanguageIcon/LanguageIcon.scss: -------------------------------------------------------------------------------- 1 | .placeholder { 2 | box-shadow: none; 3 | } -------------------------------------------------------------------------------- /public/example/cmm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/macpaw-ui/HEAD/public/example/cmm.png -------------------------------------------------------------------------------- /public/example/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/macpaw-ui/HEAD/public/example/warning.png -------------------------------------------------------------------------------- /src/TagList/TagList.scss: -------------------------------------------------------------------------------- 1 | .tagList { 2 | margin: -4px; 3 | 4 | .tag { 5 | margin: 4px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Label/Label.scss: -------------------------------------------------------------------------------- 1 | .label { 2 | color: var(--color-black-48); 3 | font-size: 12px; 4 | margin-bottom: 8px; 5 | } -------------------------------------------------------------------------------- /test/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import React from 'react'; 3 | 4 | global.React = React; 5 | -------------------------------------------------------------------------------- /.svgorc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | { 4 | "name": "removeViewBox", 5 | "active": false 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface ObjectLiteral { 3 | [key: string]: unknown; 4 | } 5 | } 6 | 7 | export {}; 8 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import HomePage from '../internal/HomePage/HomePage'; 2 | 3 | export default function Home() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /src/ModalLauncher/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { ModalLauncherProvider, useModals } from './ModalLauncherContext'; 3 | export { default as ModalLauncher } from './ModalLauncher'; 4 | -------------------------------------------------------------------------------- /src/ModalLauncher/types.ts: -------------------------------------------------------------------------------- 1 | export interface ModalItem { 2 | name: string; 3 | props?: ObjectLiteral; 4 | style?: ObjectLiteral; 5 | className?: string; 6 | onCloseModal?: () => void; 7 | } 8 | -------------------------------------------------------------------------------- /src/GridRow/GridRow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const GridRow: React.FC> = (props) =>
; 4 | 5 | export default GridRow; 6 | -------------------------------------------------------------------------------- /src/Tabs/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Tab } from './Tab'; 2 | export { default as TabList } from './TabList'; 3 | export { default as TabPanel } from './TabPanel'; 4 | export { TabsProvider, useTabsContext } from './TabContext'; 5 | -------------------------------------------------------------------------------- /src/helpers.scss: -------------------------------------------------------------------------------- 1 | @mixin mobile { 2 | @media only screen and (max-width: 768px) { 3 | @content; 4 | } 5 | } 6 | 7 | @mixin visibility($visibility, $opacity) { 8 | visibility: $visibility; 9 | opacity: $opacity; 10 | } 11 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export type Error = boolean | string | ReactNode; 4 | 5 | export type InputValueType = string | number | readonly string[]; 6 | 7 | export type Maybe = null | undefined | T; 8 | -------------------------------------------------------------------------------- /src/Normalize/Normalize.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | } 8 | 9 | hr { 10 | margin: 64px 0; 11 | border: none; 12 | height: 2px; 13 | background: var(--color-black-16); 14 | } -------------------------------------------------------------------------------- /internal/IconsGrid/IconsGrid.module.css: -------------------------------------------------------------------------------- 1 | .iconsGrid { 2 | margin-top: 16px; 3 | margin-bottom: 64px; 4 | display: grid; 5 | column-gap: 24px; 6 | row-gap: 24px; 7 | align-items: center; 8 | justify-items: center; 9 | } 10 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import Layout from '../internal/Layout/Layout'; 2 | import '../src/ui.scss'; 3 | 4 | export default function Application({ Component, pageProps }) { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/CardMask/CardMask.scss: -------------------------------------------------------------------------------- 1 | .cardMask { 2 | display: inline-flex; 3 | align-items: center; 4 | font-family: "Courier", "Courier New", "Montserrat", "Helvetica Neue", sans-serif; 5 | font-weight: bold; 6 | 7 | span { 8 | margin-right: 10px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Icons/svg/language_pl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Icons/svg/language_uk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Icons/svg/pause_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withMDX = require('@next/mdx')({ 2 | extension: /\.mdx?$/, 3 | options: { providerImportSource: '@mdx-js/react' }, 4 | }); 5 | 6 | module.exports = withMDX({ 7 | pageExtensions: ['js', 'md', 'mdx'], 8 | trailingSlash: true, 9 | output: 'export', 10 | }); 11 | -------------------------------------------------------------------------------- /src/Icons/svg/language_es.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /pages/docs/card-mask.mdx: -------------------------------------------------------------------------------- 1 | import { CardMask } from '../../src/ui'; 2 | 3 | # Card Mask 4 | 5 | 6 | 7 | ``` 8 | 9 | ``` 10 | 11 | ## Short mask 12 | 13 | 14 | 15 | ``` 16 | 17 | ``` -------------------------------------------------------------------------------- /src/GridRow/GridRow.scss: -------------------------------------------------------------------------------- 1 | .gridRow { 2 | flex: 1; 3 | display: flex; 4 | @include mobile { 5 | flex-direction: column; 6 | } 7 | 8 | &:last-child { 9 | .gridCell:last-child { 10 | @include mobile { 11 | border-radius: 0 0 22px 22px; 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Icons/svg/language_ja.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'jsdom', 3 | clearMocks: true, 4 | moduleDirectories: ['node_modules'], 5 | testPathIgnorePatterns: ['node_modules', './lib'], 6 | transform: { 7 | '^.+\\.(ts|tsx)$': '@swc/jest', 8 | }, 9 | setupFilesAfterEnv: ['/test/jest.setup.ts'], 10 | }; 11 | -------------------------------------------------------------------------------- /public/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Icons/svg/language_nl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Icons/svg/language_ru.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/DialogIcon/DialogIcon.scss: -------------------------------------------------------------------------------- 1 | .dialogIcon { 2 | position: relative; 3 | margin-bottom: 32px; 4 | text-align: center; 5 | 6 | &-backdrop { 7 | position: absolute; 8 | left: 0; 9 | top: 0; 10 | width: 100%; 11 | height: 100%; 12 | } 13 | 14 | svg, img { 15 | vertical-align: top; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Layout/Layout.scss: -------------------------------------------------------------------------------- 1 | $sizes: [4, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 96]; 2 | 3 | @each $size in $sizes { 4 | .mb-#{$size} { 5 | margin-bottom: #{$size}px; 6 | } 7 | } 8 | 9 | .wrapper { 10 | max-width: 1152px; 11 | min-height: 100vh; 12 | min-width: 320px; 13 | margin: 0 auto; 14 | padding: 0 16px; 15 | } 16 | -------------------------------------------------------------------------------- /src/Accordion/AccordionContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | interface AccordionContextInterface { 4 | activeKey: null | string; 5 | onToggle: (activeKey: string | null) => void; 6 | } 7 | 8 | export default createContext({ 9 | activeKey: null, 10 | onToggle: () => {}, 11 | }); 12 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /src/DialogContent/DialogContent.scss: -------------------------------------------------------------------------------- 1 | .dialogContent { 2 | padding: 48px; 3 | background: var(--color-white); 4 | border-radius: 20px; 5 | text-align: center; 6 | overflow: auto; 7 | 8 | @include mobile { 9 | flex: 1; 10 | padding-left: 24px; 11 | padding-right: 24px; 12 | border-radius: 0 0 20px 20px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Loader/Loader.scss: -------------------------------------------------------------------------------- 1 | .loader { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | position: absolute; 6 | left: 0; 7 | top: 0; 8 | right: 0; 9 | bottom: 0; 10 | pointer-events: none; 11 | 12 | &.-inline { 13 | display: inline-flex; 14 | position: static; 15 | vertical-align: top; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Icons/svg/language_zh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /pages/docs/logo.mdx: -------------------------------------------------------------------------------- 1 | import MacPawLogo from '../../src/MacPawLogo/MacPawLogo'; 2 | import { MacpawText, PawIcon } from '../../src/Icons/jsx'; 3 | 4 | ## MacPaw Logo 5 | 6 | 7 | ``` 8 | 9 | ``` 10 | ### Paw 11 | 12 | ``` 13 | 14 | ``` 15 | 16 | ### Text 17 | 18 | ``` 19 | 20 | ``` 21 | -------------------------------------------------------------------------------- /src/Icons/svg/dropdown_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/Icons/svg/language_fr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Icons/svg/check_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # idea setting 5 | .idea 6 | 7 | # MacOS filesystem 8 | .DS_Store 9 | 10 | # next.js 11 | .next 12 | 13 | # built components 14 | lib 15 | 16 | # static pages 17 | out 18 | 19 | # VS code setting 20 | **/.vscode/* 21 | 22 | # GPG key 23 | .gpg 24 | 25 | **/*.tsbuildinfo 26 | 27 | # Claude 28 | .claude 29 | CLAUDE.md 30 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "11:00" 8 | timezone: "Europe/Kyiv" 9 | open-pull-requests-limit: 2 10 | reviewers: 11 | - "MacPaw/platform-frontend" 12 | allowed_updates: 13 | - match: 14 | update_type: "security" 15 | -------------------------------------------------------------------------------- /.infrastructure/docker/ui/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | 3 | ARG NPM_TOKEN 4 | 5 | WORKDIR /app 6 | 7 | COPY package.json package-lock.json ./ 8 | RUN npm config set //npm.pkg.github.com/:_authToken=${NPM_TOKEN} 9 | RUN npm config set @macpaw:registry=https://npm.pkg.github.com 10 | RUN npm install 11 | 12 | COPY . . 13 | RUN npm run build 14 | 15 | ENTRYPOINT [ "npm", "run" ] 16 | -------------------------------------------------------------------------------- /src/Icons/svg/language_de.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Icons/svg/language_it.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Icons/svg/more_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Icons/svg/block_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /internal/Layout/Layout.module.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | display: flex; 3 | min-height: 100vh; 4 | } 5 | 6 | .content { 7 | flex: 1; 8 | padding: 24px; 9 | max-width: 1240px; 10 | } 11 | 12 | .paragraph { 13 | margin: 0; 14 | } 15 | 16 | @media screen and (max-width: 1280px){ 17 | .layout { 18 | flex-direction: column; 19 | } 20 | .content { 21 | max-width: 100%; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pages/docs/breadcrumbs.mdx: -------------------------------------------------------------------------------- 1 | import { Breadcrumbs } from '../../src/ui'; 2 | 3 | # Breadcrumbs 4 | 5 | 6 |
  • Overview
  • 7 |
  • CleanMyMac X Plans
  • 8 |
  • Plan
  • 9 |
    10 | 11 | ``` 12 | 13 |
  • Overview
  • 14 |
  • CleanMyMac X Plans
  • 15 |
  • Plan
  • 16 |
    17 | ``` -------------------------------------------------------------------------------- /src/Icons/svg/cancel_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/MacPawLogo/MacPawLogo.scss: -------------------------------------------------------------------------------- 1 | .mac-paw-logo { 2 | 3 | color: var(--color-black); 4 | font-size: 0; 5 | 6 | &:hover { 7 | &>.mac-paw-logo__paw_rotate { 8 | transform: rotate(1turn); 9 | } 10 | } 11 | 12 | &_inline { 13 | display: inline-block; 14 | } 15 | 16 | &__text{ 17 | margin-left: 5.8px; 18 | } 19 | 20 | &__paw { 21 | transition: all .5s ease-in-out; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Icons/svg/close_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguagePl.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguagePl = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | 12 | export default SvgLanguagePl; 13 | -------------------------------------------------------------------------------- /src/Icons/jsx/PauseIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgPauseIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | 12 | export default SvgPauseIcon; 13 | -------------------------------------------------------------------------------- /src/Tabs/TabPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTabsContext } from './TabContext'; 3 | 4 | type TabPanelProps = { 5 | children: React.ReactNode; 6 | tab: number | string; 7 | }; 8 | 9 | const TabPanel: React.FC = ({ children, tab }) => { 10 | const { activeTab } = useTabsContext(); 11 | 12 | return (activeTab === tab && children) as React.ReactElement; 13 | }; 14 | 15 | export default TabPanel; 16 | -------------------------------------------------------------------------------- /src/Hint/Hint.scss: -------------------------------------------------------------------------------- 1 | .hint{ 2 | display: block; 3 | font-size: 12px; 4 | line-height: 1.6; 5 | text-align: left; 6 | transition: all .2s ease-in-out; 7 | &.-large { 8 | font-size: 14px; 9 | } 10 | &.-error { 11 | color: var(--color-error); 12 | } 13 | a { 14 | text-decoration: underline; 15 | &:hover { 16 | text-decoration: none 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageEs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageEs = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | 12 | export default SvgLanguageEs; 13 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageUk.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageUk = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | 12 | export default SvgLanguageUk; 13 | -------------------------------------------------------------------------------- /src/Panel/Panel.scss: -------------------------------------------------------------------------------- 1 | .panel { 2 | display: block; 3 | color: var(--color-black); 4 | padding: 36px; 5 | background: var(--color-white); 6 | box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.08); 7 | border-radius: 24px; 8 | font-size: 14px; 9 | 10 | @include mobile { 11 | padding: 16px; 12 | } 13 | 14 | &.-outline { 15 | background: none; 16 | border: 2px solid rgba(0, 0, 0, 0.08); 17 | box-shadow: none; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/TableRow/TableRow.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | 3 | export interface TableRowProps extends HTMLAttributes {} 4 | 5 | const TableRow: FC> = (props) => { 6 | const { children, ...other } = props; 7 | 8 | return ( 9 | 10 | 11 | {children} 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default TableRow; 18 | -------------------------------------------------------------------------------- /src/FormRow/FormRow.scss: -------------------------------------------------------------------------------- 1 | .formRow { 2 | margin-bottom: 24px; 3 | 4 | &.-asList { 5 | margin-bottom: 8px; 6 | } 7 | 8 | &.-split { 9 | display: grid; 10 | grid-template-columns: 0.5fr 0.5fr; 11 | grid-gap: 32px; 12 | 13 | @include mobile { 14 | grid-template-columns: 1fr; 15 | grid-template-rows: 0.5fr 0.5fr; 16 | grid-gap: 16px; 17 | } 18 | } 19 | 20 | &:last-child { 21 | margin-bottom: 0; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Icons/jsx/Refresh.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgRefresh = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default SvgRefresh; 10 | -------------------------------------------------------------------------------- /src/Label/Label.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface LabelProps extends HTMLAttributes {} 5 | 6 | const Label: FC> = (props) => { 7 | const { className, children, ...other } = props; 8 | 9 | return ( 10 |
    11 | {children} 12 |
    13 | ); 14 | }; 15 | 16 | export default Label; 17 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageJa.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageJa = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | 12 | export default SvgLanguageJa; 13 | -------------------------------------------------------------------------------- /src/Table/Table.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, TableHTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface TableProps extends TableHTMLAttributes {} 5 | 6 | const Table: FC> = (props) => { 7 | const { className, children, ...other } = props; 8 | 9 | return ( 10 | 11 | {children} 12 |
    13 | ); 14 | }; 15 | 16 | export default Table; 17 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageNl.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageNl = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | 13 | export default SvgLanguageNl; 14 | -------------------------------------------------------------------------------- /src/ExternalLink/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import React, { AnchorHTMLAttributes } from 'react'; 2 | 3 | export interface ExternalLinkProps extends AnchorHTMLAttributes {} 4 | 5 | const ExternalLink: React.FC> = (props) => { 6 | const { children, ...other } = props; 7 | 8 | return ( 9 | 10 | {children} 11 | 12 | ); 13 | }; 14 | 15 | export default ExternalLink; 16 | -------------------------------------------------------------------------------- /public/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Icons/jsx/DropdownIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgDropdownIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default SvgDropdownIcon; 10 | -------------------------------------------------------------------------------- /src/Icons/jsx/MoreIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgMoreIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | ); 9 | 10 | export default SvgMoreIcon; 11 | -------------------------------------------------------------------------------- /src/TagList/TagList.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface TagListProps extends HTMLAttributes { 5 | as?: 'div' | 'span'; 6 | } 7 | 8 | const TagList: FC> = ({ className, children, as: Element = 'div', ...other }) => ( 9 | 10 | {children} 11 | 12 | ); 13 | 14 | export default TagList; 15 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageDe.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageDe = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | 13 | export default SvgLanguageDe; 14 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageFr.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageFr = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | 13 | export default SvgLanguageFr; 14 | -------------------------------------------------------------------------------- /src/GridCell/GridCell.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface GridCell { 4 | type: 'primary' | 'secondary'; 5 | } 6 | 7 | const GridCell: React.FC> = (props) => { 8 | const { type, ...other } = props; 9 | const Component = other.children ? 'div' : 'span'; // component type is important for zebra style 10 | 11 | return ; 12 | }; 13 | 14 | export default GridCell; 15 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageIt.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageIt = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | 13 | export default SvgLanguageIt; 14 | -------------------------------------------------------------------------------- /src/Icons/jsx/LightBulbIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLightBulbIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default SvgLightBulbIcon; 10 | -------------------------------------------------------------------------------- /src/Breadcrumbs/Breadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface BreadcrumbsProps extends HTMLAttributes {} 5 | 6 | const Breadcrumbs: FC> = (props) => { 7 | const { className, children, ...other } = props; 8 | 9 | return ( 10 |
      11 | {children} 12 |
    13 | ); 14 | }; 15 | 16 | export default Breadcrumbs; 17 | -------------------------------------------------------------------------------- /src/Clipboard/Clipboard.scss: -------------------------------------------------------------------------------- 1 | .clipboard { 2 | position: absolute; 3 | top: 58%; 4 | right: 12px; 5 | transform: translateY(-50%); 6 | 7 | &__icon { 8 | fill: currentColor; 9 | transition: fill .1s; 10 | 11 | &:hover { 12 | fill: var(--color-secondary); 13 | } 14 | } 15 | 16 | &__done-icon { 17 | fill: var(--color-primary); 18 | } 19 | 20 | &__button { 21 | padding: 0; 22 | border: 0; 23 | background-color: transparent; 24 | cursor: pointer; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/DialogActions/DialogActions.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface DialogActionsProps extends HTMLAttributes {} 5 | 6 | const DialogActions: FC> = (props) => { 7 | const { className, children, ...other } = props; 8 | 9 | return ( 10 |
    11 | {children} 12 |
    13 | ); 14 | }; 15 | 16 | export default DialogActions; 17 | -------------------------------------------------------------------------------- /src/DialogContent/DialogContent.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface DialogContentProps extends HTMLAttributes {} 5 | 6 | const DialogContent: FC> = (props) => { 7 | const { children, className, ...other } = props; 8 | 9 | return ( 10 |
    11 | {children} 12 |
    13 | ); 14 | }; 15 | 16 | export default DialogContent; 17 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /src/Icons/jsx/CheckIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgCheckIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | 12 | export default SvgCheckIcon; 13 | -------------------------------------------------------------------------------- /src/Icons/jsx/PlusIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgPlusIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 11 | 12 | 13 | ); 14 | 15 | export default SvgPlusIcon; 16 | -------------------------------------------------------------------------------- /pages/docs/loader.mdx: -------------------------------------------------------------------------------- 1 | import { Loader } from '../../src/ui'; 2 | 3 | # Loader 4 | 5 |
    6 |
    7 | 8 | ``` 9 |
    10 |
    11 | ``` 12 | 13 | ## Inline Loader 14 | 15 | 16 | 17 | ``` 18 | 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /src/Icons/svg/device_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageZh.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageZh = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 11 | 12 | 13 | ); 14 | 15 | export default SvgLanguageZh; 16 | -------------------------------------------------------------------------------- /src/Icons/jsx/PlayIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgPlayIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default SvgPlayIcon; 10 | -------------------------------------------------------------------------------- /src/Icons/svg/plus_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /internal/MobileNavigation/mobileNavigation.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: none; 3 | justify-content: space-between; 4 | align-items: center; 5 | } 6 | 7 | .dropdown, 8 | .logo { 9 | padding: 10px 10px 0; 10 | } 11 | 12 | .logo { 13 | cursor: pointer; 14 | } 15 | 16 | .navigationLink { 17 | display: block; 18 | color: #000; 19 | text-decoration: none; 20 | text-transform: capitalize; 21 | } 22 | 23 | .activeLink { 24 | color: #5093ff; 25 | } 26 | 27 | @media screen and (max-width: 1280px) { 28 | .container { 29 | display: flex; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Icons/jsx/BlockIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgBlockIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | ); 9 | 10 | export default SvgBlockIcon; 11 | -------------------------------------------------------------------------------- /src/FormRow/FormRow.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface FormRowProps extends HTMLAttributes { 5 | asList?: boolean; 6 | split?: boolean; 7 | } 8 | 9 | const FormRow: FC> = (props) => { 10 | const { children, asList, split, className, ...other } = props; 11 | 12 | return ( 13 |
    14 | {children} 15 |
    16 | ); 17 | }; 18 | 19 | export default FormRow; 20 | -------------------------------------------------------------------------------- /src/Icons/jsx/ExternalIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgExternalIcon = (props: React.SVGProps) => ( 4 | 5 | 9 | 10 | ); 11 | 12 | export default SvgExternalIcon; 13 | -------------------------------------------------------------------------------- /src/Icons/jsx/CancelIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgCancelIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | 13 | export default SvgCancelIcon; 14 | -------------------------------------------------------------------------------- /src/Icons/jsx/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgCloseIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | 13 | export default SvgCloseIcon; 14 | -------------------------------------------------------------------------------- /src/GridCell/GridCell.scss: -------------------------------------------------------------------------------- 1 | .gridCell { 2 | padding: 12px; 3 | flex: 0; 4 | word-break: break-word; 5 | @include mobile { 6 | padding: 8px 12px; 7 | display: flex; 8 | font-size: 14px; 9 | } 10 | 11 | &:empty { 12 | @include mobile { 13 | display: none; 14 | } 15 | } 16 | 17 | &:nth-of-type(2n) { 18 | @include mobile { 19 | background: var(--color-black-04); 20 | } 21 | } 22 | 23 | &.-primary { 24 | flex-grow: 1; 25 | flex-basis: 60%; 26 | } 27 | 28 | &.-secondary { 29 | flex-grow: 1; 30 | flex-basis: 40%; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Icons/svg/light_bulb_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /internal/IconsGrid/IconsGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as icons from '../../src/Icons/jsx'; 3 | import styles from './IconsGrid.module.css'; 4 | 5 | const IconsGrid = (props) => { 6 | const { filterFunction, ...other } = props; 7 | const iconKeys = Object.keys(icons).sort().filter(filterFunction); 8 | return ( 9 |
    10 | {iconKeys.map((iconKey, index) => { 11 | const Component = icons[iconKey]; 12 | return ; 13 | })} 14 |
    15 | ) 16 | } 17 | 18 | export default IconsGrid; 19 | -------------------------------------------------------------------------------- /src/Icons/svg/search_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /pages/docs/radio.mdx: -------------------------------------------------------------------------------- 1 | import { Radio, FormRow } from '../../src/ui'; 2 | 3 | # Radio 4 | 5 | Default 6 | Checked 7 | Disabled 8 | Error 9 | 10 | ``` 11 | Default 12 | Checked 13 | Disabled 14 | Error 15 | ``` 16 | -------------------------------------------------------------------------------- /pages/docs/icons.mdx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import IconsGrid from '../../internal/IconsGrid/IconsGrid'; 3 | 4 | ## UI Icons 5 | 6 | key.endsWith('Icon')} style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(24px, 1fr))' }} /> 7 | 8 | ## Languages Icons 9 | 10 | key.startsWith('Language')} style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(22px, 1fr))' }} /> 11 | 12 | ## Payment Icons 13 | 14 | key.startsWith('Payment')} style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(70px, 1fr))' }} /> 15 | -------------------------------------------------------------------------------- /src/DialogIcon/DialogIcon.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | import DialogIconBackdrop from './DialogIconBackdrop'; 4 | 5 | export interface DialogIconProps extends HTMLAttributes {} 6 | 7 | const DialogIcon: FC> = (props) => { 8 | const { className, children, ...other } = props; 9 | 10 | return ( 11 |
    12 | 13 | {children} 14 |
    15 | ); 16 | }; 17 | 18 | export default DialogIcon; 19 | -------------------------------------------------------------------------------- /src/Icons/svg/business_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /src/Icons/jsx/BusinessIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgBusinessIcon = (props: React.SVGProps) => ( 4 | 5 | 10 | 11 | ); 12 | 13 | export default SvgBusinessIcon; 14 | -------------------------------------------------------------------------------- /src/Tooltip/Tooltip.scss: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | z-index: 1; 3 | 4 | &-trigger { 5 | position: relative; 6 | display: inline-block; 7 | width: fit-content; 8 | } 9 | 10 | &-message { 11 | min-width: 350px; 12 | max-width: 350px; 13 | transition: all 0.1s linear; 14 | 15 | &.-custom-width { 16 | min-width: min-content; 17 | max-width: auto; 18 | } 19 | } 20 | 21 | &-content { 22 | background: var(--color-black); 23 | color: var(--color-white); 24 | font-size: 14px; 25 | line-height: 1.2; 26 | border-radius: 12px; 27 | margin: auto; 28 | padding: 24px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/config/pages.js: -------------------------------------------------------------------------------- 1 | export const pages = [ 2 | 'accordion', 3 | 'banner', 4 | 'breadcrumbs', 5 | 'button', 6 | 'card-mask', 7 | 'checkbox', 8 | 'colors', 9 | 'date-picker', 10 | 'dialog', 11 | 'dropdown', 12 | 'grid', 13 | 'icons', 14 | 'input', 15 | 'language-switcher', 16 | 'logo', 17 | 'loader', 18 | 'notification', 19 | 'pagination', 20 | 'panel', 21 | 'payment', 22 | 'radio', 23 | 'select', 24 | 'switch', 25 | 'tabs', 26 | 'tag', 27 | 'tag-input', 28 | 'table', 29 | 'tooltip', 30 | 'typography', 31 | 'modal-launcher', 32 | ]; -------------------------------------------------------------------------------- /src/Hint/Hint.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface HintProps extends HTMLAttributes { 5 | large?: boolean; 6 | error?: boolean; 7 | } 8 | 9 | const Hint: FC> = (props) => { 10 | const { large, children, error, className, ...other } = props; 11 | 12 | const hintClassName = cx('hint', className, { 13 | '-large': large, 14 | '-error': error, 15 | }); 16 | 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | export default Hint; 25 | -------------------------------------------------------------------------------- /src/Radio/Radio.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, InputHTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface RadioProps extends InputHTMLAttributes { 5 | error?: boolean; 6 | } 7 | 8 | const Radio: FC> = (props) => { 9 | const { className, error, children, ...other } = props; 10 | 11 | return ( 12 | 16 | ); 17 | }; 18 | 19 | export default Radio; 20 | -------------------------------------------------------------------------------- /src/CardMask/CardMask.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface CardMaskProps extends HTMLAttributes { 5 | digits?: string; 6 | short?: boolean; 7 | } 8 | 9 | const CardMask: FC> = (props) => { 10 | const { digits, short, className, ...other } = props; 11 | 12 | return ( 13 |
    14 | ●●●● 15 | {!short && ●●●●} 16 | {!short && ●●●●} 17 | {digits || '●●●●'} 18 |
    19 | ); 20 | }; 21 | 22 | export default CardMask; 23 | -------------------------------------------------------------------------------- /src/Icons/svg/device_active_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Icons/jsx/CopyIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgCopyIcon = (props: React.SVGProps) => ( 4 | 5 | 9 | 10 | ); 11 | 12 | export default SvgCopyIcon; 13 | -------------------------------------------------------------------------------- /src/Icons/jsx/DownloadIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgDownloadIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default SvgDownloadIcon; 10 | -------------------------------------------------------------------------------- /src/Icons/jsx/DeviceIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgDeviceIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | ); 9 | 10 | export default SvgDeviceIcon; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/Accordion/Accordion.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import AccordionContext from './AccordionContext'; 3 | 4 | interface Accordion { 5 | onChange?: (activeKey: string | null) => void; 6 | } 7 | 8 | const Accordion: React.FC> = (props) => { 9 | const { children, onChange } = props; 10 | const [activeKey, setActiveKey] = useState(null); 11 | 12 | const onToggle = (key: string | null) => { 13 | setActiveKey(key); 14 | if (onChange) onChange(key); 15 | }; 16 | 17 | return {children}; 18 | }; 19 | 20 | export default Accordion; 21 | -------------------------------------------------------------------------------- /src/Checkbox/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef, InputHTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface CheckboxProps extends InputHTMLAttributes { 5 | checked?: boolean; 6 | disabled?: boolean; 7 | error?: boolean; 8 | } 9 | 10 | const Checkbox = forwardRef((props, ref) => { 11 | const { className, error, children, style, ...other } = props; 12 | 13 | return ( 14 | 19 | ); 20 | }); 21 | 22 | export default Checkbox; 23 | -------------------------------------------------------------------------------- /src/Icons/jsx/AccountIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgAccountIcon = (props: React.SVGProps) => ( 4 | 5 | 9 | 10 | ); 11 | 12 | export default SvgAccountIcon; 13 | -------------------------------------------------------------------------------- /tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "jsx": "react-jsx", 18 | "declaration": true, 19 | "outDir": "lib" 20 | }, 21 | "include": [ 22 | "src" 23 | ], 24 | "exclude": [ 25 | "**/__tests__", 26 | "**/*.test.ts", 27 | "**/*.test.tsx", 28 | "**/*.spec.ts", 29 | "**/*.spec.tsx" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/LanguageSwitcher/LanguageSwitcher.scss: -------------------------------------------------------------------------------- 1 | .languageSwitcher { 2 | position: relative; 3 | display: inline-flex; 4 | padding: 4px 0 6px; 5 | white-space: nowrap; 6 | align-items: center; 7 | 8 | &-name { 9 | font-size: 13px; 10 | font-weight: 400; 11 | color: #333; 12 | } 13 | 14 | &-icon { 15 | margin-right: 10px; 16 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .18); 17 | } 18 | 19 | &-arrow { 20 | width: 18px; 21 | height: 18px; 22 | margin-left: 6px; 23 | } 24 | 25 | select { 26 | position: absolute; 27 | left: 0; 28 | top: 0; 29 | width: 100%; 30 | height: 100%; 31 | -webkit-appearance: none; 32 | opacity: 0; 33 | cursor: pointer; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pages/docs/checkbox.mdx: -------------------------------------------------------------------------------- 1 | import { Checkbox, FormRow } from '../../src/ui'; 2 | 3 | # Checkbox 4 | 5 | Default 6 | Default 7 | Checked 8 | Disabled 9 | Error 10 | 11 | ``` 12 | Default 13 | Default 14 | Checked 15 | Disabled 16 | Error 17 | ``` 18 | -------------------------------------------------------------------------------- /src/Icons/svg/notification_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Icons/svg/warning_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Panel/Panel.tsx: -------------------------------------------------------------------------------- 1 | import React, { ElementType, FC, HTMLAttributes, ReactNode } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface PanelProps extends HTMLAttributes { 5 | outline?: boolean; 6 | href?: string; 7 | component?: ReactNode; 8 | to?: string; 9 | } 10 | 11 | const Panel: FC> = (props) => { 12 | const { className, outline, children, component = 'div', ...other } = props; 13 | 14 | const Component = other.href ? 'a' : (component as ElementType); 15 | 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | }; 22 | 23 | export default Panel; 24 | -------------------------------------------------------------------------------- /src/Switch/Switch.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, InputHTMLAttributes, PropsWithChildren } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface SwitchProps extends InputHTMLAttributes { 5 | error?: boolean; 6 | } 7 | 8 | const Switch: FC> = (props) => { 9 | const { className, style, children, error, ...other } = props; 10 | 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | {!!children && {children}} 18 | 19 | ); 20 | }; 21 | 22 | export default Switch; 23 | -------------------------------------------------------------------------------- /pages/docs/panel.mdx: -------------------------------------------------------------------------------- 1 | import { Panel } from '../../src/ui'; 2 | 3 | # Panel 4 | 5 |
    6 | Panel Content 7 | Panel Content 8 | Panel as Link 9 | Outline Panel 10 |
    11 | 12 | ``` 13 | Panel Content 14 | Panel Content 15 | Panel as Link 16 | Outline Panel 17 | ``` -------------------------------------------------------------------------------- /src/Icons/svg/check_circle_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Icons/svg/refreshing_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /src/Icons/svg/error_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Icons/svg/external_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /internal/Navigation/Navigation.module.css: -------------------------------------------------------------------------------- 1 | .navigation { 2 | position: sticky; 3 | flex: none; 4 | top: 0; 5 | height: 100vh; 6 | width: 300px; 7 | background: #fff; 8 | color: #000; 9 | overflow: scroll; 10 | border-right: 1px solid #e8e8e8; 11 | } 12 | 13 | .projectLink { 14 | padding: 12px; 15 | font-size: 20px; 16 | font-weight: 700; 17 | color: #000; 18 | text-decoration: none; 19 | } 20 | 21 | .navigationLinks { 22 | padding: 6px 12px; 23 | } 24 | 25 | .navigationLink { 26 | margin: 8px 0; 27 | display: block; 28 | color: #000; 29 | text-decoration: none; 30 | text-transform: capitalize; 31 | } 32 | 33 | .activeLink { 34 | color: #5093ff; 35 | } 36 | 37 | @media screen and (max-width: 1280px) { 38 | .navigation { 39 | display: none; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Icons/svg/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Icons/svg/info_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /internal/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MDXProvider } from '@mdx-js/react'; 3 | import CodeBlock from '../CodeBlock/CodeBlock'; 4 | import Navigation from '../Navigation/Navigation'; 5 | import styles from './Layout.module.css'; 6 | import MobileNavigation from '../MobileNavigation/MobileNavigation'; 7 | 8 | const components = { 9 | p: (props) =>

    , 10 | pre: (props) =>

    , 11 | code: CodeBlock, 12 | }; 13 | 14 | const Layout = ({ children }) => ( 15 |
    16 | 17 | 18 |
    19 | {children} 20 |
    21 |
    22 | ); 23 | 24 | export default Layout; 25 | -------------------------------------------------------------------------------- /src/Icons/svg/download_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL Code Checks" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: [ 'javascript' ] 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v3 26 | 27 | - name: Initialize CodeQL 28 | uses: github/codeql-action/init@v2 29 | with: 30 | languages: ${{ matrix.language }} 31 | 32 | - name: Perform CodeQL Analysis 33 | uses: github/codeql-action/analyze@v2 34 | -------------------------------------------------------------------------------- /pages/docs/index.mdx: -------------------------------------------------------------------------------- 1 | # Installation 2 | ##### Install with npm: 3 | ```bash 4 | npm install --save @macpaw/macpaw-ui 5 | ``` 6 | 7 | ##### or with yarn: 8 | ```bash 9 | yarn add @macpaw/macpaw-ui 10 | ``` 11 | 12 | ##### And import stylesheets manually: 13 | ```js 14 | import '@macpaw/macpaw-ui/lib/ui.css' 15 | ``` 16 | 17 | ## Adding new Icons 18 | - Add Component's JSX and styles to [src/%ComponentName%](https://github.com/MacPaw/macpaw-ui/tree/master/src) directory 19 | - Import Component's JS, styles to [ui.js](https://github.com/MacPaw/macpaw-ui/blob/master/src/ui.js) and [ui.scss](https://github.com/MacPaw/macpaw-ui/blob/master/src/ui.scss) correspondingly 20 | - Add document page in: 21 | ``` 22 | /pages/%component-name%.mdx 23 | ``` 24 | 25 | ## Requirements 26 | - Node 20+ 27 | - npm 10+ 28 | - React.js 19.2+ 29 | 30 | -------------------------------------------------------------------------------- /src/Icons/jsx/DeviceActiveIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgDeviceActiveIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | ); 9 | 10 | export default SvgDeviceActiveIcon; 11 | -------------------------------------------------------------------------------- /.github/actions/github-config/action.yml: -------------------------------------------------------------------------------- 1 | name: 'github config' 2 | description: 'Update GIT config with signing key' 3 | inputs: 4 | gpg-key-base64: 5 | description: 'Base64 GPG key' 6 | required: true 7 | gpg-key-signing: 8 | description: 'Git signing key' 9 | required: true 10 | 11 | runs: 12 | using: "composite" 13 | steps: 14 | - run: | 15 | mkdir -p ${GITHUB_WORKSPACE}/.gpg 16 | echo ${{ inputs.gpg-key-base64 }} | base64 -d > ${GITHUB_WORKSPACE}/.gpg/private.key 17 | gpg --import ${GITHUB_WORKSPACE}/.gpg/private.key 18 | 19 | git config --global user.signingkey ${{ inputs.gpg-key-signing }} 20 | git config --global commit.gpgsign true 21 | git config user.name ci-macpaw 22 | git config user.email admin+ci-gh@macpaw.com 23 | shell: bash 24 | name: Update git config -------------------------------------------------------------------------------- /pages/docs/language-switcher.mdx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { LanguageSwitcher } from '../../src/ui'; 3 | import DropdownIcon from '../../src/Icons/jsx/DropdownIcon'; 4 | 5 | # Language Switcher 6 | 7 | export const Example = () => { 8 | const [currentLanguage, setCurrentLanguage] = useState('en'); 9 | return ( 10 | setCurrentLanguage(event.target.value)} 14 | /> 15 | ); 16 | }; 17 | 18 | 19 | 20 | ``` 21 | const [currentLanguage, setCurrentLanguage] = useState('en'); 22 | 23 | setCurrentLanguage(event.target.value)} 27 | /> 28 | ``` 29 | -------------------------------------------------------------------------------- /src/Icons/svg/back_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /internal/CodeBlock/CodeBlock.module.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'JetBrains Mono'; 3 | src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Regular.eot') format('embedded-opentype'), 4 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Regular.woff2') format('woff2'), 5 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Regular.woff') format('woff'), 6 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | .codeBlock { 12 | margin: 36px 0 64px; 13 | padding: 20px; 14 | overflow: auto; 15 | font-family: "JetBrains Mono", monospace; 16 | } 17 | -------------------------------------------------------------------------------- /internal/HomePage/homePage.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: absolute; 3 | inset: 0; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | background-color: #e7e7e7; 8 | color: #000; 9 | } 10 | 11 | .container { 12 | max-width: 1340px; 13 | padding: 30px; 14 | margin: 0 auto; 15 | } 16 | 17 | .heroSection { 18 | display: flex; 19 | text-align: center; 20 | align-items: center; 21 | flex-direction: column; 22 | gap: 20px; 23 | } 24 | 25 | .title { 26 | font-size: 40px; 27 | font-weight: 700; 28 | margin: 0; 29 | } 30 | 31 | .description { 32 | font-size: 15px; 33 | color: #707070; 34 | } 35 | 36 | .buttons { 37 | display: flex; 38 | gap: 10px; 39 | } 40 | 41 | @media screen and (max-width: 375px){ 42 | .buttons { 43 | flex-direction: column; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Icons/svg/settings_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { KeyboardEvent } from 'react'; 2 | 3 | export const uniqId = () => { 4 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 5 | const array = new Uint32Array(10); 6 | 7 | window.crypto.getRandomValues(array); 8 | 9 | return ( 10 | ( 11 | Date.now() 12 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 13 | .toString(36) + 14 | Array.from(array) 15 | .map((number) => 16 | number 17 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 18 | .toString(36), 19 | ) 20 | .join('') 21 | ).replace(/\./g, '') 22 | ); 23 | }; 24 | 25 | export const isAutofill = (e: KeyboardEvent) => 26 | // eslint-disable-next-line no-undefined 27 | e.key === 'Unidentified' && e.code === undefined && e.view === undefined; 28 | -------------------------------------------------------------------------------- /src/Icons/jsx/RefreshingIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgRefreshingIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 12 | 13 | 21 | 22 | 23 | ); 24 | 25 | export default SvgRefreshingIcon; 26 | -------------------------------------------------------------------------------- /src/Banner/Banner.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes, ReactNode } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface BannerProps extends HTMLAttributes { 5 | type?: 'secondary' | 'readonly'; 6 | icon?: ReactNode; 7 | action?: ReactNode; 8 | } 9 | 10 | const Banner: FC> = (props) => { 11 | const { className, children, type, icon, action, ...other } = props; 12 | 13 | const classNames = cx('banner', className, { 14 | '-secondary': type === 'secondary', 15 | '-readonly': type === 'readonly', 16 | }); 17 | 18 | return ( 19 |
    20 | {icon &&
    {icon}
    } 21 |
    {children}
    22 | {action &&
    {action}
    } 23 |
    24 | ); 25 | }; 26 | 27 | export default Banner; 28 | -------------------------------------------------------------------------------- /src/Icons/jsx/MenuIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgMenuIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default SvgMenuIcon; 31 | -------------------------------------------------------------------------------- /src/DialogActions/DialogActions.scss: -------------------------------------------------------------------------------- 1 | .dialogActions { 2 | padding: 24px; 3 | display: flex; 4 | justify-content: flex-end; 5 | align-items: center; 6 | 7 | @include mobile { 8 | flex-direction: column; 9 | } 10 | 11 | .button { 12 | flex-shrink: 0; 13 | 14 | @include mobile { 15 | width: 100%; 16 | } 17 | 18 | &:not(:last-child) { 19 | margin-right: 16px; 20 | 21 | @include mobile { 22 | margin-right: 0; 23 | margin-bottom: 16px; 24 | } 25 | } 26 | 27 | &.-asLink { 28 | margin-right: auto; 29 | font-size: 16px; 30 | font-weight: 600; 31 | } 32 | } 33 | 34 | .hint { 35 | margin-right: auto; 36 | padding-right: 24px; 37 | color: var(--color-black-64); 38 | 39 | @include mobile { 40 | max-width: 100%; 41 | margin-bottom: 16px; 42 | text-align: center; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Icons/svg/language_tr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Icons/jsx/NotificationIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgNotificationIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 8 | ); 9 | 10 | export default SvgNotificationIcon; 11 | -------------------------------------------------------------------------------- /src/Icons/jsx/SearchIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgSearchIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default SvgSearchIcon; 31 | -------------------------------------------------------------------------------- /src/Pagination/NextComponent.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import cx from 'clsx'; 3 | import BackIcon from '../Icons/jsx/BackIcon'; 4 | 5 | export interface NextComponentProps { 6 | currentPage: number; 7 | maxPage: number; 8 | renderItem: (n: number) => React.ReactElement; 9 | nextLabel: string; 10 | } 11 | 12 | const NextComponent: FC = ({ currentPage, maxPage, renderItem, nextLabel }) => { 13 | const isEnabled = currentPage < maxPage; 14 | const element = isEnabled ? renderItem(currentPage + 1) : React.createElement('div'); 15 | 16 | return React.cloneElement(element, { 17 | className: cx('pagination-nav', '-next', !isEnabled && '-disabled'), 18 | children: ( 19 | <> 20 | {nextLabel} 21 | 22 | 23 | ), 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 25 | } as any); 26 | }; 27 | 28 | export default NextComponent; 29 | -------------------------------------------------------------------------------- /src/Pagination/PrevComponent.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import cx from 'clsx'; 3 | import BackIcon from '../Icons/jsx/BackIcon'; 4 | 5 | export interface PrevComponentProps { 6 | currentPage: number; 7 | minPage: number; 8 | renderItem: (n: number) => React.ReactElement; 9 | prevLabel: string; 10 | } 11 | 12 | const PrevComponent: FC = ({ currentPage, minPage, renderItem, prevLabel }) => { 13 | const isEnabled = currentPage > minPage; 14 | const element = isEnabled ? renderItem(currentPage - 1) : React.createElement('div'); 15 | 16 | return React.cloneElement(element, { 17 | className: cx('pagination-nav', '-prev', !isEnabled && '-disabled'), 18 | children: ( 19 | <> 20 | 21 | {prevLabel} 22 | 23 | ), 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 25 | } as any); 26 | }; 27 | 28 | export default PrevComponent; 29 | -------------------------------------------------------------------------------- /src/Icons/svg/user_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Dropdown/Dropdown.scss: -------------------------------------------------------------------------------- 1 | .dropdown { 2 | display: inline-flex; 3 | position: relative; 4 | 5 | &.-open &-menu { 6 | visibility: visible; 7 | opacity: 1; 8 | transform: translateY(6px) scaleY(1); 9 | } 10 | 11 | &-menu { 12 | visibility: hidden; 13 | opacity: 0; 14 | position: absolute; 15 | left: 0; 16 | top: 100%; 17 | padding: 16px 0; 18 | background: var(--color-white); 19 | box-shadow: 0 8px 16px 0 rgb(0 0 0 / 8%); 20 | border-radius: 12px; 21 | width: 260px; 22 | transform-origin: 50% 0; 23 | transform: translateY(2px) scaleY(0.95); 24 | transition: 0.15s all ease-in-out; 25 | z-index: 1000; 26 | 27 | &.-right { 28 | left: auto; 29 | right: 0; 30 | } 31 | 32 | &.-center { 33 | left: 50%; 34 | margin-left: -130px; 35 | } 36 | } 37 | 38 | .button:not(.-icon) svg { 39 | width: 18px; 40 | height: 18px; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pages/docs/notification.mdx: -------------------------------------------------------------------------------- 1 | import { Button, NotificationsContainer, notify } from '../../src/ui'; 2 | 3 | # Notifications 4 | 5 | export const Example = () => { 6 | const onSuccess = () => notify('Success notification', 'success'); 7 | const onError = () => notify('Error notification', 'error'); 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | 18 | 19 | ``` 20 | const onSuccess = () => notify('Success notification', 'success'); 21 | const onError = () => notify('Error notification', 'error'); 22 | 23 | 24 | 25 | 26 | ``` 27 | -------------------------------------------------------------------------------- /src/Icons/jsx/CheckCircle.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgCheckCircle = (props: React.SVGProps) => ( 4 | 5 | 10 | 11 | ); 12 | 13 | export default SvgCheckCircle; 14 | -------------------------------------------------------------------------------- /src/Icons/svg/play_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/Banner/Banner.scss: -------------------------------------------------------------------------------- 1 | .banner { 2 | display: flex; 3 | flex-wrap: nowrap; 4 | align-items: center; 5 | justify-content: space-between; 6 | padding: 20px 24px; 7 | font-size: 14px; 8 | background: var(--color-attention); 9 | border-radius: 20px; 10 | min-height: 72px; // banner with button should be the same height as without 11 | 12 | @include mobile { 13 | flex-direction: column; 14 | } 15 | 16 | &.-secondary { 17 | background: rgba(113, 150, 225, 0.08); 18 | } 19 | 20 | &.-readonly { 21 | background: rgba(0, 0, 0, .08); 22 | } 23 | 24 | &-content { 25 | flex: 1; 26 | } 27 | 28 | &-icon { 29 | display: inline-flex; 30 | margin-right: 8px; 31 | } 32 | 33 | &-action { 34 | margin-left: 24px; 35 | 36 | @include mobile { 37 | margin-top: 16px; 38 | margin-left: 0; 39 | width: 100%; 40 | } 41 | 42 | .button { 43 | @include mobile { 44 | width: 100%; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Icons/svg/menu_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/DropdownItem/DropdownItem.scss: -------------------------------------------------------------------------------- 1 | .dropdownItem { 2 | display: block; 3 | padding: 16px 32px; 4 | width: 100%; 5 | border: none; 6 | outline: none; 7 | background: none; 8 | font-size: 14px; 9 | line-height: 1.2; 10 | box-shadow: none; 11 | border-radius: 0; 12 | text-align: left; 13 | text-decoration: none; 14 | color: var(--color-black); 15 | font-family: 'Montserrat', 'Helvetica Neue', sans-serif; 16 | 17 | &.-clickable { 18 | cursor: pointer; 19 | 20 | &:hover { 21 | background: var(--color-black-04); 22 | } 23 | 24 | &:focus { 25 | box-shadow: 0 0 0 3px var(--color-outline); 26 | } 27 | } 28 | 29 | &.-attention { 30 | color: var(--color-error); 31 | } 32 | 33 | &.-separator { 34 | display: block; 35 | width: auto; 36 | height: 2px; 37 | padding: 0; 38 | margin: 12px 32px; 39 | background: var(--color-black-08); 40 | } 41 | 42 | &[disabled] { 43 | pointer-events: none; 44 | color: var(--color-black-32); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Icons/jsx/LanguageTr.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgLanguageTr = (props: React.SVGProps) => ( 4 | 5 | 6 | 7 | 11 | 15 | 19 | 20 | 21 | ); 22 | 23 | export default SvgLanguageTr; 24 | -------------------------------------------------------------------------------- /src/Icons/jsx/StoreIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgStoreIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default SvgStoreIcon; 31 | -------------------------------------------------------------------------------- /pages/docs/switch.mdx: -------------------------------------------------------------------------------- 1 | import { Switch, FormRow } from '../../src/ui'; 2 | 3 | # Switch 4 | 5 | 6 |

    Default

    7 | 8 |
    9 | 10 | 11 |

    Disabled

    12 | 13 |
    14 | 15 | 16 |

    Error

    17 | Some label 18 |
    19 | 20 | 21 |

    With handler

    22 | alert('Value is change')} /> 23 |
    24 | 25 | 26 |

    With Label

    27 | change settings 28 |
    29 | 30 | ``` 31 | 32 |

    Default

    33 | 34 |
    35 | 36 | 37 |

    Disabled

    38 | 39 |
    40 | 41 | 42 |

    Error

    43 | 44 |
    45 | 46 | 47 |

    With handler

    48 | alert('Value is change')} /> 49 |
    50 | 51 | 52 |

    With Label

    53 | change settings 54 |
    55 | ``` 56 | -------------------------------------------------------------------------------- /src/Tag/Tag.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | import CloseIcon from '../Icons/jsx/CloseIcon'; 4 | 5 | export interface TagProps extends HTMLAttributes { 6 | color?: 'primary' | 'secondary' | 'warning' | 'custom'; 7 | as?: 'div' | 'span'; 8 | borderRadius?: string | number; 9 | onRemove?: () => void; 10 | } 11 | 12 | const Tag: FC> = ({ 13 | onRemove, 14 | className, 15 | borderRadius, 16 | color = 'secondary', 17 | as: Element = 'div', 18 | children, 19 | ...other 20 | }) => { 21 | const tagClassNames = cx('tag', className, { 22 | [`-${color}`]: color, 23 | 'tag-action': onRemove, 24 | }); 25 | 26 | return ( 27 | 28 | {onRemove && ( 29 | 32 | )} 33 | {children} 34 | 35 | ); 36 | }; 37 | 38 | export default Tag; 39 | -------------------------------------------------------------------------------- /internal/CodeBlock/CodeBlock.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Highlight, { defaultProps } from 'prism-react-renderer' 3 | import theme from 'prism-react-renderer/themes/nightOwlLight'; 4 | import cx from 'clsx' 5 | import styles from './CodeBlock.module.css'; 6 | 7 | const CodeBlock = ({ children, className }) => { 8 | const language = className ? className.replace(/language-/, '') : 'js'; 9 | 10 | return ( 11 | 12 | {({ className, style, tokens, getLineProps, getTokenProps }) => ( 13 |
    14 |           {tokens.map((line, i) => (
    15 |             
    16 | {line.map((token, key) => ( 17 | 18 | ))} 19 |
    20 | ))} 21 |
    22 | )} 23 |
    24 | ); 25 | }; 26 | 27 | export default CodeBlock; 28 | -------------------------------------------------------------------------------- /pages/docs/pagination.mdx: -------------------------------------------------------------------------------- 1 | import { Pagination } from '../../src/ui'; 2 | 3 | # Pagination 4 | 5 | ({n})} /> 6 | 7 | ``` 8 | ({n})} /> 9 | ``` 10 | 11 | ## Left range 12 | 13 | ({n})} /> 14 | 15 | ``` 16 | ({n})} /> 17 | ``` 18 | 19 | ## Right range 20 | 21 | ({n})} /> 22 | 23 | ``` 24 | ({n})} /> 25 | ``` 26 | 27 | ## Small range 28 | 29 | ({n})} /> 30 | 31 | ``` 32 | ({n})} /> 33 | ``` 34 | -------------------------------------------------------------------------------- /src/Icons/jsx/WarningIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgWarningIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default SvgWarningIcon; 31 | -------------------------------------------------------------------------------- /src/Accordion/AccordionTrigger.tsx: -------------------------------------------------------------------------------- 1 | import React, { HTMLAttributes, useContext } from 'react'; 2 | import AccordionContext from './AccordionContext'; 3 | 4 | export interface AccordionTrigger extends HTMLAttributes { 5 | sectionKey: string; 6 | onClick?: (event: React.SyntheticEvent) => void; 7 | } 8 | 9 | const AccordionTrigger: React.FC> = (props) => { 10 | const { children, sectionKey, onClick, ...other } = props; 11 | const { activeKey, onToggle } = useContext(AccordionContext); 12 | const onTrigger = (event: React.SyntheticEvent) => { 13 | onToggle(sectionKey === activeKey ? null : sectionKey); 14 | if (onClick) onClick(event); 15 | }; 16 | 17 | return ( 18 |
    { 23 | if (event.key === 'Enter') onTrigger(event); 24 | }} 25 | {...other} 26 | > 27 | {children} 28 |
    29 | ); 30 | }; 31 | 32 | export default AccordionTrigger; 33 | -------------------------------------------------------------------------------- /src/Grid/Grid.tsx: -------------------------------------------------------------------------------- 1 | import React, { ElementType, ReactNode } from 'react'; 2 | import Panel, { PanelProps } from '../Panel/Panel'; 3 | 4 | interface Grid extends PanelProps { 5 | icon?: ReactNode; 6 | action?: ReactNode; 7 | notification?: ReactNode; 8 | component?: ReactNode; 9 | } 10 | 11 | const Grid: React.FC> = (props) => { 12 | const { icon, action, notification, children, className, component, ...other } = props; 13 | let gridClassNames = 'grid'; 14 | 15 | if (className) gridClassNames += ` ${className}`; 16 | const Component = component ? (component as ElementType) : Panel; 17 | 18 | return ( 19 | 20 |
    21 | {icon &&
    {icon}
    } 22 |
    {children}
    23 |
    {action}
    24 |
    25 | {notification &&
    {notification}
    } 26 |
    27 | ); 28 | }; 29 | 30 | export default Grid; 31 | -------------------------------------------------------------------------------- /src/Breadcrumbs/Breadcrumbs.scss: -------------------------------------------------------------------------------- 1 | .breadcrumbs { 2 | display: flex; 3 | flex-wrap: wrap; 4 | align-items: center; 5 | list-style-type: none; 6 | margin: 0; 7 | padding: 0; 8 | 9 | li { 10 | color: #808080; 11 | margin-right: 38px; 12 | 13 | &:last-child { 14 | margin-right: 0; 15 | } 16 | } 17 | 18 | a { 19 | position: relative; 20 | text-decoration: none; 21 | color: var(--color-black); 22 | 23 | &:before { 24 | content: ''; 25 | position: absolute; 26 | left: 100%; 27 | top: 50%; 28 | margin-top: -8px; 29 | margin-left: 11px; 30 | width: 16px; 31 | height: 16px; 32 | background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSIxOCIgdmlld0JveD0iMCAwIDExIDE4Ij4KICA8cG9seWxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIiIHBvaW50cz0iOCAxMiAxNiAyMCAyNCAxMiIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDcgMTgpIi8+Cjwvc3ZnPgo=") center no-repeat; 33 | background-size: contain; 34 | opacity: 0.25; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Icons/jsx/InfoIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgInfoIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | 32 | export default SvgInfoIcon; 33 | -------------------------------------------------------------------------------- /src/Icons/jsx/ErrorIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgErrorIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default SvgErrorIcon; 31 | -------------------------------------------------------------------------------- /src/Icons/svg/store_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Icons/svg/copy_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /internal/Navigation/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import styles from "./Navigation.module.css"; 4 | import { pages } from "../config/pages"; 5 | import ActiveLink from "../ActiveLink/ActiveLink"; 6 | 7 | const Navigation = () => { 8 | return ( 9 |
    10 | 11 | MacPaw UI Kit 12 | 13 |
    14 | 19 | Installation 20 | 21 | {[...pages].sort().map((link) => ( 22 | 28 | {link.replaceAll("-", " ")} 29 | 30 | ))} 31 |
    32 |
    33 | ); 34 | }; 35 | 36 | export default Navigation; 37 | -------------------------------------------------------------------------------- /src/DropdownItem/DropdownItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { ButtonHTMLAttributes, ElementType, ReactNode } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface DropdownItem extends ButtonHTMLAttributes { 5 | component?: ReactNode; 6 | href?: string; 7 | attention?: boolean; 8 | withoutAction?: boolean; 9 | separator?: boolean; 10 | to?: string; 11 | } 12 | 13 | const DropdownItem: React.FC> = (props) => { 14 | const { children, component = 'button', className, attention, withoutAction, separator, ...other } = props; 15 | 16 | let Component = component as ElementType; 17 | 18 | if (Component === 'button' && other?.href) Component = 'a'; 19 | 20 | if (withoutAction || separator) Component = 'div'; 21 | 22 | const classNames = cx('dropdownItem', className, { 23 | '-attention': attention, 24 | '-clickable': !(separator || withoutAction), 25 | '-separator': separator, 26 | }); 27 | 28 | return ( 29 | 30 | {children} 31 | 32 | ); 33 | }; 34 | 35 | export default DropdownItem; 36 | -------------------------------------------------------------------------------- /src/Icons/jsx/BackIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgBackIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default SvgBackIcon; 31 | -------------------------------------------------------------------------------- /internal/ActiveLink/ActiveLink.js: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import Link from 'next/link'; 3 | import { useState, useEffect } from 'react'; 4 | import clsx from 'clsx'; 5 | 6 | const ActiveLink = ({ children, className, activeClassName, ...props }) => { 7 | const { asPath, isReady } = useRouter(); 8 | const [computedClassName, setComputedClassName] = useState(className); 9 | 10 | useEffect(() => { 11 | if (isReady) { 12 | const linkPathname = new URL(props.as || props.href, location.href) 13 | .pathname; 14 | 15 | const activePathname = new URL(asPath, location.href).pathname.replace( 16 | /\/$/, 17 | '' 18 | ); 19 | 20 | const newClassName = clsx(className, { 21 | [activeClassName]: linkPathname === activePathname, 22 | }); 23 | 24 | if (newClassName !== computedClassName) { 25 | setComputedClassName(newClassName); 26 | } 27 | } 28 | }, [asPath, isReady, props.as, props.href, computedClassName]); 29 | 30 | return ( 31 | 32 | {children} 33 | 34 | ); 35 | }; 36 | 37 | export default ActiveLink; 38 | -------------------------------------------------------------------------------- /src/Icons/svg/account_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Icons/jsx/SettingsIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgSettingsIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | 32 | export default SvgSettingsIcon; 33 | -------------------------------------------------------------------------------- /src/Tabs/TabList.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { HTMLAttributes, PropsWithChildren, useEffect } from 'react'; 3 | import { DEFAULT_TAB_TYPE } from './Tab'; 4 | import { useTabsContext } from './TabContext'; 5 | 6 | interface TabListProps extends Omit, 'role'> {} 7 | 8 | const TabList: React.FC> = ({ children, ...restProps }) => { 9 | const { activeTab, onSelectTab } = useTabsContext(); 10 | 11 | useEffect(() => { 12 | if (activeTab) return; 13 | 14 | const [firstTab] = React.Children.toArray(children); 15 | 16 | if (!React.isValidElement(firstTab)) { 17 | throw new Error('TabList must have only Tab component as a child.'); 18 | } 19 | 20 | const props = firstTab.props as { __TYPE?: string; tab: string }; 21 | 22 | if (props.__TYPE !== DEFAULT_TAB_TYPE) { 23 | throw new Error('TabList must have only Tab component as a child.'); 24 | } 25 | 26 | onSelectTab(props.tab); 27 | }, [children, activeTab]); 28 | 29 | return ( 30 |
    31 | {children} 32 |
    33 | ); 34 | }; 35 | 36 | export default TabList; 37 | -------------------------------------------------------------------------------- /src/Icons/jsx/UserIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgUserIcon = (props: React.SVGProps) => ( 4 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | 32 | export default SvgUserIcon; 33 | -------------------------------------------------------------------------------- /pages/docs/payment.mdx: -------------------------------------------------------------------------------- 1 | import { Payment } from '../../src/ui'; 2 | 3 | # Payment 4 | 5 |
    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
    23 | 24 | ``` 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ``` -------------------------------------------------------------------------------- /src/MacPawLogo/MacPawLogo.tsx: -------------------------------------------------------------------------------- 1 | import React, { ElementType, FC, HTMLAttributes, ReactNode } from 'react'; 2 | import cx from 'clsx'; 3 | import { MacpawText, PawIcon } from '../Icons/jsx'; 4 | 5 | export interface MacPawLogoProps extends HTMLAttributes { 6 | className?: string; 7 | disableRotate?: boolean; 8 | component?: ReactNode | string; 9 | pawProps?: ObjectLiteral; 10 | textProps?: ObjectLiteral; 11 | inline?: boolean; 12 | href?: string; 13 | target?: string; 14 | to?: string; 15 | } 16 | 17 | const MacPawLogo: FC> = (props) => { 18 | const { pawProps, textProps, className = '', disableRotate = false, inline, component, ...rest } = props; 19 | 20 | const Component = (component as ElementType) || 'div'; 21 | 22 | return ( 23 | 29 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | export default MacPawLogo; 41 | -------------------------------------------------------------------------------- /src/Icons/svg/payment_visa.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /pages/docs/banner.mdx: -------------------------------------------------------------------------------- 1 | import { Banner, Button } from '../../src/ui'; 2 | import { RefreshingIcon } from '../../src/Icons/jsx'; 3 | 4 | # Banner 5 | 6 | ## Needs Attention 7 | 8 | Update Billing Method}> 9 | Payment failed. Please update your billing method. 10 | 11 | 12 | ``` 13 | Update Billing Method}> 14 | Payment failed. Please update your billing method. 15 | 16 | ``` 17 | 18 | ## Secondary 19 | 20 | Resend Email} icon={}> 21 | Please confirm your email by clicking the link we sent to email 22 | 23 | 24 | ``` 25 | Resend Email} icon={}> 26 | Please confirm your email by clicking the link we sent to email 27 | 28 | ``` 29 | 30 | ## Readonly 31 | 32 | 33 | Payment failed. Please update your billing method. 34 | 35 | 36 | ``` 37 | 38 | Payment failed. Please update your billing method. 39 | 40 | ``` 41 | -------------------------------------------------------------------------------- /src/Loader/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | 4 | export interface LoaderProps extends HTMLAttributes { 5 | size?: number; 6 | inline?: boolean; 7 | } 8 | const DEFAULT_LOADER_SIZE = 75; 9 | 10 | const Loader: FC> = (props) => { 11 | const { size = DEFAULT_LOADER_SIZE, inline = false, className, ...other } = props; 12 | 13 | return ( 14 |
    15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | 31 | 32 |
    33 | ); 34 | }; 35 | 36 | export default Loader; 37 | -------------------------------------------------------------------------------- /internal/HomePage/HomePage.js: -------------------------------------------------------------------------------- 1 | import styles from './homePage.module.css'; 2 | import Button from '../../src/Button/Button'; 3 | import Link from 'next/link'; 4 | 5 | export default function HomePage() { 6 | return ( 7 |
    8 |
    9 |
    10 |

    MacPaw UI

    11 | 12 | With MacPaw's UI Library, you're not just building interfaces; 13 | you're crafting experiences. 14 | 15 |
    16 | 17 | 18 | 19 | 20 | 32 | 33 |
    34 |
    35 |
    36 |
    37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /internal/Palette/Palette.module.css: -------------------------------------------------------------------------------- 1 | .palette { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, minmax(160px, 180px)); 4 | column-gap: 32px; 5 | row-gap: 32px; 6 | } 7 | 8 | .palette:not(:last-child) { 9 | margin-bottom: 64px; 10 | } 11 | 12 | .paletteCell { 13 | border-radius: 20px; 14 | height: 100px; 15 | overflow: hidden; 16 | } 17 | 18 | .paletteColor { 19 | height: 100%; 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | 25 | .paletteText { 26 | margin-top: 6px; 27 | font-size: 12px; 28 | font-weight: 600; 29 | text-align: center; 30 | } 31 | 32 | .paletteGradients { 33 | grid-template-columns: 1fr 1fr; 34 | } 35 | 36 | .paletteGradients .paletteCell { 37 | display: flex; 38 | } 39 | 40 | .paletteGradients .paletteColor { 41 | width: 160px; 42 | } 43 | 44 | .paletteGradient { 45 | position: relative; 46 | flex: 1; 47 | min-width: 300px; 48 | } 49 | 50 | .paletteGradientColor { 51 | position: absolute; 52 | top: 50%; 53 | transform: translateY(-50%); 54 | } 55 | 56 | .paletteGradientColor:first-child { 57 | left: 48px; 58 | } 59 | 60 | .paletteGradientColor:last-child { 61 | right: 48px; 62 | } 63 | 64 | @media screen and (max-width: 1024px) { 65 | .paletteGradients { 66 | grid-template-columns: 1fr; 67 | overflow: scroll; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Dialog/Dialog.scss: -------------------------------------------------------------------------------- 1 | .dialog { 2 | display: flex; 3 | flex-direction: column; 4 | position: relative; 5 | width: 100%; 6 | margin: auto; 7 | max-width: 736px; 8 | max-height: 100%; 9 | background: #f7f7f7; 10 | border-radius: 20px; 11 | 12 | @include mobile { 13 | height: 100%; 14 | max-width: none; 15 | max-height: none; 16 | border-radius: 0; 17 | } 18 | 19 | &-root { 20 | position: fixed; 21 | z-index: 9000; 22 | top: 0; 23 | left: 0; 24 | bottom: 0; 25 | right: 0; 26 | padding: 16px; 27 | display: flex; 28 | align-items: center; 29 | justify-content: center; 30 | 31 | @include mobile { 32 | padding: 0; 33 | } 34 | } 35 | 36 | &-overlay { 37 | position: fixed; 38 | top: 0; 39 | left: 0; 40 | bottom: 0; 41 | right: 0; 42 | background-color: rgba(218, 218, 218, .72); 43 | 44 | &:focus { 45 | outline: none; 46 | } 47 | } 48 | 49 | &-close { 50 | display: inline-flex; 51 | position: absolute; 52 | left: 6px; 53 | top: 6px; 54 | padding: 12px; 55 | border-radius: 20px; 56 | border: none; 57 | background: none; 58 | cursor: pointer; 59 | outline: none; 60 | 61 | &:focus { 62 | box-shadow: 0 0 0 3px var(--color-outline); 63 | } 64 | } 65 | 66 | >form { 67 | display: contents; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pages/docs/table.mdx: -------------------------------------------------------------------------------- 1 | import { Table, TableRow } from '../../src/ui'; 2 | 3 | # Table 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
    DateDescriptionStatusAmount
    Sep 4, 2020PaymentSuccessful$20.00Sep 4, 2020PaymentSuccessful$20.00
    29 | 30 | ``` 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
    DateDescriptionStatusAmount
    Sep 4, 2020PaymentSuccessful$20.00Sep 4, 2020PaymentSuccessful$20.00
    55 | ``` -------------------------------------------------------------------------------- /src/Icons/jsx/AppStoreIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const SvgAppStoreIcon = (props: React.SVGProps) => ( 4 | 5 | 6 | 11 | 12 | 13 | ); 14 | 15 | export default SvgAppStoreIcon; 16 | -------------------------------------------------------------------------------- /src/Select/Select.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactNode, SelectHTMLAttributes } from 'react'; 2 | import cx from 'clsx'; 3 | import Hint from '../Hint/Hint'; 4 | import { Error } from '../types'; 5 | 6 | export interface SelectProps extends SelectHTMLAttributes { 7 | scale?: 'medium' | 'small' | 'big'; 8 | selected?: string; 9 | disabled?: boolean; 10 | error?: Error; 11 | label?: string | ReactNode; 12 | } 13 | 14 | const Select: FC> = (props) => { 15 | const { className, selected, disabled, error, scale, style, children, label, ...other } = props; 16 | 17 | const classNames = cx('select', className, { 18 | '-error': Boolean(error), 19 | '-medium': scale === 'medium', 20 | '-small': scale === 'small', 21 | '-big': scale === 'big', 22 | }); 23 | 24 | const showHintError = error && typeof error !== 'boolean'; 25 | 26 | return ( 27 | 43 | ); 44 | }; 45 | 46 | export default Select; 47 | -------------------------------------------------------------------------------- /src/Radio/Radio.scss: -------------------------------------------------------------------------------- 1 | .radio { 2 | display: inline-flex; 3 | align-items: center; 4 | font-size: 14px; 5 | cursor: pointer; 6 | user-select: none; 7 | 8 | &__input[type=radio].radio__input_default { 9 | color: var(--color-black-16); 10 | 11 | &:checked, &:hover { 12 | color: var(--color-secondary); 13 | } 14 | } 15 | 16 | &.-error, .-error[type=radio] { 17 | color: var(--color-error); 18 | cursor: not-allowed; 19 | 20 | &:hover, &:checked { 21 | color: var(--color-error); 22 | } 23 | } 24 | 25 | input[type=radio] { 26 | width: 1em; 27 | height: 1em; 28 | margin: 0; 29 | margin-right: 12px; 30 | font: inherit; 31 | -webkit-appearance: none; 32 | appearance: none; 33 | color: currentColor; 34 | background-color: transparent; 35 | border: 0.125em solid currentColor; 36 | border-radius: 50%; 37 | transition: color .3s; 38 | 39 | &:checked { 40 | border: 0.3125em solid currentColor; 41 | } 42 | 43 | &:focus-visible { 44 | outline: 0.15em solid color-mix(in srgb, currentColor 30%, transparent); 45 | outline-offset: 0; 46 | } 47 | 48 | &:disabled { 49 | cursor: not-allowed; 50 | background-color: var(--color-black-16); 51 | border: 0; 52 | 53 | &:checked { 54 | background-color: transparent; 55 | border: 0.125em solid var(--color-black-16); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Multiselect/Multiselect.scss: -------------------------------------------------------------------------------- 1 | .multiselect { 2 | position: relative; 3 | display: flex; 4 | flex-wrap: wrap; 5 | align-items: center; 6 | padding: 10px; 7 | border-radius: 8px; 8 | border: 2px solid rgba(111, 114, 121, 0.16); 9 | outline: none; 10 | 11 | &:focus, 12 | &:focus-within { 13 | border-color: var(--color-secondary); 14 | box-shadow: 0 0 0 3px var(--color-outline); 15 | } 16 | 17 | &:not(:focus):not(:focus-within):not([disabled]):not(.-error):hover { 18 | border-color: var(--color-black); 19 | } 20 | 21 | &.-error { 22 | border-color: var(--color-error); 23 | } 24 | 25 | &.-empty { 26 | height: 48px; 27 | 28 | .select select { 29 | opacity: 1; 30 | } 31 | } 32 | 33 | &.-medium { 34 | padding: 6px; 35 | 36 | .select select { 37 | padding-top: 0; 38 | padding-bottom: 0; 39 | } 40 | 41 | &.-empty { 42 | height: 40px; 43 | } 44 | } 45 | 46 | .select select { 47 | appearance: none; 48 | -webkit-appearance: none; 49 | -moz-appearance: none; 50 | position: absolute; 51 | left: 0; 52 | top: 0; 53 | width: 100%; 54 | height: 100%; 55 | opacity: 0; 56 | border: none; 57 | box-shadow: none; 58 | outline: none; 59 | } 60 | 61 | .tag { 62 | position: relative; 63 | pointer-events: none; 64 | z-index: 1; 65 | 66 | button { 67 | pointer-events: auto; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Tabs/Tab.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, PropsWithChildren } from 'react'; 2 | import cx from 'clsx'; 3 | import Button from '../Button/Button'; 4 | import { useTabsContext } from './TabContext'; 5 | 6 | export const DEFAULT_TAB_TYPE = 'Tab'; 7 | 8 | export interface TabProps extends Omit, 'role' | 'color'> { 9 | tab: string; 10 | activeClassName?: string; 11 | __TYPE?: typeof DEFAULT_TAB_TYPE; 12 | } 13 | 14 | const Tab: React.FC> = ({ 15 | children, 16 | tab, 17 | className, 18 | activeClassName = '', 19 | __TYPE = DEFAULT_TAB_TYPE, 20 | ...restProps 21 | }) => { 22 | const { activeTab, onSelectTab, color, activeColor, scale } = useTabsContext(); 23 | 24 | const isActive = tab === activeTab; 25 | 26 | const tabClassName = cx(className, { 27 | [activeClassName]: isActive, 28 | }); 29 | 30 | const buttonColor = isActive ? activeColor : color; 31 | 32 | const handleSelectTab = (): void => onSelectTab(tab); 33 | 34 | useEffect(() => { 35 | if (__TYPE !== DEFAULT_TAB_TYPE) throw new Error('You may NOT pass in a prop value for __TYPE.'); 36 | }, [__TYPE]); 37 | 38 | return ( 39 | 49 | ); 50 | }; 51 | 52 | export default Tab; 53 | -------------------------------------------------------------------------------- /.github/actions/prepare-node/action.yml: -------------------------------------------------------------------------------- 1 | name: "Prepare Node" 2 | description: "Sets up Node.js and runs install if it's needed" 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 | scope: 18 | description: 'The scope to use' 19 | required: false 20 | default: '' 21 | install-dependencies: 22 | description: 'Whether to install dependencies' 23 | required: false 24 | default: true 25 | 26 | runs: 27 | using: composite 28 | steps: 29 | - name: Install pnpm 30 | if: ${{ inputs.package-manager == 'pnpm' }} 31 | uses: pnpm/action-setup@v2.2.4 32 | with: 33 | version: 8 34 | run_install: false 35 | 36 | - name: Setup Node version 37 | uses: actions/setup-node@v3 38 | with: 39 | node-version: ${{ inputs.node-version }} 40 | cache: ${{ inputs.node-cache }} 41 | registry-url: ${{ inputs.registry-url }} 42 | scope: ${{ inputs.scope }} 43 | 44 | - name: Install dependencies 45 | shell: bash 46 | if: ${{ inputs.install-dependencies == 'true' }} 47 | run: ${{ inputs.package-manager }} install 48 | -------------------------------------------------------------------------------- /src/Icons/svg/payment_card.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @macpaw/macpaw-ui 2 | 3 | ## 5.0.6 4 | 5 | ### Patch Changes 6 | 7 | - 2c7a7a9: Just a release to check new action 8 | 9 | ## 5.0.5 10 | 11 | ### Patch Changes 12 | 13 | - 3b16c42: Just a release to check new action 14 | 15 | ## 5.0.4 16 | 17 | ### Patch Changes 18 | 19 | - deda836: Just triger release to check trusted publishing 20 | 21 | ## 5.0.3 22 | 23 | ### Patch Changes 24 | 25 | - 4433cdf: Just triger release to check trusted publishing 26 | 27 | ## 5.0.2 28 | 29 | ### Patch Changes 30 | 31 | - 79849a6: Next js update to 15.5.7 to fix CVE-2025-66478 (Next.js) 32 | 33 | ## 5.0.1 34 | 35 | ### Patch Changes 36 | 37 | - - Fix build issue 38 | 39 | ## 5.0.0 40 | 41 | ### Major Changes 42 | 43 | - 1de0d8e: - Migration to React 19 44 | - updated major dependencies to latest version 45 | - fixed all known vulnerabilities 46 | 47 | ## 4.18.0 48 | 49 | ### Minor Changes 50 | 51 | - 914d300: Added new pyament methods 52 | 53 | ## 4.17.0 54 | 55 | ### Minor Changes 56 | 57 | - f6428c0: - bump Node.js version from 16 to 20 58 | - update ui-kit iner UI 59 | - fix warnings about usage of deprecated methods like defaultProps 60 | - fix warining in FormRow component 61 | - merge dependabot PRs 62 | - bump Next.js from 12 to 13 63 | 64 | ## 4.16.2 65 | 66 | ### Patch Changes 67 | 68 | - d4147a6: Patch changes to trigger release 69 | - a5e1f6d: first automatic patch version upgrade 70 | - b453403: Changes to trigger patch release for testing that auto release is working well 71 | -------------------------------------------------------------------------------- /src/LanguageIcon/LanguageIcon.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, HTMLAttributes } from 'react'; 2 | import LanguageDe from '../Icons/jsx/LanguageDe'; 3 | import LanguageEn from '../Icons/jsx/LanguageEn'; 4 | import LanguageEs from '../Icons/jsx/LanguageEs'; 5 | import LanguageFr from '../Icons/jsx/LanguageFr'; 6 | import LanguageIt from '../Icons/jsx/LanguageIt'; 7 | import LanguageJa from '../Icons/jsx/LanguageJa'; 8 | import LanguageKo from '../Icons/jsx/LanguageKo'; 9 | import LanguageNl from '../Icons/jsx/LanguageNl'; 10 | import LanguagePl from '../Icons/jsx/LanguagePl'; 11 | import LanguagePt from '../Icons/jsx/LanguagePt'; 12 | import LanguageTr from '../Icons/jsx/LanguageTr'; 13 | import LanguageUk from '../Icons/jsx/LanguageUk'; 14 | import LanguageZh from '../Icons/jsx/LanguageZh'; 15 | 16 | const LanguageIcons = { 17 | en: LanguageEn, 18 | de: LanguageDe, 19 | es: LanguageEs, 20 | fr: LanguageFr, 21 | it: LanguageIt, 22 | ja: LanguageJa, 23 | ko: LanguageKo, 24 | nl: LanguageNl, 25 | pl: LanguagePl, 26 | pt: LanguagePt, 27 | tr: LanguageTr, 28 | uk: LanguageUk, 29 | zh: LanguageZh, 30 | }; 31 | 32 | export interface LanguageIconProps extends HTMLAttributes { 33 | language?: keyof typeof LanguageIcons; 34 | } 35 | 36 | const LanguageIcon: FC> = (props) => { 37 | const { language, ...other } = props; 38 | 39 | const Component = language ? LanguageIcons[language] : LanguageEn; 40 | 41 | return ; 42 | }; 43 | 44 | export default LanguageIcon; 45 | -------------------------------------------------------------------------------- /src/Password/Password.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, InputHTMLAttributes, ReactNode, useState } from 'react'; 2 | import Button from '../Button/Button'; 3 | import Input from '../Input/Input'; 4 | import { Error, InputValueType } from '../types'; 5 | 6 | export interface PasswordProps extends Omit, 'onChange'> { 7 | scale?: 'medium' | 'small'; 8 | label?: string | ReactNode; 9 | error?: Error; 10 | withToggle?: boolean; 11 | i18nToggle?: (isPasswordVisible: boolean) => string; 12 | onToggle?: () => void; 13 | onChange?: (value: InputValueType, event?: React.ChangeEvent) => void; 14 | } 15 | 16 | const i18nToggleDefault = (isPasswordVisible: boolean) => (isPasswordVisible ? 'Hide' : 'Show'); 17 | 18 | const Password: FC> = (props) => { 19 | const { withToggle, i18nToggle = i18nToggleDefault, onToggle, ...other } = props; 20 | const [passwordVisible, setPasswordVisible] = useState(false); 21 | const toggleHandler = (event: React.MouseEvent) => { 22 | event.preventDefault(); 23 | setPasswordVisible(!passwordVisible); 24 | if (onToggle) onToggle(); 25 | }; 26 | 27 | return ( 28 | 34 | {i18nToggle(passwordVisible)} 35 | 36 | ) 37 | } 38 | /> 39 | ); 40 | }; 41 | 42 | export default Password; 43 | -------------------------------------------------------------------------------- /src/Icons/jsx/PaymentVisa.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SVGProps } from 'react'; 3 | 4 | const SvgPaymentVisa = (props: SVGProps) => ( 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 | Menu Icon 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 | 50 | 51 | ``` 52 | 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 | 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 | 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 | 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 | 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 | --------------------------------------------------------------------------------