From e850435c2d5314479e51d6b98e17d93ae1c5a200 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:13:11 +0700 Subject: [PATCH 01/14] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20create=20but?= =?UTF-8?q?ton=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Button/Button.theme.ts | 137 ++++++++++++++++ .../src/components/shared/Button/Button.tsx | 150 ++++++++++++++++++ .../src/components/shared/Button/index.ts | 2 + 3 files changed, 289 insertions(+) create mode 100644 packages/frontend/src/components/shared/Button/Button.theme.ts create mode 100644 packages/frontend/src/components/shared/Button/Button.tsx create mode 100644 packages/frontend/src/components/shared/Button/index.ts diff --git a/packages/frontend/src/components/shared/Button/Button.theme.ts b/packages/frontend/src/components/shared/Button/Button.theme.ts new file mode 100644 index 00000000..74613090 --- /dev/null +++ b/packages/frontend/src/components/shared/Button/Button.theme.ts @@ -0,0 +1,137 @@ +import { tv } from 'tailwind-variants'; +import type { VariantProps } from 'tailwind-variants'; + +/** + * Defines the theme for a button component. + */ +export const buttonTheme = tv( + { + base: [ + 'inline-flex', + 'items-center', + 'justify-center', + 'font-medium', + 'whitespace-nowrap', + 'focus-ring', + 'disabled:cursor-not-allowed', + ], + variants: { + size: { + lg: ['gap-3', 'py-4', 'px-6', 'rounded-lg', 'text-lg'], + md: ['gap-2', 'py-3', 'px-4', 'rounded-lg', 'text-base'], + sm: ['gap-1', 'py-2', 'px-2', 'rounded-md', 'text-xs'], + xs: ['gap-1', 'py-1', 'px-2', 'rounded-md', 'text-xs'], + }, + fullWidth: { + true: 'w-full', + }, + shape: { + default: '', + rounded: 'rounded-full', + }, + variant: { + primary: [ + 'text-elements-on-primary', + 'border', + 'border-primary-700', + 'bg-controls-primary', + 'shadow-button', + 'hover:bg-controls-primary-hovered', + 'focus-visible:bg-controls-primary-hovered', + 'disabled:text-elements-on-disabled', + 'disabled:bg-controls-disabled', + 'disabled:border-transparent', + 'disabled:shadow-none', + ], + secondary: [ + 'text-components-buttons-secondary-foreground', + 'border', + 'border-primary-500', + 'bg-components-buttons-secondary-background', + 'hover:text-components-buttons-secondary-foreground-hover', + 'hover:bg-components-buttons-secondary-background-hover', + 'focus-visible:text-components-buttons-secondary-foreground-focus', + 'focus-visible:bg-components-buttons-secondary-background-focus', + 'disabled:text-components-buttons-secondary-foreground-disabled', + 'disabled:bg-components-buttons-secondary-background-disabled ', + 'disabled:border-transparent', + ], + tertiary: [ + 'text-components-buttons-tertiary-background', + 'border', + 'border-components-buttons-tertiary-background', + 'bg-transparent', + 'hover:text-components-buttons-tertiary-hover', + 'hover:border-components-buttons-tertiary-hover', + 'focus-visible:text-components-buttons-tertiary-focus', + 'focus-visible:border-components-buttons-tertiary-focus', + 'disabled:text-components-buttons-tertiary-disabled', + 'disabled:border-components-buttons-tertiary-disabled', + ], + 'text-only': [ + 'text-components-buttons-text-only-background', + 'border', + 'border-transparent', + 'bg-transparent', + 'hover:text-components-buttons-text-only-foreground-hover', + 'hover:bg-components-buttons-text-only-background-hover', + 'focus-visible:text-components-buttons-text-only-foreground-focus', + 'focus-visible:bg-components-buttons-text-only-background-focus', + 'disabled:text-components-buttons-tertiary-disabled', + 'disabled:bg-transparent', + ], + danger: [ + 'text-components-button-icon-alert-foreground', + 'border', + 'border-transparent', + 'bg-components-buttons-alert-background', + 'hover:text-components-buttons-alert-foreground-hover', + 'hover:bg-components-buttons-alert-background-hover', + 'focus-visible:text-components-buttons-alert-foreground-focus', + 'focus-visible:bg-components-buttons-alert-background-focus', + 'disabled:text-components-button-icon-alert-foreground-disabled', + 'disabled:bg-components-button-icon-alert-background-disabled', + ], + 'icon-only': [ + 'p-0 flex items-center justify-center', + 'text-components-button-icon-text-only-foreground', + 'border', + 'border-transparent', + 'bg-transparent', + 'hover:text-components-button-icon-text-only-foreground-hover', + 'hover:bg-components-button-icon-text-only-background-hover', + 'focus-visible:text-components-button-icon-low-emphasis-foreground-focus', + 'focus-visible:bg-components-button-icon-low-emphasis-background-focus', + 'disabled:text-components-button-icon-low-emphasis-outlined-foreground-disabled', + 'disabled:bg-transparent', + ], + unstyled: [], + }, + }, + compoundVariants: [ + { + size: 'md', + variant: 'icon-only', + class: ['h-11', 'w-11', 'rounded-lg'], + }, + { + size: 'sm', + variant: 'icon-only', + class: ['h-8', 'w-8', 'rounded-md'], + }, + ], + defaultVariants: { + size: 'md', + variant: 'primary', + fullWidth: false, + }, + }, + { + responsiveVariants: true, + }, +); + +/** + * Represents the type of a button theme, which is derived from the `buttonTheme` variant props. + */ +export type ButtonTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Button/Button.tsx b/packages/frontend/src/components/shared/Button/Button.tsx new file mode 100644 index 00000000..917642a8 --- /dev/null +++ b/packages/frontend/src/components/shared/Button/Button.tsx @@ -0,0 +1,150 @@ +import React, { useCallback } from 'react'; +import type { ComponentPropsWithoutRef, ReactNode } from 'react'; + +import { buttonTheme } from './Button.theme'; +import type { ButtonTheme } from './Button.theme'; +import { Link } from 'react-router-dom'; + +/** + * Represents the properties of a base button component. + */ +interface ButtonBaseProps { + /** + * The optional left icon element for a component. + * @type {ReactNode} + */ + leftIcon?: ReactNode; + /** + * The optional right icon element to display. + * @type {ReactNode} + */ + rightIcon?: ReactNode; +} + +/** + * Interface for the props of a button link component. + */ +export interface ButtonLinkProps + extends Omit, 'color'> { + /** + * Specifies the optional property `as` with a value of `'a'`. + * @type {'a'} + */ + as?: 'a'; + /** + * Indicates whether the item is external or not. + * @type {boolean} + */ + external?: boolean; + /** + * The URL of a web page or resource. + * @type {string} + */ + href: string; +} + +export interface ButtonProps + extends Omit, 'color'> { + /** + * Specifies the optional property `as` with a value of `'button'`. + * @type {'button'} + */ + as?: 'button'; +} + +/** + * Interface representing the props for a button component. + * Extends the ComponentPropsWithoutRef<'button'> and ButtonTheme interfaces. + */ +export type ButtonOrLinkProps = (ButtonLinkProps | ButtonProps) & + ButtonBaseProps & + ButtonTheme; + +/** + * A custom button component that can be used in React applications. + */ +const Button = ({ + children, + className, + leftIcon, + rightIcon, + fullWidth, + shape, + variant, + ...props +}: ButtonOrLinkProps) => { + // Conditionally render between , or ; + } + }, + [], + ); + + /** + * Extracts specific style properties from the given props object and returns them as a new object. + */ + const styleProps = (({ + variant = 'primary', + size = 'md', + fullWidth = false, + shape = 'rounded', + as, + }) => ({ + variant, + size, + fullWidth, + shape, + as, + }))({ ...props, fullWidth, shape, variant }); + + /** + * Validates that a button component has either children or an aria-label prop. + */ + if (typeof children === 'undefined' && !props['aria-label']) { + throw new Error( + 'Button components must have either children or an aria-label prop', + ); + } + + return ( + + {leftIcon} + {children} + {rightIcon} + + ); +}; + +Button.displayName = 'Button'; + +export { Button }; diff --git a/packages/frontend/src/components/shared/Button/index.ts b/packages/frontend/src/components/shared/Button/index.ts new file mode 100644 index 00000000..1331278b --- /dev/null +++ b/packages/frontend/src/components/shared/Button/index.ts @@ -0,0 +1,2 @@ +export * from './Button'; +export * from './Button.theme'; From 8021ff7ff9ff669c25118a56a767f99693819a69 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:13:36 +0700 Subject: [PATCH 02/14] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20vscode=20se?= =?UTF-8?q?ttings=20on=20frontend=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/.vscode/settings.json | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 packages/frontend/.vscode/settings.json diff --git a/packages/frontend/.vscode/settings.json b/packages/frontend/.vscode/settings.json new file mode 100644 index 00000000..3566c26b --- /dev/null +++ b/packages/frontend/.vscode/settings.json @@ -0,0 +1,39 @@ +{ + // eslint extension options + "eslint.enable": true, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "css.customData": [".vscode/tailwind.json"], + // prettier extension setting + "editor.formatOnSave": true, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.rulers": [80], + "editor.codeActionsOnSave": [ + "source.addMissingImports", + "source.fixAll", + "source.organizeImports" + ], + // Show in vscode "Problems" tab when there are errors + "typescript.tsserver.experimental.enableProjectDiagnostics": true, + // Use absolute import for typescript files + "typescript.preferences.importModuleSpecifier": "non-relative", + // IntelliSense for taiwind variants + "tailwindCSS.experimental.classRegex": [ + ["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] + ] +} From b51d050ee600aa7521abf03d523a5ce7bd98855a Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:13:56 +0700 Subject: [PATCH 03/14] =?UTF-8?q?=F0=9F=93=9D=20docs:=20add=20button=20to?= =?UTF-8?q?=20the=20example=20comopnent=20pag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 9a7a8519..30234712 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,3 +1,4 @@ +import { Button } from 'components/shared/Button'; import React from 'react'; const Page = () => { @@ -15,20 +16,14 @@ const Page = () => { {/* Insert Components here */}
-

Component A

- -
-
-
-
-
-
- -
-
-
-
-
+

Button

+
+ + + +
From 56adfc48061b16f6d3d2fd3ff5177d28f49702c7 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:14:23 +0700 Subject: [PATCH 04/14] =?UTF-8?q?=F0=9F=90=9B=20fix:=20typescript=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../projects/project/settings/DisplayEnvironmentVariables.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx b/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx index 75ee3dad..49e14c5e 100644 --- a/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx +++ b/packages/frontend/src/components/projects/project/settings/DisplayEnvironmentVariables.tsx @@ -1,9 +1,8 @@ import React, { useState } from 'react'; import { Card, Collapse, Typography } from '@material-tailwind/react'; -import { Environment, EnvironmentVariable } from 'gql-client/dist/src/types'; - import EditEnvironmentVariableRow from './EditEnvironmentVariableRow'; +import { Environment, EnvironmentVariable } from 'gql-client'; interface DisplayEnvironmentVariablesProps { environment: Environment; From 17b5ff9a7484a3f1b2b0782d3a67c8e9202c4efe Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:14:49 +0700 Subject: [PATCH 05/14] =?UTF-8?q?=F0=9F=8E=A8=20style:=20add=20colors,=20f?= =?UTF-8?q?ont=20family,=20and=20shadow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/tailwind.config.js | 139 ++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 70266ee3..a1b6f778 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -8,7 +8,144 @@ export default withMT({ '../../node_modules/@material-tailwind/react/theme/components/**/*.{js,ts,jsx,tsx}', ], theme: { - extend: {}, + fontFamily: { + sans: ['Inter', 'sans-serif'], + }, + extend: { + colors: { + base: { + bg: '#ffffff', + 'bg-alternate': '#f8fafc', + 'bg-emphasized': '#f1f5f9', + 'bg-emphasized-danger': '#fff1f2', + 'bg-emphasized-info': '#ecf6fe', + 'bg-emphasized-success': '#ecfdf5', + 'bg-emphasized-warning': '#fff7ed', + }, + border: { + active: '#0f86f5', + danger: '#e11d48', + 'danger-light': '#ffe4e6', + 'info-light': '#ddeefd', + interactive: '#082f561a', + 'interactive-hovered': '#082f5624', + separator: '#082f560f', + 'success-light': '#d1fae5', + 'warning-light': '#ffedd5', + }, + controls: { + danger: '#e11d48', + 'danger-hovered': '#be123c', + disabled: '#e2e9f0', + 'disabled-active': '#74bafb', + elevated: '#ffffff', + inset: '#e2e9f0', + 'inset-hovered': '#cbd6e1', + primary: '#0f86f5', + 'primary-hovered': '#0977dc', + secondary: '#ddeefd', + 'secondary-hovered': '#cfe6fc', + tertiary: '#ffffff', + 'tertiary-hovered': '#f8fafc', + }, + elements: { + danger: '#e11d48', + disabled: '#94a7b8', + 'high-em': '#0b1d2e', + info: '#0f86f5', + link: '#0f86f5', + 'link-hovered': '#0977dc', + 'low-em': '#60788f', + 'mid-em': '#475969', + 'on-danger': '#ffffff', + 'on-disabled': '#60788f', + 'on-disabled-active': '#0a3a5c', + 'on-emphasized-danger': '#9f1239', + 'on-emphasized-info': '#0a3a5c', + 'on-emphasized-success': '#065f46', + 'on-emphasized-warning': '#9a3412', + 'on-high-contrast': '#ffffff', + 'on-primary': '#ffffff', + 'on-secondary': '#0977dc', + 'on-secondary-tinted': '#075185', + 'on-tertiary': '#1b2d3e', + success: '#059669', + warning: '#ea580c', + }, + surface: { + card: '#ffffff', + 'card-hovered': '#f8fafc', + floating: '#ffffff', + 'floating-hovered': '#f1f5f9', + 'high-contrast': '#0b1d2e', + }, + emerald: { + 50: '#ecfdf5', + 100: '#d1fae5', + 200: '#a9f1d0', + 300: '#6ee7b7', + 400: '#34d399', + 500: '#10b981', + 600: '#059669', + 700: '#047857', + 800: '#065f46', + 900: '#064e3b', + }, + gray: { + 0: '#ffffff', + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e9f0', + 300: '#cbd6e1', + 400: '#94a7b8', + 500: '#60788f', + 600: '#475969', + 700: '#334555', + 800: '#1b2d3e', + 900: '#0b1d2e', + }, + orange: { + 50: '#fff7ed', + 100: '#ffedd5', + 200: '#fed7aa', + 300: '#fdba74', + 400: '#fb923c', + 500: '#f97316', + 600: '#ea580c', + 700: '#c2410c', + 800: '#9a3412', + 900: '#7c2d12', + }, + rose: { + 50: '#fff1f2', + 100: '#ffe4e6', + 200: '#fecdd3', + 300: '#fda4af', + 400: '#fb7185', + 500: '#f43f5e', + 600: '#e11d48', + 700: '#be123c', + 800: '#9f1239', + 900: '#881337', + }, + snowball: { + 50: '#ecf6fe', + 100: '#e1f1fe', + 200: '#ddeefd', + 300: '#cfe6fc', + 400: '#74bafb', + 500: '#47a4fa', + 600: '#0f86f5', + 700: '#0977dc', + 800: '#075185', + 900: '#0a3a5c', + }, + }, + boxShadow: { + button: + 'inset 0px 0px 4px rgba(255, 255, 255, 0.25), inset 0px -2px 0px rgba(0, 0, 0, 0.04)', + }, + }, }, plugins: [], }); From 1c848c5a892f50979a21c1a7aa5c9927f7e13873 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:15:32 +0700 Subject: [PATCH 06/14] =?UTF-8?q?=F0=9F=8E=A8=20style:=20add=20inter=20fon?= =?UTF-8?q?t=20family?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx index 6317b830..da29f575 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -7,6 +7,7 @@ import { GQLClient } from 'gql-client'; import { ThemeProvider } from '@material-tailwind/react'; import './index.css'; +import '@fontsource/inter'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { GQLClientProvider } from './context/GQLClientContext'; From 6fb94aeb115b33a810b523be3ba52bebe4932620 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:15:56 +0700 Subject: [PATCH 07/14] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20absolute=20?= =?UTF-8?q?path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index 9d379a3c..5452a6dc 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "baseUrl": "src" }, "include": ["src"] } From 92f7d07f3fd67f35d3e1b82092bf6d4fbafb9f1f Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:16:25 +0700 Subject: [PATCH 08/14] =?UTF-8?q?=F0=9F=94=A7=20chore:=20install=20`tailwi?= =?UTF-8?q?nd-variants`=20and=20`@fontsource/inter`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/package.json | 2 ++ yarn.lock | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 98f62537..a2a77ba1 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fontsource/inter": "^5.0.16", "@material-tailwind/react": "^2.1.7", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", @@ -29,6 +30,7 @@ "react-router-dom": "^6.20.1", "react-scripts": "5.0.1", "react-timer-hook": "^3.0.7", + "tailwind-variants": "^0.2.0", "typescript": "^4.9.5", "usehooks-ts": "^2.10.0", "vertical-stepper-nav": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index 79c848fa..e4713492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1280,7 +1280,7 @@ resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.10.4", "@babel/runtime@^7.3.1": +"@babel/runtime@^7.10.4", "@babel/runtime@^7.23.7", "@babel/runtime@^7.3.1": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== @@ -2068,6 +2068,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== +"@fontsource/inter@^5.0.16": + version "5.0.16" + resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.0.16.tgz#b858508cdb56dcbbf3166903122851e2fbd16b50" + integrity sha512-qF0aH5UiZvCmneX5orJbVRoc2VTyLTV3X/7laMp03Qt28L+B9tFlZODOGUL64wDWc69YVdi1LeJB0cIgd51lvw== + "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -15026,6 +15031,20 @@ tailwind-merge@1.8.1: resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.8.1.tgz#0e56c8afbab2491f72e06381043ffec8b720ba04" integrity sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww== +tailwind-merge@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.2.1.tgz#3f10f296a2dba1d88769de8244fafd95c3324aeb" + integrity sha512-o+2GTLkthfa5YUt4JxPfzMIpQzZ3adD1vLVkvKE1Twl9UAhGsEbIZhHHZVRttyW177S8PDJI3bTQNaebyofK3Q== + dependencies: + "@babel/runtime" "^7.23.7" + +tailwind-variants@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/tailwind-variants/-/tailwind-variants-0.2.0.tgz#5c3a24afb6e8bb23f33aa3aa3550add8848de8a3" + integrity sha512-EuW5Sic7c0tzp+p5rJwAgb7398Jb0hi4zkyCstOoZPW0DWwr+EWkNtnZYEo5CjgE1tazHUzyt4oIhss64UXRVA== + dependencies: + tailwind-merge "^2.2.0" + tailwindcss@^3.0.2, tailwindcss@^3.3.6: version "3.3.6" resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz" From 862862a9c5b4480257e66a1193b5aeb89e0dbd6c Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 21:15:29 +0700 Subject: [PATCH 09/14] =?UTF-8?q?=F0=9F=8E=A8=20style:=20update=20button?= =?UTF-8?q?=20and=20button=20icon=20only=20theme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Button/Button.theme.ts | 129 +++++++++++------- .../src/components/shared/Button/Button.tsx | 24 +++- 2 files changed, 98 insertions(+), 55 deletions(-) diff --git a/packages/frontend/src/components/shared/Button/Button.theme.ts b/packages/frontend/src/components/shared/Button/Button.theme.ts index 74613090..897fdfbc 100644 --- a/packages/frontend/src/components/shared/Button/Button.theme.ts +++ b/packages/frontend/src/components/shared/Button/Button.theme.ts @@ -7,20 +7,20 @@ import type { VariantProps } from 'tailwind-variants'; export const buttonTheme = tv( { base: [ + 'h-fit', 'inline-flex', 'items-center', 'justify-center', - 'font-medium', 'whitespace-nowrap', 'focus-ring', 'disabled:cursor-not-allowed', ], variants: { size: { - lg: ['gap-3', 'py-4', 'px-6', 'rounded-lg', 'text-lg'], - md: ['gap-2', 'py-3', 'px-4', 'rounded-lg', 'text-base'], - sm: ['gap-1', 'py-2', 'px-2', 'rounded-md', 'text-xs'], - xs: ['gap-1', 'py-1', 'px-2', 'rounded-md', 'text-xs'], + lg: ['gap-2', 'py-3.5', 'px-5', 'text-base', 'tracking-[-0.011em]'], + md: ['gap-2', 'py-3.25', 'px-5', 'text-sm', 'tracking-[-0.006em]'], + sm: ['gap-1', 'py-2', 'px-3', 'text-xs'], + xs: ['gap-1', 'py-1', 'px-2', 'text-xs'], }, fullWidth: { true: 'w-full', @@ -29,11 +29,14 @@ export const buttonTheme = tv( default: '', rounded: 'rounded-full', }, + iconOnly: { + true: '', + }, variant: { primary: [ 'text-elements-on-primary', 'border', - 'border-primary-700', + 'border-transparent', 'bg-controls-primary', 'shadow-button', 'hover:bg-controls-primary-hovered', @@ -44,86 +47,108 @@ export const buttonTheme = tv( 'disabled:shadow-none', ], secondary: [ - 'text-components-buttons-secondary-foreground', + 'text-elements-on-secondary', 'border', - 'border-primary-500', - 'bg-components-buttons-secondary-background', - 'hover:text-components-buttons-secondary-foreground-hover', - 'hover:bg-components-buttons-secondary-background-hover', - 'focus-visible:text-components-buttons-secondary-foreground-focus', - 'focus-visible:bg-components-buttons-secondary-background-focus', - 'disabled:text-components-buttons-secondary-foreground-disabled', - 'disabled:bg-components-buttons-secondary-background-disabled ', + 'border-transparent', + 'bg-controls-secondary', + 'hover:bg-controls-secondary-hovered', + 'focus-visible:bg-controls-secondary-hovered', + 'disabled:text-elements-on-disabled', + 'disabled:bg-controls-disabled', 'disabled:border-transparent', + 'disabled:shadow-none', ], tertiary: [ - 'text-components-buttons-tertiary-background', + 'text-elements-on-tertiary', 'border', - 'border-components-buttons-tertiary-background', + 'border-border-interactive/10', 'bg-transparent', - 'hover:text-components-buttons-tertiary-hover', - 'hover:border-components-buttons-tertiary-hover', - 'focus-visible:text-components-buttons-tertiary-focus', - 'focus-visible:border-components-buttons-tertiary-focus', - 'disabled:text-components-buttons-tertiary-disabled', - 'disabled:border-components-buttons-tertiary-disabled', + 'hover:bg-controls-tertiary-hovered', + 'hover:border-border-interactive-hovered', + 'hover:border-border-interactive-hovered/[0.14]', + 'focus-visible:bg-controls-tertiary-hovered', + 'focus-visible:border-border-interactive-hovered', + 'focus-visible:border-border-interactive-hovered/[0.14]', + 'disabled:text-elements-on-disabled', + 'disabled:bg-controls-disabled', + 'disabled:border-transparent', + 'disabled:shadow-none', ], - 'text-only': [ - 'text-components-buttons-text-only-background', + ghost: [ + 'text-elements-on-tertiary', 'border', 'border-transparent', 'bg-transparent', - 'hover:text-components-buttons-text-only-foreground-hover', - 'hover:bg-components-buttons-text-only-background-hover', - 'focus-visible:text-components-buttons-text-only-foreground-focus', - 'focus-visible:bg-components-buttons-text-only-background-focus', - 'disabled:text-components-buttons-tertiary-disabled', - 'disabled:bg-transparent', + 'hover:bg-controls-tertiary-hovered', + 'hover:border-border-interactive-hovered', + 'hover:border-border-interactive-hovered/[0.14]', + 'focus-visible:bg-controls-tertiary-hovered', + 'focus-visible:border-border-interactive-hovered', + 'focus-visible:border-border-interactive-hovered/[0.14]', + 'disabled:text-elements-on-disabled', + 'disabled:bg-controls-disabled', + 'disabled:border-transparent', + 'disabled:shadow-none', ], danger: [ - 'text-components-button-icon-alert-foreground', + 'text-elements-on-danger', 'border', 'border-transparent', - 'bg-components-buttons-alert-background', - 'hover:text-components-buttons-alert-foreground-hover', - 'hover:bg-components-buttons-alert-background-hover', - 'focus-visible:text-components-buttons-alert-foreground-focus', - 'focus-visible:bg-components-buttons-alert-background-focus', - 'disabled:text-components-button-icon-alert-foreground-disabled', - 'disabled:bg-components-button-icon-alert-background-disabled', + 'bg-border-danger', + 'hover:bg-controls-danger-hovered', + 'focus-visible:bg-controls-danger-hovered', + 'disabled:text-elements-on-disabled', + 'disabled:bg-controls-disabled', + 'disabled:border-transparent', + 'disabled:shadow-none', ], - 'icon-only': [ - 'p-0 flex items-center justify-center', - 'text-components-button-icon-text-only-foreground', + 'danger-ghost': [ + 'text-elements-danger', 'border', 'border-transparent', 'bg-transparent', - 'hover:text-components-button-icon-text-only-foreground-hover', - 'hover:bg-components-button-icon-text-only-background-hover', - 'focus-visible:text-components-button-icon-low-emphasis-foreground-focus', - 'focus-visible:bg-components-button-icon-low-emphasis-background-focus', - 'disabled:text-components-button-icon-low-emphasis-outlined-foreground-disabled', - 'disabled:bg-transparent', + 'hover:bg-controls-tertiary-hovered', + 'hover:border-border-interactive-hovered', + 'hover:border-border-interactive-hovered/[0.14]', + 'focus-visible:bg-controls-tertiary-hovered', + 'focus-visible:border-border-interactive-hovered', + 'focus-visible:border-border-interactive-hovered/[0.14]', + 'disabled:text-elements-on-disabled', + 'disabled:bg-controls-disabled', + 'disabled:border-transparent', + 'disabled:shadow-none', ], unstyled: [], }, }, compoundVariants: [ + { + size: 'lg', + iconOnly: true, + class: ['py-3.5', 'px-3.5'], + }, { size: 'md', - variant: 'icon-only', - class: ['h-11', 'w-11', 'rounded-lg'], + iconOnly: true, + class: ['py-3.25', 'px-3.25'], }, { size: 'sm', - variant: 'icon-only', - class: ['h-8', 'w-8', 'rounded-md'], + iconOnly: true, + class: ['py-2', 'px-2'], + }, + { + size: 'xs', + iconOnly: true, + class: ['py-1', 'px-1'], }, ], defaultVariants: { size: 'md', variant: 'primary', fullWidth: false, + iconOnly: false, + shape: 'rounded', }, }, { diff --git a/packages/frontend/src/components/shared/Button/Button.tsx b/packages/frontend/src/components/shared/Button/Button.tsx index 917642a8..d47c0162 100644 --- a/packages/frontend/src/components/shared/Button/Button.tsx +++ b/packages/frontend/src/components/shared/Button/Button.tsx @@ -4,6 +4,7 @@ import type { ComponentPropsWithoutRef, ReactNode } from 'react'; import { buttonTheme } from './Button.theme'; import type { ButtonTheme } from './Button.theme'; import { Link } from 'react-router-dom'; +import { cloneIcon } from 'utils/cloneIcon'; /** * Represents the properties of a base button component. @@ -69,6 +70,7 @@ const Button = ({ leftIcon, rightIcon, fullWidth, + iconOnly, shape, variant, ...props @@ -114,15 +116,17 @@ const Button = ({ variant = 'primary', size = 'md', fullWidth = false, + iconOnly = false, shape = 'rounded', as, }) => ({ variant, size, fullWidth, + iconOnly, shape, as, - }))({ ...props, fullWidth, shape, variant }); + }))({ ...props, fullWidth, iconOnly, shape, variant }); /** * Validates that a button component has either children or an aria-label prop. @@ -133,14 +137,28 @@ const Button = ({ ); } + const iconSize = useCallback(() => { + switch (styleProps.size) { + case 'lg': + return { width: 20, height: 20 }; + case 'sm': + case 'xs': + return { width: 16, height: 16 }; + case 'md': + default: { + return { width: 18, height: 18 }; + } + } + }, [styleProps.size])(); + return ( - {leftIcon} + {cloneIcon(leftIcon, { ...iconSize })} {children} - {rightIcon} + {cloneIcon(rightIcon, { ...iconSize })} ); }; From 2b78cab849bae68a5541534af656e488ce05489b Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 21:15:51 +0700 Subject: [PATCH 10/14] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20create=20cus?= =?UTF-8?q?tom=20icon=20and=20plus=20icon=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/CustomIcon/CustomIcon.tsx | 30 +++++++++++++++++++ .../components/shared/CustomIcon/PlusIcon.tsx | 21 +++++++++++++ .../components/shared/CustomIcon/README.md | 24 +++++++++++++++ .../src/components/shared/CustomIcon/index.ts | 2 ++ 4 files changed, 77 insertions(+) create mode 100644 packages/frontend/src/components/shared/CustomIcon/CustomIcon.tsx create mode 100644 packages/frontend/src/components/shared/CustomIcon/PlusIcon.tsx create mode 100644 packages/frontend/src/components/shared/CustomIcon/README.md create mode 100644 packages/frontend/src/components/shared/CustomIcon/index.ts diff --git a/packages/frontend/src/components/shared/CustomIcon/CustomIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/CustomIcon.tsx new file mode 100644 index 00000000..6b32bb73 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/CustomIcon.tsx @@ -0,0 +1,30 @@ +import React, { ComponentPropsWithoutRef } from 'react'; + +export interface CustomIconProps extends ComponentPropsWithoutRef<'svg'> { + size?: number | string; // width and height will both be set as the same value + name?: string; +} + +export const CustomIcon = ({ + children, + width = 24, + height = 24, + size, + viewBox = '0 0 24 24', + name, + ...rest +}: CustomIconProps) => { + return ( + + {children} + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/PlusIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/PlusIcon.tsx new file mode 100644 index 00000000..da4b1ee0 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/PlusIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const PlusIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/README.md b/packages/frontend/src/components/shared/CustomIcon/README.md new file mode 100644 index 00000000..d7e8b3d1 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/README.md @@ -0,0 +1,24 @@ +# 1. What icons are compatible with this component? + +- Viewbox "0 0 24 24": From where you're exporting from, please make sure the icon is using viewBox="0 0 24 24" before downloading/exporting. Not doing so will result in incorrect icon scaling + +# 2. How to add a new icon? + +**2.1 Sanitising the icon** + +1. Duplicate a current icon e.g. CrossIcon and rename it accordingly. +2. Rename the function inside the new file you duplicated too +3. Replace the markup with your SVG markup (make sure it complies with the above section's rule) +4. Depending on the svg you pasted... + A. If the `` has only 1 child, remove the `` parent entirely so you only have the path left + B. If your component has more than 1 paths, rename `` tag with the `` tag. Then, remove all attributes of this `` tag so that it's just `` +5. Usually, icons are single colored. If that's the case, replace all fill/stroke color with `currentColor`. E.g. . Leave the other attributes without removing them. +6. If your icon has more than one colour, then it's up to you to decide whether we want to use tailwind to help set the fill and stroke colors +7. Lastly, export your icon in `index.ts` by following what was done for CrossIcon +8. Make sure to provide a name to the `` component for accessibility sake +9. Done! + +**2.3 Use your newly imported icon** + +1. You can change simply use `` to quickly change both width and height with the same value (square). For custom viewBox, width and height, simply provide all three props. +2. Coloring the icon: Simply add a className with text color. E.g. `` diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts new file mode 100644 index 00000000..d50d942c --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -0,0 +1,2 @@ +export * from './PlusIcon'; +export * from './CustomIcon'; From 7e6b74208d46a5eda30b2c72665763965bc81815 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 21:16:17 +0700 Subject: [PATCH 11/14] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20create=20`cl?= =?UTF-8?q?oneIcon`=20util=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/utils/cloneIcon.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/frontend/src/utils/cloneIcon.tsx diff --git a/packages/frontend/src/utils/cloneIcon.tsx b/packages/frontend/src/utils/cloneIcon.tsx new file mode 100644 index 00000000..a9dc2745 --- /dev/null +++ b/packages/frontend/src/utils/cloneIcon.tsx @@ -0,0 +1,17 @@ +import { Children, cloneElement, isValidElement } from 'react'; +import type { Attributes, ReactElement, ReactNode } from 'react'; + +/** + * Clones an icon element with optional additional props. + * @param {ReactNode} icon - The icon element to clone. + * @param {Attributes & P} [props] - Additional props to apply to the cloned icon. + * @returns {ReactNode} - The cloned icon element with the applied props. + */ +export function cloneIcon

( + icon: ReactNode, + props?: Attributes & P, +): ReactNode { + return Children.map(icon, (child) => + isValidElement(child) ? cloneElement(child as ReactElement, props) : child, + ); +} From 943d427db12c95ab82f8e4d8d778ac92f0a048e7 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 21:16:39 +0700 Subject: [PATCH 12/14] =?UTF-8?q?=F0=9F=93=9D=20docs:=20add=20button=20ico?= =?UTF-8?q?n=20only=20on=20component=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 30234712..8c23f341 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,4 +1,5 @@ -import { Button } from 'components/shared/Button'; +import { Button, ButtonOrLinkProps } from 'components/shared/Button'; +import { PlusIcon } from 'components/shared/CustomIcon'; import React from 'react'; const Page = () => { @@ -17,13 +18,55 @@ const Page = () => { {/* Insert Components here */}

Button

-
- - - - +
+ {['primary', 'secondary', 'tertiary', 'danger'].map( + (variant, index) => ( +
+ {['lg', 'md', 'sm', 'xs', 'disabled'].map((size) => ( + + ))} +
+ ), + )} + {[ + 'primary', + 'secondary', + 'tertiary', + 'ghost', + 'danger', + 'danger-ghost', + ].map((variant, index) => ( +
+ {['lg', 'md', 'sm', 'xs', 'disabled'].map((size) => ( + + ))} +
+ ))}
From 2ac657c32e5e3ce5b94246bea54c5aaca3c64f35 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 21:16:55 +0700 Subject: [PATCH 13/14] =?UTF-8?q?=F0=9F=8E=A8=20style:=20add=20new=20spaci?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/tailwind.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 8c63962f..92ee77d0 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -145,6 +145,11 @@ export default withMT({ button: 'inset 0px 0px 4px rgba(255, 255, 255, 0.25), inset 0px -2px 0px rgba(0, 0, 0, 0.04)', }, + spacing: { + 2.5: '0.625rem', + 3.25: '0.8125rem', + 3.5: '0.875rem', + }, }, }, plugins: [], From 0f7c6c73c93e75d3d811e2c06222b4ec6ba9b3c9 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 22:59:31 +0700 Subject: [PATCH 14/14] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20make=20the?= =?UTF-8?q?=20button=20component=20to=20forward=20ref?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/shared/Button/Button.tsx | 194 +++++++++--------- 1 file changed, 102 insertions(+), 92 deletions(-) diff --git a/packages/frontend/src/components/shared/Button/Button.tsx b/packages/frontend/src/components/shared/Button/Button.tsx index d47c0162..af845e0c 100644 --- a/packages/frontend/src/components/shared/Button/Button.tsx +++ b/packages/frontend/src/components/shared/Button/Button.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { forwardRef, useCallback } from 'react'; import type { ComponentPropsWithoutRef, ReactNode } from 'react'; import { buttonTheme } from './Button.theme'; @@ -64,104 +64,114 @@ export type ButtonOrLinkProps = (ButtonLinkProps | ButtonProps) & /** * A custom button component that can be used in React applications. */ -const Button = ({ - children, - className, - leftIcon, - rightIcon, - fullWidth, - iconOnly, - shape, - variant, - ...props -}: ButtonOrLinkProps) => { - // Conditionally render between , or ; - } +const Button = forwardRef< + HTMLButtonElement | HTMLAnchorElement, + ButtonOrLinkProps +>( + ( + { + children, + className, + leftIcon, + rightIcon, + fullWidth, + iconOnly, + shape, + variant, + ...props }, - [], - ); + ref, + ) => { + // Conditionally render between , or ; + } + }, + [], ); - } - const iconSize = useCallback(() => { - switch (styleProps.size) { - case 'lg': - return { width: 20, height: 20 }; - case 'sm': - case 'xs': - return { width: 16, height: 16 }; - case 'md': - default: { - return { width: 18, height: 18 }; - } + /** + * Extracts specific style properties from the given props object and returns them as a new object. + */ + const styleProps = (({ + variant = 'primary', + size = 'md', + fullWidth = false, + iconOnly = false, + shape = 'rounded', + as, + }) => ({ + variant, + size, + fullWidth, + iconOnly, + shape, + as, + }))({ ...props, fullWidth, iconOnly, shape, variant }); + + /** + * Validates that a button component has either children or an aria-label prop. + */ + if (typeof children === 'undefined' && !props['aria-label']) { + throw new Error( + 'Button components must have either children or an aria-label prop', + ); } - }, [styleProps.size])(); - return ( - - {cloneIcon(leftIcon, { ...iconSize })} - {children} - {cloneIcon(rightIcon, { ...iconSize })} - - ); -}; + const iconSize = useCallback(() => { + switch (styleProps.size) { + case 'lg': + return { width: 20, height: 20 }; + case 'sm': + case 'xs': + return { width: 16, height: 16 }; + case 'md': + default: { + return { width: 18, height: 18 }; + } + } + }, [styleProps.size])(); + + return ( + + {cloneIcon(leftIcon, { ...iconSize })} + {children} + {cloneIcon(rightIcon, { ...iconSize })} + + ); + }, +); Button.displayName = 'Button';