├── .browserlistrc ├── .editorconfig ├── .env.sample ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .nvmrc ├── .prettierrc.json ├── .storybook ├── .babelrc ├── main.js ├── preview-head.html └── preview.js ├── .yarn └── releases │ └── yarn-3.1.1.cjs ├── .yarnrc.yml ├── Dockerfile ├── README.md ├── components ├── button │ ├── component.stories.tsx │ ├── component.tsx │ ├── constants.ts │ ├── index.ts │ └── types.d.ts ├── forms │ ├── checkbox │ │ ├── component.stories.tsx │ │ ├── component.tsx │ │ ├── constants.ts │ │ ├── index.ts │ │ └── types.d.ts │ ├── input │ │ ├── component.stories.tsx │ │ ├── component.tsx │ │ ├── constants.ts │ │ ├── index.ts │ │ └── types.d.ts │ ├── label │ │ ├── component.stories.tsx │ │ ├── component.tsx │ │ ├── constants.ts │ │ ├── index.ts │ │ └── types.d.ts │ ├── radio │ │ ├── component.stories.tsx │ │ ├── component.tsx │ │ ├── constants.ts │ │ ├── index.ts │ │ └── types.d.ts │ └── textarea │ │ ├── component.stories.tsx │ │ ├── component.tsx │ │ ├── index.ts │ │ └── types.d.ts ├── icon │ ├── component.stories.tsx │ ├── component.tsx │ ├── index.ts │ └── types.d.ts ├── loading │ ├── component.stories.tsx │ ├── component.tsx │ ├── index.ts │ └── types.d.ts ├── modal │ ├── component.stories.tsx │ ├── component.tsx │ ├── content │ │ ├── component.tsx │ │ ├── constants.ts │ │ ├── index.ts │ │ └── types.ts │ ├── index.ts │ └── types.d.ts └── tag │ ├── component.stories.tsx │ ├── component.tsx │ ├── index.ts │ └── types.d.ts ├── constants ├── breakpoints.js ├── cards.tsx ├── colors.js └── images.ts ├── containers ├── 404 │ ├── component.tsx │ └── index.ts ├── .gitkeep ├── coming-soon │ ├── ball │ │ ├── component.tsx │ │ └── index.ts │ ├── component.tsx │ └── index.ts ├── contact │ ├── button │ │ ├── component.tsx │ │ └── index.ts │ ├── component.tsx │ ├── form │ │ ├── answer │ │ │ ├── component.tsx │ │ │ └── index.ts │ │ ├── component.tsx │ │ ├── constants.ts │ │ ├── index.ts │ │ └── question │ │ │ ├── component.tsx │ │ │ └── index.ts │ ├── index.ts │ └── modal │ │ ├── component.tsx │ │ └── index.ts ├── cookies │ ├── component.tsx │ └── index.ts ├── favicon │ ├── component.tsx │ └── index.ts ├── header │ ├── component.tsx │ └── index.ts ├── home │ ├── component.tsx │ ├── index.ts │ ├── intro │ │ ├── cards │ │ │ ├── card │ │ │ │ ├── component.tsx │ │ │ │ └── index.ts │ │ │ ├── component.tsx │ │ │ └── index.ts │ │ ├── component.tsx │ │ └── index.ts │ ├── scroller │ │ ├── component.tsx │ │ ├── fake-item │ │ │ ├── component.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ └── item │ │ │ ├── component.tsx │ │ │ ├── content │ │ │ ├── common │ │ │ │ ├── footer │ │ │ │ │ ├── component.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── hero │ │ │ │ │ ├── component.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── img-txt │ │ │ │ │ ├── component.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── img │ │ │ │ │ ├── component.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── txt-3-columns │ │ │ │ │ ├── component.tsx │ │ │ │ │ └── index.ts │ │ │ ├── contact │ │ │ │ ├── component.tsx │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── lab │ │ │ │ ├── component.tsx │ │ │ │ └── index.ts │ │ │ ├── method │ │ │ │ ├── component.tsx │ │ │ │ └── index.ts │ │ │ ├── principles │ │ │ │ ├── component.tsx │ │ │ │ └── index.ts │ │ │ ├── services │ │ │ │ ├── component.tsx │ │ │ │ └── index.ts │ │ │ └── studio │ │ │ │ ├── component.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ └── section │ │ ├── component.tsx │ │ └── index.ts └── mouse │ ├── component.tsx │ ├── index.ts │ └── types.d.ts ├── cypress-unit.json ├── cypress.json ├── cypress ├── fixtures │ ├── example.json │ ├── profile.json │ └── users.json ├── integration │ └── examples │ │ ├── actions.spec.js │ │ ├── aliasing.spec.js │ │ ├── assertions.spec.js │ │ ├── connectors.spec.js │ │ ├── cookies.spec.js │ │ ├── cypress_api.spec.js │ │ ├── files.spec.js │ │ ├── local_storage.spec.js │ │ ├── location.spec.js │ │ ├── misc.spec.js │ │ ├── navigation.spec.js │ │ ├── network_requests.spec.js │ │ ├── querying.spec.js │ │ ├── spies_stubs_clocks.spec.js │ │ ├── traversal.spec.js │ │ ├── utilities.spec.js │ │ ├── viewport.spec.js │ │ ├── waiting.spec.js │ │ └── window.spec.js ├── plugins │ └── index.js ├── support │ ├── commands.js │ └── index.js └── unit │ └── unit-example.spec.js ├── docs ├── deploy.stories.mdx ├── fetching.stories.mdx ├── install.stories.mdx ├── intro.stories.mdx ├── media.stories.mdx └── tests.stories.mdx ├── hooks ├── articles │ └── index.ts ├── categories │ └── index.ts ├── contact │ └── index.ts ├── images │ └── index.ts ├── interval │ └── index.ts ├── scroll │ ├── index.tsx │ └── types.ts ├── size │ └── index.ts ├── steps │ └── index.ts ├── timeout │ └── index.ts └── window │ └── index.ts ├── lib ├── analytics │ └── ga.ts └── matter │ └── render.js ├── local.d.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── 404 │ └── index.tsx ├── _app.tsx ├── _document.tsx ├── coming-soon │ └── index.tsx └── index.tsx ├── postcss.config.js ├── public ├── android-icon-144x144.png ├── android-icon-192x192.png ├── android-icon-36x36.png ├── android-icon-48x48.png ├── android-icon-72x72.png ├── android-icon-96x96.png ├── apple-icon-114x114.png ├── apple-icon-120x120.png ├── apple-icon-144x144.png ├── apple-icon-152x152.png ├── apple-icon-180x180.png ├── apple-icon-57x57.png ├── apple-icon-60x60.png ├── apple-icon-72x72.png ├── apple-icon-76x76.png ├── apple-icon-precomposed.png ├── apple-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon-dark.svg ├── favicon-light.svg ├── favicon.ico ├── fonts │ ├── Graphik-Regular.woff │ ├── Graphik-Regular.woff2 │ ├── ITCGaramondStd-LtCond.woff │ ├── ITCGaramondStd-LtCond.woff2 │ ├── ITCGaramondStd-LtCondIta.woff │ └── ITCGaramondStd-LtCondIta.woff2 ├── images │ ├── avatar.png │ ├── cookies │ │ └── cookie.png │ ├── lab │ │ └── 01.png │ ├── method │ │ └── 01.png │ ├── pixel.png │ ├── principles │ │ ├── 01.png │ │ ├── 02.png │ │ └── 03.png │ ├── services │ │ ├── 01.png │ │ ├── 02.png │ │ ├── 03.png │ │ └── 04.png │ └── studio │ │ ├── 01.png │ │ ├── 02-xs.png │ │ └── 02.png ├── manifest.json ├── ms-icon-144x144.png ├── ms-icon-150x150.png ├── ms-icon-310x310.png ├── ms-icon-70x70.png └── vercel.svg ├── services ├── articles │ └── index.ts ├── categories │ └── index.ts └── contact │ └── index.ts ├── store ├── application │ └── slice.ts ├── home │ └── slice.ts ├── hooks.ts └── index.ts ├── styles ├── fonts.css └── globals.css ├── svgs ├── check.svg ├── close.svg ├── contact-isotype-2.svg ├── contact-text.svg ├── cursor.svg ├── email.svg ├── hamburger.svg ├── hour-glass.json ├── instagram.svg ├── isotype.svg ├── link.svg ├── linkedin.svg ├── loading.svg ├── logo.svg ├── thank-you.svg └── twitter.svg ├── tailwind.config.js ├── tsconfig.eslint.json ├── tsconfig.json ├── types └── index.d.ts └── yarn.lock /.browserlistrc: -------------------------------------------------------------------------------- 1 | # defaults means > 0.5%, last 2 versions, Firefox ESR, not dead 2 | defaults 3 | not IE 11 4 | maintained node versions 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | indent_style = tab 15 | indent_size = 4 16 | trim_trailing_whitespace = false 17 | 18 | [Dockerfile] 19 | indent_style = tab 20 | indent_size = 4 21 | 22 | [Makefile] 23 | indent_style = tab 24 | indent_size = 4 -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_GOOGLE_ANALYTICS="" 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /.next 2 | /node_modules 3 | /cypress 4 | /public 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # tailwind styles 19 | /public/styles/ 20 | 21 | # misc 22 | .DS_Store 23 | *.pem 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # local env files 31 | .env.local 32 | .env.development.local 33 | .env.test.local 34 | .env.production.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # Test with Cypress 40 | /cypress/screenshots 41 | /cypress/videos 42 | 43 | # Yarn 44 | .yarn/* 45 | !.yarn/patches 46 | !.yarn/plugins 47 | !.yarn/releases 48 | !.yarn/sdks 49 | !.yarn/versions 50 | 51 | # typescript 52 | tsconfig.tsbuildinfo 53 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn lint 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.13.2 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "tabWidth": 2, 4 | "printWidth": 200, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.storybook/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"] 3 | } 4 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | stories: [ 5 | '../components/**/*.stories.@(js|jsx|ts|tsx)', 6 | '../containers/**/*.stories.@(js|jsx|ts|tsx)', 7 | ], 8 | addons: [ 9 | '@storybook/addon-links', 10 | '@storybook/addon-essentials', 11 | { 12 | /** 13 | * Fix Storybook issue with PostCSS@8 14 | * @see https://github.com/storybookjs/storybook/issues/12668#issuecomment-773958085 15 | */ 16 | name: '@storybook/addon-postcss', 17 | options: { 18 | postcssLoaderOptions: { 19 | implementation: require('postcss'), 20 | }, 21 | }, 22 | }, 23 | ], 24 | core: { 25 | builder: 'webpack5', 26 | }, 27 | /* nextjs -> no need to import React and can use alias modules */ 28 | webpackFinal: async (config) => { 29 | // ************************* 30 | // RESOLVE node_modules 31 | // If you want to add a directory to search in that takes precedence over node_modules/: 32 | // https://webpack.js.org/configuration/resolve/#resolvemodules 33 | config.resolve.modules = [path.resolve(__dirname, '..'), 'node_modules']; 34 | 35 | /** 36 | * Add support for alias-imports 37 | * @see https://github.com/storybookjs/storybook/issues/11989#issuecomment-715524391 38 | */ 39 | config.resolve.alias = { 40 | ...config.resolve?.alias, 41 | '@': [path.resolve(__dirname, '../')], 42 | }; 43 | 44 | /** 45 | * Fixes font import with / 46 | * @see https://github.com/storybookjs/storybook/issues/12844#issuecomment-867544160 47 | */ 48 | config.resolve.roots = [ 49 | path.resolve(__dirname, '../public'), 50 | 'node_modules', 51 | ]; 52 | // ************************* 53 | // SVGS 54 | // Remove how storybook is handling the svgs. They are using file-loader 55 | // https://github.com/JetBrains/svg-sprite-loader/issues/267 56 | config.module.rules = config.module.rules.map((rule) => { 57 | if (rule.test && rule.test.toString().includes('svg')) { 58 | const test = rule.test.toString().replace('svg|', '').replace(/\//g, ''); 59 | return { ...rule, test: new RegExp(test) }; 60 | } else { 61 | return rule; 62 | } 63 | }); 64 | 65 | config.module.rules.push({ 66 | test: /\.mjs$/, 67 | include: /node_modules/, 68 | type: 'javascript/auto', 69 | }); 70 | 71 | // Add custom loaders for svgs 72 | config.module.rules.push({ 73 | test: /\.svg$/, 74 | use: [ 75 | { 76 | loader: 'svg-sprite-loader', 77 | options: { 78 | extract: false, 79 | }, 80 | }, 81 | { 82 | loader: 'svgo-loader', 83 | options: { 84 | plugins: [ 85 | { removeTitle: true }, 86 | { convertColors: { shorthex: false } }, 87 | { convertPathData: false }, 88 | ], 89 | }, 90 | }, 91 | ], 92 | }); 93 | 94 | return config; 95 | }, 96 | }; 97 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { OverlayProvider } from '@react-aria/overlays'; 4 | 5 | import '../styles/fonts.css'; 6 | 7 | export const parameters = { 8 | actions: { argTypesRegex: '^on[A-Z].*' }, 9 | options: { 10 | storySort: { 11 | order: [ 12 | 'Intro', 13 | 'Docs', 14 | ['Install', 'Deploy', 'Authentication', 'Fetching', 'Media', 'Tests'], 15 | 'Components', 16 | ], 17 | }, 18 | }, 19 | previewTabs: { 'storybook/docs/panel': { index: -1 } }, 20 | }; 21 | 22 | export const decorators = [ 23 | (Story) => { 24 | return ( 25 | 26 |
{Story()}
27 |
28 | ); 29 | }, 30 | ]; 31 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.1.1.cjs 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Install dependencies only when needed 2 | FROM node:16-alpine AS deps 3 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 4 | RUN apk add --no-cache libc6-compat 5 | WORKDIR /app 6 | COPY package.json yarn.lock ./ 7 | RUN yarn install --immutable 8 | 9 | # If using npm with a `package-lock.json` comment out above and use below instead 10 | # COPY package.json package-lock.json ./ 11 | # RUN npm ci 12 | 13 | # Rebuild the source code only when needed 14 | FROM node:16-alpine AS builder 15 | WORKDIR /app 16 | COPY --from=deps /app/node_modules ./node_modules 17 | COPY . . 18 | 19 | # Next.js collects completely anonymous telemetry data about general usage. 20 | # Learn more here: https://nextjs.org/telemetry 21 | # Uncomment the following line in case you want to disable telemetry during the build. 22 | ENV NEXT_TELEMETRY_DISABLED 1 23 | 24 | RUN yarn build 25 | 26 | # Production image, copy all the files and run next 27 | FROM node:16-alpine AS runner 28 | WORKDIR /app 29 | 30 | ENV NODE_ENV production 31 | # Uncomment the following line in case you want to disable telemetry during runtime. 32 | # ENV NEXT_TELEMETRY_DISABLED 1 33 | 34 | RUN addgroup --system --gid 1001 nodejs 35 | RUN adduser --system --uid 1001 nextjs 36 | 37 | # You only need to copy next.config.js if you are NOT using the default configuration 38 | COPY --from=builder /app/next.config.js ./ 39 | COPY --from=builder /app/public ./public 40 | COPY --from=builder /app/package.json ./package.json 41 | 42 | # Automatically leverage output traces to reduce image size 43 | # https://nextjs.org/docs/advanced-features/output-file-tracing 44 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 45 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 46 | 47 | USER nextjs 48 | 49 | EXPOSE 3000 50 | 51 | ENV PORT 3000 52 | 53 | CMD ["node", "server.js"] 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Front end scaffold 2 | 3 | This is a project template which could be used to the creation of new projects. Some of the features included are: 4 | 5 | - Based on [Next.js](https://nextjs.org/) 6 | - [Tailwind](https://tailwindcss.com/) as CSS Framework 7 | - Reusable components such as forms, modals, icons, and other most use components 8 | - [Redux](https://redux.js.org/) and [Redux-Toolkit](https://redux-toolkit.js.org/) 9 | - [Typescript](https://www.typescriptlang.org/) already configured 10 | - [Cypress](https://www.cypress.io/) as testing client 11 | - git workflow and hooks 12 | - editorconfig and code style based on [Airbnb](https://github.com/airbnb/javascript) 13 | - [Storybook](https://storybook.js.org/) also available and configured 14 | 15 | ## Getting Started 16 | 17 | Check our [documentation](https://minima-docs.vercel.app/?path=/story/intro--page). 18 | 19 | ### Quick start 20 | 21 | First, run the development server: 22 | 23 | ```bash 24 | yarn dev 25 | ``` 26 | 27 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 28 | 29 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 30 | 31 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 32 | 33 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 34 | 35 | ## Learn More 36 | 37 | To learn more about this project, take a look at the following resources: 38 | 39 | - [Blogin internal post](https://vizzuality.blogin.co/posts/a-scaffold-to-rule-all-of-them-135768) - communication to Vizzuality 40 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 41 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 42 | - [Standardization guidelines](https://vizzuality.github.io/frontismos/docs/guidelines/standardization/) - a set of agreements and conventions. 43 | 44 | ## Deploy on Vercel 45 | 46 | First, we recommend to read the guideline about [how to use Vercel](https://vizzuality.github.io/frontismos/docs/guidelines/vercel/). 47 | 48 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 49 | 50 | Check out the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 51 | 52 | ## Contribution rules 53 | 54 | Please, **create a PR** for any improvement or feature you want to add. Try to not commit directly anything on the `main` branch. 55 | -------------------------------------------------------------------------------- /components/button/component.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Story } from '@storybook/react/types-6-0'; 2 | 3 | import Icon from 'components/icon'; 4 | 5 | import CLOSE_SVG from 'svgs/close.svg'; 6 | 7 | import Button from './component'; 8 | import { ButtonProps } from './types'; 9 | 10 | export default { 11 | title: 'Components/Button', 12 | component: Button, 13 | argTypes: { 14 | size: { 15 | control: { 16 | type: 'select', 17 | options: ['s', 'base', 'icon'], 18 | }, 19 | }, 20 | theme: { 21 | control: { 22 | type: 'select', 23 | options: ['primary', 'primary-alt', 'clean'], 24 | }, 25 | }, 26 | }, 27 | }; 28 | 29 | const Template: Story = ({ children, ...args }: ButtonProps) => ( 30 | 31 | ); 32 | 33 | export const Default = Template.bind({}); 34 | Default.args = { 35 | children: 'Button', 36 | disabled: false, 37 | }; 38 | 39 | export const WithIcon = Template.bind({}); 40 | WithIcon.args = { 41 | children: , 42 | disabled: false, 43 | size: 'icon-base', 44 | }; 45 | -------------------------------------------------------------------------------- /components/button/component.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | 3 | import cx from 'classnames'; 4 | 5 | import Link from 'next/link'; 6 | 7 | import { THEME, SIZE } from './constants'; 8 | import type { ButtonProps, AnchorProps, Overload } from './types'; 9 | 10 | // Guard to check if href exists in props 11 | const hasHref = (props: ButtonProps | AnchorProps): props is AnchorProps => 'href' in props; 12 | 13 | function buildClassName({ 14 | className, disabled, size, theme, 15 | }) { 16 | return cx({ 17 | 'flex items-center justify-center rounded-3xl': true, 18 | [THEME[theme]]: true, 19 | [SIZE[size]]: true, 20 | [className]: !!className, 21 | 'opacity-50 pointer-events-none': disabled, 22 | }); 23 | } 24 | 25 | export const LinkAnchor: FC = ({ 26 | children, 27 | theme = 'primary', 28 | size = 'base', 29 | className, 30 | disabled, 31 | href, 32 | anchorLinkProps, 33 | ...restProps 34 | }: AnchorProps) => ( 35 | 36 | 45 | {children} 46 | 47 | 48 | ); 49 | 50 | export const Anchor: FC = ({ 51 | children, 52 | theme = 'primary', 53 | size = 'base', 54 | className, 55 | disabled, 56 | href, 57 | ...restProps 58 | }: AnchorProps) => { 59 | // Anchor element doesn't support disabled attribute 60 | // https://www.w3.org/TR/2014/REC-html5-20141028/disabled-elements.html 61 | if (disabled) { 62 | return {children}; 63 | } 64 | return ( 65 | 75 | {children} 76 | 77 | ); 78 | }; 79 | 80 | export const Button: FC = ({ 81 | children, 82 | theme = 'primary', 83 | size = 'base', 84 | className, 85 | disabled, 86 | ...restProps 87 | }: ButtonProps) => ( 88 | 101 | ); 102 | 103 | export const LinkButton: Overload = (props: ButtonProps | AnchorProps) => { 104 | // We consider a link button when href attribute exits 105 | if (hasHref(props)) { 106 | const { href } = props; 107 | if (href.startsWith('http')) { 108 | return ; 109 | } 110 | return ; 111 | } 112 | return