From 082353e9bca3c6917f24548606c3c66e29578fc6 Mon Sep 17 00:00:00 2001 From: Andre H Date: Fri, 16 Feb 2024 12:31:13 +0700 Subject: [PATCH 01/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20implement=20?= =?UTF-8?q?`components`=20page=20to=20act=20as=20storybook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 packages/frontend/src/pages/components/index.tsx diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx new file mode 100644 index 00000000..9a7a8519 --- /dev/null +++ b/packages/frontend/src/pages/components/index.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +const Page = () => { + return ( +
+
+

Manual Storybook

+

+ Get started by editing{' '} + + packages/frontend/src/pages/components/index.tsx + +

+
+ + {/* Insert Components here */} +
+

Component A

+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ ); +}; + +export default Page; From d498ba7e9a0822c75b0e5170d24a7533d20e45c3 Mon Sep 17 00:00:00 2001 From: Zachery Ng Date: Mon, 19 Feb 2024 20:05:29 +0800 Subject: [PATCH 02/82] feat: focus ring class --- packages/frontend/src/index.css | 6 ++++++ .../frontend/src/pages/components/index.tsx | 20 +++++++++++++++---- packages/frontend/tailwind.config.js | 9 ++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/index.css b/packages/frontend/src/index.css index b5c61c95..5f6197b6 100644 --- a/packages/frontend/src/index.css +++ b/packages/frontend/src/index.css @@ -1,3 +1,9 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer utilities { + .focus-ring { + @apply focus-visible:ring-[3px] focus-visible:ring-snowball-200 focus-visible:ring-offset-1 focus-visible:ring-offset-snowball-500 focus-visible:outline-none; + } +} \ No newline at end of file diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 9a7a8519..9ba75753 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -18,10 +18,22 @@ const Page = () => {

Component A

-
-
-
-
+
+
+
+
diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 70266ee3..c3d8590f 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -8,7 +8,14 @@ export default withMT({ '../../node_modules/@material-tailwind/react/theme/components/**/*.{js,ts,jsx,tsx}', ], theme: { - extend: {}, + extend: { + colors: { + snowball: { + 200: '#DDEEFD', + 500: '#47A4FA', + }, + }, + }, }, plugins: [], }); From 499dba2c5ecf8b62c8a2fd8c80104807541f6a2f Mon Sep 17 00:00:00 2001 From: Zachery Ng Date: Mon, 19 Feb 2024 20:12:25 +0800 Subject: [PATCH 03/82] chore: add full palette colors --- packages/frontend/tailwind.config.js | 127 ++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index c3d8590f..6477b651 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -10,9 +10,132 @@ export default withMT({ theme: { extend: { colors: { + emerald: { + 100: '#d1fae5', + 200: '#a9f1d0', + 300: '#6ee7b7', + 400: '#34d399', + 50: '#ecfdf5', + 500: '#10b981', + 600: '#059669', + 700: '#047857', + 800: '#065f46', + 900: '#064e3b', + }, + gray: { + 0: '#ffffff', + 100: '#f1f5f9', + 200: '#e2e9f0', + 300: '#cbd6e1', + 400: '#94a7b8', + 50: '#f8fafc', + 500: '#60788f', + 600: '#475969', + 700: '#334555', + 800: '#1b2d3e', + 900: '#0b1d2e', + }, + orange: { + 100: '#ffedd5', + 200: '#fed7aa', + 300: '#fdba74', + 400: '#fb923c', + 50: '#fff7ed', + 500: '#f97316', + 600: '#ea580c', + 700: '#c2410c', + 800: '#9a3412', + 900: '#7c2d12', + }, + rose: { + 100: '#ffe4e6', + 200: '#fecdd3', + 300: '#fda4af', + 400: '#fb7185', + 50: '#fff1f2', + 500: '#f43f5e', + 600: '#e11d48', + 700: '#be123c', + 800: '#9f1239', + 900: '#881337', + }, snowball: { - 200: '#DDEEFD', - 500: '#47A4FA', + 100: '#e1f1fe', + 200: '#ddeefd', + 300: '#cfe6fc', + 400: '#74bafb', + 50: '#ecf6fe', + 500: '#47a4fa', + 600: '#0f86f5', + 700: '#0977dc', + 800: '#075185', + 900: '#0a3a5c', + }, + 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', }, }, }, From e850435c2d5314479e51d6b98e17d93ae1c5a200 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Mon, 19 Feb 2024 20:13:11 +0700 Subject: [PATCH 04/82] =?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 05/82] =?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 06/82] =?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 07/82] =?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 08/82] =?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 09/82] =?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 10/82] =?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 11/82] =?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 12/82] =?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 13/82] =?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 14/82] =?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 15/82] =?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 16/82] =?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 17/82] =?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'; From 4f2944a9746f9be96149239d187f6c5f31f165db Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 16:31:56 +0700 Subject: [PATCH 18/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20install=20`date-f?= =?UTF-8?q?ns`=20and=20`react-calendar`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/package.json | 3 +- yarn.lock | 62 ++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index a2a77ba1..49736684 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -13,13 +13,14 @@ "@types/react": "^18.2.42", "@types/react-dom": "^18.2.17", "assert": "^2.1.0", - "date-fns": "^3.0.1", + "date-fns": "^3.3.1", "downshift": "^8.2.3", "eslint-config-react-app": "^7.0.1", "gql-client": "^1.0.0", "luxon": "^3.4.4", "octokit": "^3.1.2", "react": "^18.2.0", + "react-calendar": "^4.8.0", "react-code-blocks": "^0.1.6", "react-day-picker": "^8.9.1", "react-dom": "^18.2.0", diff --git a/yarn.lock b/yarn.lock index e4713492..8845c973 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3910,6 +3910,18 @@ dependencies: "@types/node" "*" +"@types/lodash.memoize@^4.1.7": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz#9f8912d39b6e450c0d342a2b74c99d331bf2016b" + integrity sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.202" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" @@ -4687,6 +4699,11 @@ "@webassemblyjs/ast" "1.11.6" "@xtuc/long" "4.2.2" +"@wojtekmaj/date-utils@^1.1.3": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz#c3cd67177ac781cfa5736219d702a55a2aea5f2b" + integrity sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww== + "@wry/caches@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@wry/caches/-/caches-1.0.1.tgz#8641fd3b6e09230b86ce8b93558d44cf1ece7e52" @@ -6144,6 +6161,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +clsx@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" + integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== + cmd-shim@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" @@ -6821,10 +6843,10 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.0.1.tgz#a95b3e8296e72cf57c99819f37679aa27ca65ec4" - integrity sha512-cr9igCUa0QSqgAMj7JOrYTY6Nh1rmyGrFDko7ADqfmaQqP/I2N4rlfrLl7AWuzDaoIpz6MNjoEcTPzgZYIrhnA== +date-fns@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.1.tgz#7581daca0892d139736697717a168afbb908cfed" + integrity sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw== dateformat@^3.0.3: version "3.0.3" @@ -8720,6 +8742,14 @@ get-tsconfig@^4.7.0: dependencies: resolve-pkg-maps "^1.0.0" +get-user-locale@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-2.3.1.tgz#fc7319429c8a70fac01b3b2a0b08b0c71c1d3fe2" + integrity sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ== + dependencies: + "@types/lodash.memoize" "^4.1.7" + lodash.memoize "^4.1.1" + git-raw-commits@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-3.0.0.tgz#5432f053a9744f67e8db03dbc48add81252cfdeb" @@ -11012,7 +11042,7 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== -lodash.memoize@^4.1.2: +lodash.memoize@^4.1.1, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== @@ -11065,7 +11095,7 @@ long@^5.2.0: resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -13281,7 +13311,7 @@ promzard@^1.0.0: dependencies: read "^2.0.0" -prop-types@15.8.1, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@15.8.1, prop-types@^15.6.0, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -13440,6 +13470,17 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" +react-calendar@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-4.8.0.tgz#61edbba6d17e7ef8a8012de9143b5e5ff41104c8" + integrity sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA== + dependencies: + "@wojtekmaj/date-utils" "^1.1.3" + clsx "^2.0.0" + get-user-locale "^2.2.1" + prop-types "^15.6.0" + warning "^4.0.0" + react-code-blocks@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/react-code-blocks/-/react-code-blocks-0.1.6.tgz#ec64e7899223d3e910eb916465a66d95ce1ae1b2" @@ -15880,6 +15921,13 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" +warning@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz" From 0a5a1b8c5550c8bca0acaa8a47673e19c60f54e5 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 16:32:35 +0700 Subject: [PATCH 19/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20vscode=20se?= =?UTF-8?q?ttings=20to=20enable=20tailwind=20variant=20intellisense?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..f3e08235 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + // IntelliSense for taiwind variants + "tailwindCSS.experimental.classRegex": [ + ["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] + ] +} From cc97ddff9d828592d38827a981173ffcec6ce83b Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 16:33:12 +0700 Subject: [PATCH 20/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20create=20che?= =?UTF-8?q?vron=20left,=20right,=20and=20grabbed=20horizontal=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomIcon/ChevronGrabberHorizontal.tsx | 20 ++++++++++++++++++ .../shared/CustomIcon/ChevronLeft.tsx | 21 +++++++++++++++++++ .../shared/CustomIcon/ChevronRight.tsx | 21 +++++++++++++++++++ .../src/components/shared/CustomIcon/index.ts | 3 +++ 4 files changed, 65 insertions(+) create mode 100644 packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx create mode 100644 packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx create mode 100644 packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx new file mode 100644 index 00000000..b62f79d0 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const ChevronGrabberHorizontal = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx new file mode 100644 index 00000000..f9c4885c --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const ChevronLeft = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx new file mode 100644 index 00000000..75da0052 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const ChevronRight = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index d50d942c..e4b51032 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -1,2 +1,5 @@ export * from './PlusIcon'; export * from './CustomIcon'; +export * from './ChevronGrabberHorizontal'; +export * from './ChevronLeft'; +export * from './ChevronRight'; From ad7dd1920a3a0f74d3f2520a6a284b49da7a7b97 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 16:33:26 +0700 Subject: [PATCH 21/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20create=20cal?= =?UTF-8?q?endar=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Calendar/Calendar.css | 128 ++++++++ .../shared/Calendar/Calendar.theme.ts | 49 +++ .../components/shared/Calendar/Calendar.tsx | 292 ++++++++++++++++++ .../src/components/shared/Calendar/index.ts | 1 + 4 files changed, 470 insertions(+) create mode 100644 packages/frontend/src/components/shared/Calendar/Calendar.css create mode 100644 packages/frontend/src/components/shared/Calendar/Calendar.theme.ts create mode 100644 packages/frontend/src/components/shared/Calendar/Calendar.tsx create mode 100644 packages/frontend/src/components/shared/Calendar/index.ts diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.css b/packages/frontend/src/components/shared/Calendar/Calendar.css new file mode 100644 index 00000000..85ce4182 --- /dev/null +++ b/packages/frontend/src/components/shared/Calendar/Calendar.css @@ -0,0 +1,128 @@ +/* React Calendar */ +.react-calendar { + @apply border-none font-sans; +} + +/* Weekdays -- START */ +.react-calendar__month-view__weekdays { + @apply p-0 flex items-center justify-center; +} + +.react-calendar__month-view__weekdays__weekday { + @apply h-8 w-12 flex items-center justify-center p-0 font-medium text-xs text-elements-disabled mb-2; +} + +abbr[title] { + text-decoration: none; +} +/* Weekdays -- END */ + +/* Days -- START */ +.react-calendar__month-view__days { + @apply p-0 gap-0; +} + +.react-calendar__month-view__days__day--neighboringMonth { + @apply !text-elements-disabled; +} + +.react-calendar__month-view__days__day--neighboringMonth:hover { + @apply !text-elements-disabled !bg-transparent; +} + +.react-calendar__month-view__days__day--neighboringMonth { + @apply !text-elements-disabled !bg-transparent; +} + +/* For weekend days */ +.react-calendar__month-view__days__day--weekend { + /* color: ${colors.grey[950]} !important; */ +} + +.react-calendar__tile { + @apply h-12 w-12 text-elements-high-em; +} + +.react-calendar__tile:hover { + @apply bg-base-bg-emphasized rounded-lg; +} + +.react-calendar__tile:focus-visible { + @apply bg-base-bg-emphasized rounded-lg focus-ring z-10; +} + +.react-calendar__tile--now { + @apply bg-base-bg-emphasized text-elements-high-em rounded-lg; +} + +.react-calendar__tile--now:hover { + @apply bg-base-bg-emphasized text-elements-high-em rounded-lg; +} + +.react-calendar__tile--now:focus-visible { + @apply bg-base-bg-emphasized text-elements-high-em rounded-lg focus-ring; +} + +.react-calendar__tile--active { + @apply bg-controls-primary text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--active:hover { + @apply bg-controls-primary-hovered; +} + +.react-calendar__tile--active:focus-visible { + @apply bg-controls-primary-hovered focus-ring; +} + +/* Range -- START */ +.react-calendar__tile--range { + @apply bg-controls-secondary text-elements-on-secondary rounded-none; +} + +.react-calendar__tile--range:hover { + @apply bg-controls-secondary-hovered text-elements-on-secondary rounded-none; +} + +.react-calendar__tile--range:focus-visible { + @apply bg-controls-secondary-hovered text-elements-on-secondary rounded-lg; +} + +.react-calendar__tile--rangeStart { + @apply bg-controls-primary text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--rangeStart:hover { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--rangeStart:focus-visible { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring; +} + +.react-calendar__tile--rangeEnd { + @apply bg-controls-primary text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--rangeEnd:hover { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--rangeEnd:focus-visible { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring; +} +/* Range -- END */ +/* Days -- END */ + +/* Months -- START */ +.react-calendar__tile--hasActive { + @apply bg-controls-primary text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--hasActive:hover { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--hasActive:focus-visible { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring; +} diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts new file mode 100644 index 00000000..e39e5925 --- /dev/null +++ b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts @@ -0,0 +1,49 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const calendarTheme = tv({ + slots: { + wrapper: [ + 'max-w-[352px]', + 'bg-surface-floating', + 'shadow-calendar', + 'rounded-xl', + ], + calendar: ['flex', 'flex-col', 'py-2', 'px-2', 'gap-2'], + navigation: [ + 'flex', + 'items-center', + 'justify-between', + 'gap-3', + 'py-2.5', + 'px-1', + ], + dropdowns: ['flex', 'items-center', 'justify-center', 'gap-1.5', 'flex-1'], + dropdown: [ + 'flex', + 'items-center', + 'gap-2', + 'px-2', + 'py-2', + 'rounded-lg', + 'border', + 'border-border-interactive', + 'text-elements-high-em', + 'shadow-field', + 'bg-white', + 'hover:bg-base-bg-alternate', + 'focus-visible:bg-base-bg-alternate', + ], + footer: [ + 'flex', + 'items-center', + 'justify-end', + 'py-3', + 'px-2', + 'gap-3', + 'border-t', + 'border-border-separator', + ], + }, +}); + +export type CalendarTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.tsx b/packages/frontend/src/components/shared/Calendar/Calendar.tsx new file mode 100644 index 00000000..737eee75 --- /dev/null +++ b/packages/frontend/src/components/shared/Calendar/Calendar.tsx @@ -0,0 +1,292 @@ +import React, { + ComponentPropsWithRef, + MouseEvent, + ReactNode, + useCallback, + useState, +} from 'react'; +import { + Calendar as ReactCalendar, + CalendarProps as ReactCalendarProps, +} from 'react-calendar'; +import { Value } from 'react-calendar/dist/cjs/shared/types'; +import { CalendarTheme, calendarTheme } from './Calendar.theme'; +import { Button } from 'components/shared/Button'; +import { + ChevronGrabberHorizontal, + ChevronLeft, + ChevronRight, +} from 'components/shared/CustomIcon'; + +import './Calendar.css'; +import { format } from 'date-fns'; + +const CALENDAR_VIEW = ['month', 'year', 'decade', 'century'] as const; +export type CalendarView = (typeof CALENDAR_VIEW)[number]; + +/** + * Defines a custom set of props for a React calendar component by excluding specific props + * from the original ReactCalendarProps type. + * @type {CustomReactCalendarProps} + */ +type CustomReactCalendarProps = Omit< + ReactCalendarProps, + 'view' | 'showNavigation' | 'onClickMonth' | 'onClickYear' +>; + +export interface CalendarProps extends CustomReactCalendarProps, CalendarTheme { + /** + * Optional props for wrapping a component with a div element. + */ + wrapperProps?: ComponentPropsWithRef<'div'>; + /** + * Props for the calendar wrapper component. + */ + calendarWrapperProps?: ComponentPropsWithRef<'div'>; + /** + * Optional props for the footer component. + */ + footerProps?: ComponentPropsWithRef<'div'>; + /** + * Optional custom actions to be rendered. + */ + actions?: ReactNode; + /** + * Optional callback function that is called when a value is selected. + * @param {Value} value - The selected value + * @returns None + */ + onSelect?: (value: Value) => void; + /** + * Optional callback function that is called when a cancel action is triggered. + * @returns None + */ + onCancel?: () => void; +} + +/** + * Calendar component that allows users to select dates and navigate through months and years. + * @param {Object} CalendarProps - Props for the Calendar component. + * @returns {JSX.Element} A calendar component with navigation, date selection, and actions. + */ +export const Calendar = ({ + selectRange, + activeStartDate: activeStartDateProp, + value: valueProp, + wrapperProps, + calendarWrapperProps, + footerProps, + actions, + onSelect, + onCancel, + onChange: onChangeProp, + ...props +}: CalendarProps): JSX.Element => { + const { wrapper, calendar, navigation, dropdowns, dropdown, footer } = + calendarTheme(); + + const today = new Date(); + const currentMonth = format(today, 'MMM'); + const currentyear = format(today, 'yyyy'); + + const [view, setView] = useState('month'); + const [activeDate, setActiveDate] = useState( + activeStartDateProp ?? today, + ); + const [value, setValue] = useState(valueProp as Value); + const [month, setMonth] = useState(currentMonth); + const [year, setYear] = useState(currentyear); + + /** + * Update the navigation label based on the active date + */ + const changeNavigationLabel = useCallback( + (date: Date) => { + setMonth(format(date, 'MMM')); + setYear(format(date, 'yyyy')); + }, + [setMonth, setYear], + ); + + /** + * Change the active date base on the action and range + */ + const handleNavigate = useCallback( + (action: 'previous' | 'next', range: 'month' | 'year' | 'decade') => { + setActiveDate((date) => { + const newDate = new Date(date); + switch (range) { + case 'month': + newDate.setMonth( + action === 'previous' ? date.getMonth() - 1 : date.getMonth() + 1, + ); + break; + case 'year': + newDate.setFullYear( + action === 'previous' + ? date.getFullYear() - 1 + : date.getFullYear() + 1, + ); + break; + case 'decade': + newDate.setFullYear( + action === 'previous' + ? date.getFullYear() - 10 + : date.getFullYear() + 10, + ); + break; + } + changeNavigationLabel(newDate); + return newDate; + }); + }, + [setActiveDate, changeNavigationLabel], + ); + + /** + * Change the view of the calendar + */ + const handleChangeView = useCallback( + (view: CalendarView) => { + setView(view); + }, + [setView], + ); + + /** + * Change the active date and set the view to the selected type + * and also update the navigation label + */ + const handleChangeNavigation = useCallback( + (type: 'month' | 'year', date: Date) => { + setActiveDate(date); + changeNavigationLabel(date); + setView(type); + }, + [setActiveDate, changeNavigationLabel, setView], + ); + + const handlePrevious = useCallback(() => { + switch (view) { + case 'month': + return handleNavigate('previous', 'month'); + case 'year': + return handleNavigate('previous', 'year'); + case 'decade': + return handleNavigate('previous', 'decade'); + } + }, [view]); + + const handleNext = useCallback(() => { + switch (view) { + case 'month': + return handleNavigate('next', 'month'); + case 'year': + return handleNavigate('next', 'year'); + case 'decade': + return handleNavigate('next', 'decade'); + } + }, [view]); + + const handleChange = useCallback( + (newValue: Value, event: MouseEvent) => { + setValue(newValue); + + // Call the onChange prop if it exists + onChangeProp?.(newValue, event); + + /** + * Update the active date and navigation label + * + * NOTE: + * For range selection, the active date is not updated + * The user only can select multiple dates within the same month + */ + if (!selectRange) { + setActiveDate(newValue as Date); + changeNavigationLabel(newValue as Date); + } + }, + [setValue, setActiveDate, changeNavigationLabel, selectRange], + ); + + return ( +
+ {/* Calendar wrapper */} +
+ {/* Navigation */} +
+ +
+ + +
+ +
+ + {/* Calendar */} + handleChangeNavigation('month', date)} + onClickYear={(date) => handleChangeNavigation('year', date)} + /> +
+ + {/* Footer or CTA */} +
+ {actions ? ( + actions + ) : ( + <> + + + + )} +
+
+ ); +}; diff --git a/packages/frontend/src/components/shared/Calendar/index.ts b/packages/frontend/src/components/shared/Calendar/index.ts new file mode 100644 index 00000000..a7233805 --- /dev/null +++ b/packages/frontend/src/components/shared/Calendar/index.ts @@ -0,0 +1 @@ +export * from './Calendar'; From 4f7f9cf9143ddd60a1ab4fd91852d50216153924 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 16:33:59 +0700 Subject: [PATCH 22/82] =?UTF-8?q?=F0=9F=93=9D=20docs:=20add=20calendar=20c?= =?UTF-8?q?omponent=20to=20the=20example=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/components/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 8c23f341..4ff2e58f 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,4 +1,5 @@ import { Button, ButtonOrLinkProps } from 'components/shared/Button'; +import { Calendar } from 'components/shared/Calendar'; import { PlusIcon } from 'components/shared/CustomIcon'; import React from 'react'; @@ -13,6 +14,7 @@ const Page = () => { packages/frontend/src/pages/components/index.tsx

+
{/* Insert Components here */} @@ -69,6 +71,15 @@ const Page = () => { ))}
+ +
+
+

Calendar

+
+ + +
+
); From af0d28e6546d0b4f41d4f8a6a178fec04917d015 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 16:34:15 +0700 Subject: [PATCH 23/82] =?UTF-8?q?=F0=9F=8E=A8=20style:=20add=20new=20shado?= =?UTF-8?q?ws?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/tailwind.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 92ee77d0..00ce21f9 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -144,6 +144,9 @@ export default withMT({ boxShadow: { button: 'inset 0px 0px 4px rgba(255, 255, 255, 0.25), inset 0px -2px 0px rgba(0, 0, 0, 0.04)', + calendar: + '0px 3px 20px rgba(8, 47, 86, 0.1), 0px 0px 4px rgba(8, 47, 86, 0.14)', + field: '0px 1px 2px rgba(0, 0, 0, 0.04)', }, spacing: { 2.5: '0.625rem', From dc6bf3794a143dfc42d1e9dc99ccbc73c6c607f4 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 16:54:32 +0700 Subject: [PATCH 24/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20add=20intera?= =?UTF-8?q?ction=20on=20the=20example=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 4ff2e58f..775acbb1 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,9 +1,13 @@ import { Button, ButtonOrLinkProps } from 'components/shared/Button'; import { Calendar } from 'components/shared/Calendar'; import { PlusIcon } from 'components/shared/CustomIcon'; -import React from 'react'; +import React, { useState } from 'react'; +import { Value } from 'react-calendar/dist/cjs/shared/types'; const Page = () => { + const [singleDate, setSingleDate] = useState(); + const [dateRange, setDateRange] = useState(); + return (
@@ -75,9 +79,25 @@ const Page = () => {

Calendar

-
- - +
+
+

Selected date: {singleDate?.toString()}

+ +
+
+

+ Start date:{' '} + {dateRange instanceof Array ? dateRange[0]?.toString() : ''}{' '} +
+ End date:{' '} + {dateRange instanceof Array ? dateRange[1]?.toString() : ''} +

+ +
From 5c5d759c10c8d891e854e0052da115ada6daccfd Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 23:08:04 +0700 Subject: [PATCH 25/82] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20fix=20ty?= =?UTF-8?q?po=20and=20change=20to=20readable=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/Calendar/Calendar.theme.ts | 4 +-- .../components/shared/Calendar/Calendar.tsx | 30 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts index e39e5925..ce6e93d0 100644 --- a/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts +++ b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts @@ -17,8 +17,8 @@ export const calendarTheme = tv({ 'py-2.5', 'px-1', ], - dropdowns: ['flex', 'items-center', 'justify-center', 'gap-1.5', 'flex-1'], - dropdown: [ + actions: ['flex', 'items-center', 'justify-center', 'gap-1.5', 'flex-1'], + button: [ 'flex', 'items-center', 'gap-2', diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.tsx b/packages/frontend/src/components/shared/Calendar/Calendar.tsx index 737eee75..dfc15e36 100644 --- a/packages/frontend/src/components/shared/Calendar/Calendar.tsx +++ b/packages/frontend/src/components/shared/Calendar/Calendar.tsx @@ -21,7 +21,7 @@ import { import './Calendar.css'; import { format } from 'date-fns'; -const CALENDAR_VIEW = ['month', 'year', 'decade', 'century'] as const; +const CALENDAR_VIEW = ['month', 'year', 'decade'] as const; export type CalendarView = (typeof CALENDAR_VIEW)[number]; /** @@ -82,12 +82,18 @@ export const Calendar = ({ onChange: onChangeProp, ...props }: CalendarProps): JSX.Element => { - const { wrapper, calendar, navigation, dropdowns, dropdown, footer } = - calendarTheme(); + const { + wrapper, + calendar, + navigation, + actions: actionsClass, + button, + footer, + } = calendarTheme(); const today = new Date(); const currentMonth = format(today, 'MMM'); - const currentyear = format(today, 'yyyy'); + const currentYear = format(today, 'yyyy'); const [view, setView] = useState('month'); const [activeDate, setActiveDate] = useState( @@ -95,7 +101,7 @@ export const Calendar = ({ ); const [value, setValue] = useState(valueProp as Value); const [month, setMonth] = useState(currentMonth); - const [year, setYear] = useState(currentyear); + const [year, setYear] = useState(currentYear); /** * Update the navigation label based on the active date @@ -112,10 +118,10 @@ export const Calendar = ({ * Change the active date base on the action and range */ const handleNavigate = useCallback( - (action: 'previous' | 'next', range: 'month' | 'year' | 'decade') => { + (action: 'previous' | 'next', view: CalendarView) => { setActiveDate((date) => { const newDate = new Date(date); - switch (range) { + switch (view) { case 'month': newDate.setMonth( action === 'previous' ? date.getMonth() - 1 : date.getMonth() + 1, @@ -158,10 +164,10 @@ export const Calendar = ({ * and also update the navigation label */ const handleChangeNavigation = useCallback( - (type: 'month' | 'year', date: Date) => { + (view: 'month' | 'year', date: Date) => { setActiveDate(date); changeNavigationLabel(date); - setView(type); + setView(view); }, [setActiveDate, changeNavigationLabel, setView], ); @@ -225,10 +231,10 @@ export const Calendar = ({ -
+
+ +
+ +
+

Checkbox

+
+ {Array.from({ length: 5 }).map((_, index) => ( + + ))} +
+
+ {Array.from({ length: 2 }).map((_, index) => ( + + ))} +
+
); From e482f998a194b8edf7b5f640ade1f7cd546c6722 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 18:50:31 +0700 Subject: [PATCH 30/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20create=20bad?= =?UTF-8?q?ge=20comopnent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Badge/Badge.theme.ts | 43 +++++++++++++++++++ .../src/components/shared/Badge/Badge.tsx | 33 ++++++++++++++ .../src/components/shared/Badge/index.ts | 1 + 3 files changed, 77 insertions(+) create mode 100644 packages/frontend/src/components/shared/Badge/Badge.theme.ts create mode 100644 packages/frontend/src/components/shared/Badge/Badge.tsx create mode 100644 packages/frontend/src/components/shared/Badge/index.ts diff --git a/packages/frontend/src/components/shared/Badge/Badge.theme.ts b/packages/frontend/src/components/shared/Badge/Badge.theme.ts new file mode 100644 index 00000000..9019a0da --- /dev/null +++ b/packages/frontend/src/components/shared/Badge/Badge.theme.ts @@ -0,0 +1,43 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const badgeTheme = tv({ + slots: { + wrapper: ['rounded-full', 'grid', 'place-content-center'], + }, + variants: { + variant: { + primary: { + wrapper: ['bg-controls-primary', 'text-elements-on-primary'], + }, + secondary: { + wrapper: ['bg-controls-secondary', 'text-elements-on-secondary'], + }, + tertiary: { + wrapper: [ + 'bg-controls-tertiary', + 'border', + 'border-border-interactive/10', + 'text-elements-high-em', + 'shadow-button', + ], + }, + inset: { + wrapper: ['bg-controls-inset', 'text-elements-high-em'], + }, + }, + size: { + sm: { + wrapper: ['h-5', 'w-5', 'text-xs'], + }, + xs: { + wrapper: ['h-4', 'w-4', 'text-2xs', 'font-medium'], + }, + }, + }, + defaultVariants: { + variant: 'primary', + size: 'sm', + }, +}); + +export type BadgeTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Badge/Badge.tsx b/packages/frontend/src/components/shared/Badge/Badge.tsx new file mode 100644 index 00000000..50bae236 --- /dev/null +++ b/packages/frontend/src/components/shared/Badge/Badge.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { ComponentPropsWithoutRef } from 'react'; +import { BadgeTheme, badgeTheme } from './Badge.theme'; + +export interface BadgeProps + extends ComponentPropsWithoutRef<'div'>, + BadgeTheme {} + +/** + * A badge is a small status descriptor for UI elements. + * It can be used to indicate a status, a count, or a category. + * It is typically used in lists, tables, or navigation elements. + * + * @example + * ```tsx + * 1 { + const { wrapper } = badgeTheme(); + + return ( +
+ {children} +
+ ); +}; diff --git a/packages/frontend/src/components/shared/Badge/index.ts b/packages/frontend/src/components/shared/Badge/index.ts new file mode 100644 index 00000000..9c8edca2 --- /dev/null +++ b/packages/frontend/src/components/shared/Badge/index.ts @@ -0,0 +1 @@ +export * from './Badge'; From cc9c24cca119cb7a3d8fcbdbd67cd3b6069635b8 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 18:50:52 +0700 Subject: [PATCH 31/82] =?UTF-8?q?=F0=9F=8E=A8=20style:=20add=20new=20font?= =?UTF-8?q?=20sizes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/tailwind.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 92ee77d0..6f6fc6ff 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -12,6 +12,10 @@ export default withMT({ sans: ['Inter', 'sans-serif'], }, extend: { + fontSize: { + '2xs': '0.625rem', + '3xs': '0.5rem', + }, colors: { emerald: { 100: '#d1fae5', From f764bea6d18837a270f6ca6b3c2a878000feb2a3 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 18:51:09 +0700 Subject: [PATCH 32/82] =?UTF-8?q?=F0=9F=93=9D=20docs:=20add=20badge=20to?= =?UTF-8?q?=20the=20example=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 8c23f341..d751a7c5 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,3 +1,4 @@ +import { Badge, BadgeProps } from 'components/shared/Badge'; import { Button, ButtonOrLinkProps } from 'components/shared/Button'; import { PlusIcon } from 'components/shared/CustomIcon'; import React from 'react'; @@ -69,6 +70,29 @@ const Page = () => { ))}
+ +
+ +
+

Badge

+
+ {['primary', 'secondary', 'tertiary', 'inset'].map( + (variant, index) => ( +
+ {['sm', 'xs'].map((size) => ( + + 1 + + ))} +
+ ), + )} +
+
); From a5bce0cda24573cd945b9f3f61f3a967fc82a4f4 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Tue, 20 Feb 2024 23:34:20 +0700 Subject: [PATCH 33/82] =?UTF-8?q?=F0=9F=8E=A8=20style:=20adjust=20disabled?= =?UTF-8?q?=20hover=20text=20color?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/components/shared/Checkbox/Checkbox.theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Checkbox/Checkbox.theme.ts b/packages/frontend/src/components/shared/Checkbox/Checkbox.theme.ts index 7122f656..e39b66f3 100644 --- a/packages/frontend/src/components/shared/Checkbox/Checkbox.theme.ts +++ b/packages/frontend/src/components/shared/Checkbox/Checkbox.theme.ts @@ -54,7 +54,7 @@ export const getCheckboxVariant = tv({ disabled: { true: { wrapper: ['cursor-not-allowed'], - indicator: ['group-hover:text-current'], + indicator: ['group-hover:text-transparent'], input: [ 'group-hover:border-border-interactive/[0.14]', 'group-hover:bg-controls-disabled', From f0121605c4a97939d64c845492f33ece82743203 Mon Sep 17 00:00:00 2001 From: Zachery Date: Wed, 21 Feb 2024 11:39:38 +0800 Subject: [PATCH 34/82] feat: avatar component (#76) --- packages/frontend/package.json | 1 + .../components/shared/Avatar/Avatar.theme.ts | 71 +++++++ .../src/components/shared/Avatar/Avatar.tsx | 40 ++++ .../src/components/shared/Avatar/index.ts | 2 + .../frontend/src/pages/components/index.tsx | 189 +++++++++++------- yarn.lock | 57 +++++- 6 files changed, 286 insertions(+), 74 deletions(-) create mode 100644 packages/frontend/src/components/shared/Avatar/Avatar.theme.ts create mode 100644 packages/frontend/src/components/shared/Avatar/Avatar.tsx create mode 100644 packages/frontend/src/components/shared/Avatar/index.ts diff --git a/packages/frontend/package.json b/packages/frontend/package.json index d1e6e78b..7e369024 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -6,6 +6,7 @@ "@fontsource/inter": "^5.0.16", "@material-tailwind/react": "^2.1.7", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-avatar": "^1.0.4", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/packages/frontend/src/components/shared/Avatar/Avatar.theme.ts b/packages/frontend/src/components/shared/Avatar/Avatar.theme.ts new file mode 100644 index 00000000..bc594274 --- /dev/null +++ b/packages/frontend/src/components/shared/Avatar/Avatar.theme.ts @@ -0,0 +1,71 @@ +import { tv, type VariantProps } from 'tailwind-variants'; + +export const avatarTheme = tv( + { + base: ['relative', 'block', 'rounded-full', 'overflow-hidden'], + slots: { + image: [ + 'h-full', + 'w-full', + 'rounded-[inherit]', + 'object-cover', + 'object-center', + ], + fallback: [ + 'grid', + 'select-none', + 'place-content-center', + 'h-full', + 'w-full', + 'rounded-[inherit]', + 'font-medium', + ], + }, + variants: { + type: { + gray: { + fallback: ['text-elements-highEm', 'bg-base-bg-emphasized'], + }, + orange: { + fallback: ['text-elements-warning', 'bg-base-bg-emphasized-warning'], + }, + blue: { + fallback: ['text-elements-info', 'bg-base-bg-emphasized-info'], + }, + }, + size: { + 18: { + base: ['rounded-md', 'h-[18px]', 'w-[18px]', 'text-[0.625rem]'], + }, + 20: { + base: ['rounded-md', 'h-5', 'w-5', 'text-[0.625rem]'], + }, + 24: { + base: ['rounded-md', 'h-6', 'w-6', 'text-[0.625rem]'], + }, + 28: { + base: ['rounded-lg', 'h-[28px]', 'w-[28px]', 'text-[0.625rem]'], + }, + 32: { + base: ['rounded-lg', 'h-8', 'w-8', 'text-xs'], + }, + 36: { + base: ['rounded-xl', 'h-[36px]', 'w-[36px]', 'text-xs'], + }, + 40: { + base: ['rounded-xl', 'h-10', 'w-10', 'text-sm'], + }, + 44: { + base: ['rounded-xl', 'h-[44px]', 'w-[44px]', 'text-sm'], + }, + }, + }, + defaultVariants: { + size: 24, + type: 'gray', + }, + }, + { responsiveVariants: true }, +); + +export type AvatarVariants = VariantProps; diff --git a/packages/frontend/src/components/shared/Avatar/Avatar.tsx b/packages/frontend/src/components/shared/Avatar/Avatar.tsx new file mode 100644 index 00000000..fd7b1e59 --- /dev/null +++ b/packages/frontend/src/components/shared/Avatar/Avatar.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { type ComponentPropsWithoutRef, type ComponentProps } from 'react'; +import { avatarTheme, type AvatarVariants } from './Avatar.theme'; +import * as PrimitiveAvatar from '@radix-ui/react-avatar'; + +export type AvatarProps = ComponentPropsWithoutRef<'div'> & { + imageSrc?: string | null; + initials?: string; + imageProps?: ComponentProps; + fallbackProps?: ComponentProps; +} & AvatarVariants; + +export const Avatar = ({ + className, + size, + type, + imageSrc, + imageProps, + fallbackProps, + initials, +}: AvatarProps) => { + const { base, image, fallback } = avatarTheme({ size, type }); + + return ( + + {imageSrc && ( + + )} + +
+ {initials} +
+
+
+ ); +}; diff --git a/packages/frontend/src/components/shared/Avatar/index.ts b/packages/frontend/src/components/shared/Avatar/index.ts new file mode 100644 index 00000000..35478c8f --- /dev/null +++ b/packages/frontend/src/components/shared/Avatar/index.ts @@ -0,0 +1,2 @@ +export * from './Avatar'; +export * from './Avatar.theme'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 74086848..456f433e 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,3 +1,4 @@ +import { Avatar, AvatarVariants } from 'components/shared/Avatar'; import { Badge, BadgeProps } from 'components/shared/Badge'; import { Button, ButtonOrLinkProps } from 'components/shared/Button'; import { Calendar } from 'components/shared/Calendar'; @@ -6,10 +7,37 @@ import { PlusIcon } from 'components/shared/CustomIcon'; import React, { useState } from 'react'; import { Value } from 'react-calendar/dist/cjs/shared/types'; +const avatarSizes: AvatarVariants['size'][] = [18, 20, 24, 28, 32, 36, 40, 44]; +const avatarVariants: AvatarVariants['type'][] = ['gray', 'orange', 'blue']; + const Page = () => { const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); + const avatars = avatarSizes.map((size) => { + return ( + + ); + }); + + const avatarsFallback = avatarVariants.map((color) => { + return avatarSizes.map((size) => { + return ( + + ); + }); + }); + return (
@@ -76,82 +104,97 @@ const Page = () => {
))}
-
-
+
-
-

Badge

-
- {['primary', 'secondary', 'tertiary', 'inset'].map( - (variant, index) => ( -
- {['sm', 'xs'].map((size) => ( - - 1 - - ))} -
- ), - )} -
-
- -
- -
-

Checkbox

-
- {Array.from({ length: 5 }).map((_, index) => ( - - ))} -
-
- {Array.from({ length: 2 }).map((_, index) => ( - - ))} -
-
- -
- -
-

Calendar

-
-
-

Selected date: {singleDate?.toString()}

- +
+

Badge

+
+ {['primary', 'secondary', 'tertiary', 'inset'].map( + (variant, index) => ( +
+ {['sm', 'xs'].map((size) => ( + + 1 + + ))} +
+ ), + )}
-
-

- Start date:{' '} - {dateRange instanceof Array ? dateRange[0]?.toString() : ''}{' '} -
- End date:{' '} - {dateRange instanceof Array ? dateRange[1]?.toString() : ''} -

- +
+ +
+ +
+

Checkbox

+
+ {Array.from({ length: 5 }).map((_, index) => ( + + ))} +
+
+ {Array.from({ length: 2 }).map((_, index) => ( + + ))} +
+
+ +
+ +
+

Calendar

+
+
+

Selected date: {singleDate?.toString()}

+ +
+
+

+ Start date:{' '} + {dateRange instanceof Array ? dateRange[0]?.toString() : ''}{' '} +
+ End date:{' '} + {dateRange instanceof Array ? dateRange[1]?.toString() : ''} +

+ +
+
+ +
+ + {/* Avatar */} +
+

Avatar

+
+ {avatars} + {avatarsFallback} +
diff --git a/yarn.lock b/yarn.lock index 42d230b9..87b34483 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.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.3.1": +"@babel/runtime@^7.10.4", "@babel/runtime@^7.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.13.10", "@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== @@ -3277,6 +3277,61 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@radix-ui/react-avatar@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623" + integrity sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + +"@radix-ui/react-compose-refs@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" + integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-context@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" + integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-primitive@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" + integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-slot" "1.0.2" + +"@radix-ui/react-slot@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" + integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + +"@radix-ui/react-use-callback-ref@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" + integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-use-layout-effect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" + integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" From b5eef95d157ca3f8bdd5c0261e30cbaabc4344dc Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 11:00:14 +0700 Subject: [PATCH 35/82] Merge remote-tracking branch 'origin/main' --- README.md | 4 +- packages/backend/environments/local.toml | 1 + packages/backend/package.json | 7 +- packages/backend/src/config.ts | 1 + packages/backend/src/database.ts | 33 ++- packages/backend/src/entity/Deployment.ts | 20 +- packages/backend/src/entity/Project.ts | 4 +- packages/backend/src/index.ts | 2 +- packages/backend/src/registry.ts | 38 +++- packages/backend/src/resolvers.ts | 2 +- packages/backend/src/schema.gql | 2 +- packages/backend/src/service.ts | 155 ++++++++++---- packages/backend/src/types.ts | 24 +++ .../backend/test/fixtures/deployments.json | 58 +++--- .../backend/test/fixtures/organizations.json | 4 +- .../test/fixtures/primary-domains.json | 12 +- packages/backend/test/fixtures/projects.json | 20 +- .../test/fixtures/redirected-domains.json | 6 +- ...nizations.json => user-organizations.json} | 0 packages/backend/test/fixtures/users.json | 12 +- packages/backend/test/initialize-db.ts | 2 +- .../backend/test/publish-deploy-records.ts | 77 +++++++ packages/frontend/src/assets/domains.json | 6 +- packages/frontend/src/assets/members.json | 17 -- .../frontend/src/assets/process-logs.json | 2 +- packages/frontend/src/assets/projects.json | 190 ------------------ .../projects/create/RepositoryList.tsx | 8 +- .../deployments/DeploymentDetailsCard.tsx | 12 +- .../deployments/DeploymentDialogBodyCard.tsx | 8 +- .../projects/project/settings/DomainCard.tsx | 7 +- .../project/settings/EditDomainDialog.tsx | 15 +- .../frontend/src/context/OctokitContext.tsx | 4 +- .../pages/org-slug/projects/create/index.tsx | 2 +- .../org-slug/projects/id/Deployments.tsx | 6 +- .../pages/org-slug/projects/id/Overview.tsx | 4 - .../org-slug/projects/id/settings/Domains.tsx | 34 +++- .../id/settings/EnvironmentVariables.tsx | 3 +- packages/frontend/src/types.ts | 9 - packages/gql-client/src/types.ts | 2 +- 39 files changed, 430 insertions(+), 383 deletions(-) rename packages/backend/test/fixtures/{user-orgnizations.json => user-organizations.json} (100%) create mode 100644 packages/backend/test/publish-deploy-records.ts delete mode 100644 packages/frontend/src/assets/members.json delete mode 100644 packages/frontend/src/assets/projects.json diff --git a/README.md b/README.md index 3accec13..c46cbbb3 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ - Load fixtures in database ```bash - yarn db:load:fixtures + yarn test:db:load:fixtures ``` - Set `gitHub.oAuth.clientId` and `gitHub.oAuth.clientSecret` in backend [config file](packages/backend/environments/local.toml) @@ -66,7 +66,7 @@ - Run the script to create bond, reserve the authority and set authority bond ```bash - yarn registry:init + yarn test:registry:init # snowball:initialize-registry bondId: 6af0ab81973b93d3511ae79841756fb5da3fd2f70ea1279e81fae7c9b19af6c4 +0ms ``` diff --git a/packages/backend/environments/local.toml b/packages/backend/environments/local.toml index 7ca5521f..b5b5d03a 100644 --- a/packages/backend/environments/local.toml +++ b/packages/backend/environments/local.toml @@ -13,6 +13,7 @@ clientSecret = "" [registryConfig] + fetchDeploymentRecordDelay = 5000 restEndpoint = "http://localhost:1317" gqlEndpoint = "http://localhost:9473/api" chainId = "laconic_9000-1" diff --git a/packages/backend/package.json b/packages/backend/package.json index 75ba8d67..83138f1d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -36,9 +36,10 @@ "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", - "registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts", - "db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts", - "db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts" + "test:registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts", + "test:registry:publish-deploy-records": "DEBUG=snowball:* ts-node ./test/publish-deploy-records.ts", + "test:db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts", + "test:db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts" }, "devDependencies": { "@types/fs-extra": "^11.0.4", diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 50b34145..1a98a657 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -22,6 +22,7 @@ export interface RegistryConfig { chainId: string; privateKey: string; bondId: string; + fetchDeploymentRecordDelay: number; fee: { amount: string; denom: string; diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 81c9fbd3..a45c354d 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -126,10 +126,18 @@ export class Database { return projects; } - async getDeploymentsByProjectId (projectId: string): Promise { + /** + * Get deployments with specified filter + */ + async getDeployments (options: FindManyOptions): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); + const deployments = await deploymentRepository.find(options); - const deployments = await deploymentRepository.find({ + return deployments; + } + + async getDeploymentsByProjectId (projectId: string): Promise { + return this.getDeployments({ relations: { project: true, domain: true, @@ -144,8 +152,6 @@ export class Database { createdAt: 'DESC' } }); - - return deployments; } async getDeployment (options: FindOneOptions): Promise { @@ -162,16 +168,14 @@ export class Database { return domains; } - async addDeployement (data: DeepPartial): Promise { + async addDeployment (data: DeepPartial): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const id = nanoid(); - const url = `${data.project!.name}-${id}.${PROJECT_DOMAIN}`; const updatedData = { ...data, - id, - url + id }; const deployment = await deploymentRepository.save(updatedData); @@ -312,6 +316,19 @@ export class Database { return Boolean(updateResult.affected); } + async updateDeploymentsByProjectIds (projectIds: string[], data: DeepPartial): Promise { + const deploymentRepository = this.dataSource.getRepository(Deployment); + + const updateResult = await deploymentRepository + .createQueryBuilder() + .update(Deployment) + .set(data) + .where('projectId IN (:...projectIds)', { projectIds }) + .execute(); + + return Boolean(updateResult.affected); + } + async addProject (userId: string, organizationId: string, data: DeepPartial): Promise { const projectRepository = this.dataSource.getRepository(Project); diff --git a/packages/backend/src/entity/Deployment.ts b/packages/backend/src/entity/Deployment.ts index 2ccb1c0b..62d3f38f 100644 --- a/packages/backend/src/entity/Deployment.ts +++ b/packages/backend/src/entity/Deployment.ts @@ -12,6 +12,7 @@ import { import { Project } from './Project'; import { Domain } from './Domain'; import { User } from './User'; +import { AppDeploymentRecordAttributes } from '../types'; export enum Environment { Production = 'Production', @@ -28,7 +29,7 @@ export enum DeploymentStatus { export interface ApplicationRecord { type: string; version:string - name?: string + name: string description?: string homepage?: string license?: string @@ -45,6 +46,9 @@ export class Deployment { @PrimaryColumn('varchar') id!: string; + @Column() + projectId!: string; + @ManyToOne(() => Project, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'projectId' }) project!: Project; @@ -65,14 +69,20 @@ export class Deployment { @Column('varchar') commitMessage!: string; - @Column('varchar') - url!: string; + @Column('varchar', { nullable: true }) + url!: string | null; @Column('varchar') - registryRecordId!: string; + applicationRecordId!: string; @Column('simple-json') - registryRecordData!: ApplicationRecord; + applicationRecordData!: ApplicationRecord; + + @Column('varchar', { nullable: true }) + applicationDeploymentRecordId!: string | null; + + @Column('simple-json', { nullable: true }) + applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null; @Column({ enum: Environment diff --git a/packages/backend/src/entity/Project.ts b/packages/backend/src/entity/Project.ts index 667d120c..86fa7b3d 100644 --- a/packages/backend/src/entity/Project.ts +++ b/packages/backend/src/entity/Project.ts @@ -53,10 +53,10 @@ export class Project { prodBranch!: string; @Column('varchar', { nullable: true }) - registryRecordId!: string | null; + applicationDeploymentRequestId!: string | null; @Column('simple-json', { nullable: true }) - registryRecordData!: ApplicationDeploymentRequest | null; + applicationDeploymentRequestData!: ApplicationDeploymentRequest | null; @Column('text', { default: '' }) description!: string; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d29f3c32..7a871a28 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -31,7 +31,7 @@ export const main = async (): Promise => { await db.init(); const registry = new Registry(registryConfig); - const service = new Service({ gitHubConfig: gitHub }, db, app, registry); + const service = new Service({ gitHubConfig: gitHub, registryConfig }, db, app, registry); const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString(); const resolvers = await createResolvers(service); diff --git a/packages/backend/src/registry.ts b/packages/backend/src/registry.ts index 5c17871a..108318bb 100644 --- a/packages/backend/src/registry.ts +++ b/packages/backend/src/registry.ts @@ -7,13 +7,14 @@ import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk'; import { RegistryConfig } from './config'; import { ApplicationDeploymentRequest } from './entity/Project'; -import { ApplicationRecord } from './entity/Deployment'; -import { PackageJSON } from './types'; +import { ApplicationRecord, Deployment } from './entity/Deployment'; +import { AppDeploymentRecord, PackageJSON } from './types'; const log = debug('snowball:registry'); const APP_RECORD_TYPE = 'ApplicationRecord'; -const DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRequest'; +const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest'; +const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord'; // TODO: Move registry code to laconic-sdk/watcher-ts export class Registry { @@ -35,7 +36,8 @@ export class Registry { commitHash: string, appType: string, repoUrl: string - }): Promise<{registryRecordId: string, registryRecordData: ApplicationRecord}> { + }): Promise<{applicationRecordId: string, applicationRecordData: ApplicationRecord}> { + assert(packageJSON.name, "name field doesn't exist in package.json"); // Use laconic-sdk to publish record // Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh // Fetch previous records @@ -58,7 +60,7 @@ export class Registry { repository_ref: commitHash, repository: [repoUrl], app_type: appType, - ...(packageJSON.name && { name: packageJSON.name }), + name: packageJSON.name, ...(packageJSON.description && { description: packageJSON.description }), ...(packageJSON.homepage && { homepage: packageJSON.homepage }), ...(packageJSON.license && { license: packageJSON.license }), @@ -79,14 +81,14 @@ export class Registry { log('Application record data:', applicationRecord); // TODO: Discuss computation of CRN - const crn = this.getCrn(packageJSON.name ?? ''); + const crn = this.getCrn(packageJSON.name); log(`Setting name: ${crn} for record ID: ${result.data.id}`); await this.registry.setName({ cid: result.data.id, crn }, this.registryConfig.privateKey, this.registryConfig.fee); await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee); await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.repository_ref}` }, this.registryConfig.privateKey, this.registryConfig.fee); - return { registryRecordId: result.data.id, registryRecordData: applicationRecord }; + return { applicationRecordId: result.data.id, applicationRecordData: applicationRecord }; } async createApplicationDeploymentRequest (data: { @@ -95,8 +97,8 @@ export class Registry { repository: string, environmentVariables: { [key: string]: string } }): Promise<{ - registryRecordId: string, - registryRecordData: ApplicationDeploymentRequest + applicationDeploymentRequestId: string, + applicationDeploymentRequestData: ApplicationDeploymentRequest }> { const crn = this.getCrn(data.appName); const records = await this.registry.resolveNames([crn]); @@ -108,7 +110,7 @@ export class Registry { // Create record of type ApplicationDeploymentRequest and publish const applicationDeploymentRequest = { - type: DEPLOYMENT_RECORD_TYPE, + type: APP_DEPLOYMENT_REQUEST_TYPE, version: '1.0.0', name: `${applicationRecord.attributes.name}@${applicationRecord.attributes.app_version}`, application: `${crn}@${applicationRecord.attributes.app_version}`, @@ -140,7 +142,21 @@ export class Registry { log(`Application deployment request record published: ${result.data.id}`); log('Application deployment request data:', applicationDeploymentRequest); - return { registryRecordId: result.data.id, registryRecordData: applicationDeploymentRequest }; + return { applicationDeploymentRequestId: result.data.id, applicationDeploymentRequestData: applicationDeploymentRequest }; + } + + /** + * Fetch ApplicationDeploymentRecords for deployments + */ + async getDeploymentRecords (deployments: Deployment[]): Promise { + // Fetch ApplicationDeploymentRecords for corresponding ApplicationRecord set in deployments + // TODO: Implement Laconicd GQL query to filter records by multiple values for an attribute + const records = await this.registry.queryRecords({ + type: APP_DEPLOYMENT_RECORD_TYPE + }, true); + + // Filter records with ApplicationRecord ids + return records.filter((record: AppDeploymentRecord) => deployments.some(deployment => deployment.applicationRecordId === record.attributes.application)); } getCrn (packageJsonName: string): string { diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 02602aae..f583bdab 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -30,7 +30,7 @@ export const createResolvers = async (service: Service): Promise => { }, deployments: async (_: any, { projectId }: { projectId: string }) => { - return service.getDeployementsByProjectId(projectId); + return service.getDeploymentsByProjectId(projectId); }, environmentVariables: async (_: any, { projectId }: { projectId: string }) => { diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 8d048078..620dec72 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -91,7 +91,7 @@ type Deployment { branch: String! commitHash: String! commitMessage: String! - url: String! + url: String environment: Environment! isCurrent: Boolean! status: DeploymentStatus! diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 8edb63a0..0ce326ff 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -14,14 +14,16 @@ import { Project } from './entity/Project'; import { Permission, ProjectMember } from './entity/ProjectMember'; import { User } from './entity/User'; import { Registry } from './registry'; -import { GitHubConfig } from './config'; -import { GitPushEventPayload } from './types'; +import { GitHubConfig, RegistryConfig } from './config'; +import { AppDeploymentRecord, GitPushEventPayload } from './types'; const log = debug('snowball:service'); + const GITHUB_UNIQUE_WEBHOOK_ERROR = 'Hook already exists on this repository'; interface Config { gitHubConfig: GitHubConfig + registryConfig: RegistryConfig } export class Service { @@ -30,11 +32,112 @@ export class Service { private registry: Registry; private config: Config; + private deployRecordCheckTimeout?: NodeJS.Timeout; + constructor (config: Config, db: Database, app: OAuthApp, registry: Registry) { this.db = db; this.oauthApp = app; this.registry = registry; this.config = config; + this.init(); + } + + /** + * Initialize services + */ + init (): void { + // Start check for ApplicationDeploymentRecords asynchronously + this.checkDeployRecordsAndUpdate(); + } + + /** + * Destroy services + */ + destroy (): void { + clearTimeout(this.deployRecordCheckTimeout); + } + + /** + * Checks for ApplicationDeploymentRecord and update corresponding deployments + * Continues check in loop after a delay of DEPLOY_RECORD_CHECK_DELAY_MS + */ + async checkDeployRecordsAndUpdate (): Promise { + // Fetch deployments in building state + const deployments = await this.db.getDeployments({ + where: { + status: DeploymentStatus.Building + // TODO: Fetch and check records for recent deployments + } + }); + + if (deployments.length) { + log(`Found ${deployments.length} deployments in ${DeploymentStatus.Building} state`); + + // Fetch ApplicationDeploymentRecord for deployments + const records = await this.registry.getDeploymentRecords(deployments); + log(`Found ${records.length} ApplicationDeploymentRecords`); + + // Update deployments for which ApplicationDeploymentRecords were returned + if (records.length) { + await this.updateDeploymentsWithRecordData(records); + } + } + + this.deployRecordCheckTimeout = setTimeout(() => { + this.checkDeployRecordsAndUpdate(); + }, this.config.registryConfig.fetchDeploymentRecordDelay); + } + + /** + * Update deployments with ApplicationDeploymentRecord data + */ + async updateDeploymentsWithRecordData (records: AppDeploymentRecord[]): Promise { + // Get deployments for ApplicationDeploymentRecords + const deployments = await this.db.getDeployments({ + where: records.map(record => ({ + applicationRecordId: record.attributes.application + })), + order: { + createdAt: 'DESC' + } + }); + + // Get project IDs of deployments that are in production environment + const productionDeploymentProjectIds = deployments.reduce((acc, deployment): Set => { + if (deployment.environment === Environment.Production) { + acc.add(deployment.projectId); + } + + return acc; + }, new Set()); + + // Set old deployments isCurrent to false + await this.db.updateDeploymentsByProjectIds(Array.from(productionDeploymentProjectIds), { isCurrent: false }); + + const recordToDeploymentsMap = deployments.reduce((acc: {[key: string]: Deployment}, deployment) => { + acc[deployment.applicationRecordId] = deployment; + return acc; + }, {}); + + // Update deployment data for ApplicationDeploymentRecords + const deploymentUpdatePromises = records.map(async (record) => { + const deployment = recordToDeploymentsMap[record.attributes.application]; + + await this.db.updateDeploymentById( + deployment.id, + { + applicationDeploymentRecordId: record.id, + applicationDeploymentRecordData: record.attributes, + url: record.attributes.url, + status: DeploymentStatus.Ready, + isCurrent: deployment.environment === Environment.Production + } + ); + + log(`Updated deployment ${deployment.id} with URL ${record.attributes.url}`); + }); + + await Promise.all(deploymentUpdatePromises); } async getUser (userId: string): Promise { @@ -67,7 +170,7 @@ export class Service { return dbProjects; } - async getDeployementsByProjectId (projectId: string): Promise { + async getDeploymentsByProjectId (projectId: string): Promise { const dbDeployments = await this.db.getDeploymentsByProjectId(projectId); return dbDeployments; } @@ -187,11 +290,10 @@ export class Service { const octokit = await this.getOctokit(userId); - const newDeployement = await this.createDeployment(userId, + const newDeployment = await this.createDeployment(userId, octokit, { project: oldDeployment.project, - isCurrent: true, branch: oldDeployment.branch, environment: Environment.Production, domain: prodBranchDomains[0], @@ -199,7 +301,7 @@ export class Service { commitMessage: oldDeployment.commitMessage }); - return newDeployement; + return newDeployment; } async createDeployment ( @@ -232,24 +334,13 @@ export class Service { } // TODO: Set environment variables for each deployment (environment variables can`t be set in application record) - const { registryRecordId, registryRecordData } = await this.registry.createApplicationRecord({ + const { applicationRecordId, applicationRecordData } = await this.registry.createApplicationRecord({ packageJSON, appType: data.project!.template!, commitHash: data.commitHash!, repoUrl: recordData.repoUrl }); - // Check if new deployment is set to current - if (data.isCurrent) { - // Update previous current deployment - await this.db.updateDeployment({ - project: { - id: data.project.id - }, - isCurrent: true - }, { isCurrent: false }); - } - // Update previous deployment with prod branch domain // TODO: Fix unique constraint error for domain await this.db.updateDeployment({ @@ -258,24 +349,23 @@ export class Service { domain: null }); - const newDeployement = await this.db.addDeployement({ + const newDeployment = await this.db.addDeployment({ project: data.project, branch: data.branch, commitHash: data.commitHash, commitMessage: data.commitMessage, environment: data.environment, - isCurrent: data.isCurrent, status: DeploymentStatus.Building, - registryRecordId, - registryRecordData, + applicationRecordId, + applicationRecordData, domain: data.domain, createdBy: Object.assign(new User(), { id: userId }) }); - log(`Created deployment ${newDeployement.id} and published application record ${registryRecordId}`); - return newDeployement; + log(`Created deployment ${newDeployment.id} and published application record ${applicationRecordId}`); + return newDeployment; } async addProject (userId: string, organizationSlug: string, data: DeepPartial): Promise { @@ -307,7 +397,6 @@ export class Service { octokit, { project, - isCurrent: true, branch: project.prodBranch, environment: Environment.Production, domain: null, @@ -327,17 +416,17 @@ export class Service { return acc; }, {} as { [key: string]: string }); - const { registryRecordId, registryRecordData } = await this.registry.createApplicationDeploymentRequest( + const { applicationDeploymentRequestId, applicationDeploymentRequestData } = await this.registry.createApplicationDeploymentRequest( { - appName: newDeployment.registryRecordData.name!, + appName: newDeployment.applicationRecordData.name!, commitHash: latestCommit.sha, repository: repoDetails.html_url, environmentVariables: environmentVariablesObj }); await this.db.updateProjectById(project.id, { - registryRecordId, - registryRecordData + applicationDeploymentRequestId, + applicationDeploymentRequestData }); await this.createRepoHook(octokit, project); @@ -393,7 +482,6 @@ export class Service { octokit, { project, - isCurrent: project.prodBranch === branch, branch, environment: project.prodBranch === branch ? Environment.Production : Environment.Preview, domain, @@ -444,20 +532,19 @@ export class Service { const octokit = await this.getOctokit(userId); - const newDeployement = await this.createDeployment(userId, + const newDeployment = await this.createDeployment(userId, octokit, { project: oldDeployment.project, // TODO: Put isCurrent field in project branch: oldDeployment.branch, - isCurrent: true, environment: Environment.Production, domain: oldDeployment.domain, commitHash: oldDeployment.commitHash, commitMessage: oldDeployment.commitMessage }); - return newDeployement; + return newDeployment; } async rollbackDeployment (projectId: string, deploymentId: string): Promise { @@ -475,7 +562,7 @@ export class Service { }); if (!oldCurrentDeployment) { - throw new Error('Current deployement doesnot exist'); + throw new Error('Current deployment doesnot exist'); } const oldCurrentDeploymentUpdate = await this.db.updateDeploymentById(oldCurrentDeployment.id, { isCurrent: false, domain: null }); diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 9fb154cb..71626645 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -25,3 +25,27 @@ export interface GitPushEventPayload { message: string; }; } + +export interface AppDeploymentRecordAttributes { + application: string; + dns: string; + meta: string; + name: string; + request: string; + type: string; + url: string; + version: string; +} + +interface RegistryRecord { + id: string; + names: string[] | null; + owners: string[]; + bondId: string; + createTime: string; + expiryTime: string; +} + +export interface AppDeploymentRecord extends RegistryRecord { + attributes: AppDeploymentRecordAttributes +} diff --git a/packages/backend/test/fixtures/deployments.json b/packages/backend/test/fixtures/deployments.json index e0900921..5eb682a8 100644 --- a/packages/backend/test/fixtures/deployments.json +++ b/packages/backend/test/fixtures/deployments.json @@ -4,11 +4,11 @@ "domainIndex":0, "createdByIndex": 0, "id":"ffhae3zq", - "status": "Building", + "status": "Ready", "environment": "Production", "isCurrent": true, - "registryRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationRecordData": {}, "branch": "main", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -22,8 +22,8 @@ "status": "Ready", "environment": "Preview", "isCurrent": false, - "registryRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi", + "applicationRecordData": {}, "branch": "test", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -34,11 +34,11 @@ "domainIndex":2, "createdByIndex": 0, "id":"qmgekyte", - "status": "Error", + "status": "Ready", "environment": "Development", "isCurrent": false, - "registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", + "applicationRecordData": {}, "branch": "test", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -52,8 +52,8 @@ "status": "Ready", "environment": "Production", "isCurrent": false, - "registryRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi", + "applicationRecordData": {}, "branch": "prod", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -64,11 +64,11 @@ "domainIndex":3, "createdByIndex": 1, "id":"eO8cckxk", - "status": "Building", + "status": "Ready", "environment": "Production", "isCurrent": true, - "registryRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationRecordData": {}, "branch": "main", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -82,8 +82,8 @@ "status": "Ready", "environment": "Preview", "isCurrent": false, - "registryRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", + "applicationRecordData": {}, "branch": "test", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -94,11 +94,11 @@ "domainIndex":5, "createdByIndex": 1, "id":"hwwr6sbx", - "status": "Error", + "status": "Ready", "environment": "Development", "isCurrent": false, - "registryRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi", + "applicationRecordData": {}, "branch": "test", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -109,11 +109,11 @@ "domainIndex":9, "createdByIndex": 2, "id":"ndxje48a", - "status": "Building", + "status": "Ready", "environment": "Production", "isCurrent": true, - "registryRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationRecordData": {}, "branch": "main", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -127,8 +127,8 @@ "status": "Ready", "environment": "Preview", "isCurrent": false, - "registryRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationRecordData": {}, "branch": "test", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -139,11 +139,11 @@ "domainIndex":8, "createdByIndex": 2, "id":"b4bpthjr", - "status": "Error", + "status": "Ready", "environment": "Development", "isCurrent": false, - "registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", - "registryRecordData": {}, + "applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", + "applicationRecordData": {}, "branch": "test", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", @@ -154,11 +154,11 @@ "domainIndex": 6, "createdByIndex": 2, "id":"b4bpthjr", - "status": "Building", + "status": "Ready", "environment": "Production", "isCurrent": true, - "registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", - "registryRecordData": {}, + "applicationRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", + "applicationRecordData": {}, "branch": "test", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00", "commitMessage": "subscription added", diff --git a/packages/backend/test/fixtures/organizations.json b/packages/backend/test/fixtures/organizations.json index bf934e81..ace516d3 100644 --- a/packages/backend/test/fixtures/organizations.json +++ b/packages/backend/test/fixtures/organizations.json @@ -6,7 +6,7 @@ }, { "id": "7eb9b3eb-eb74-4b53-b59a-69884c82a7fb", - "name": "AirFoil", - "slug": "airfoil-2" + "name": "Laconic", + "slug": "laconic-2" } ] diff --git a/packages/backend/test/fixtures/primary-domains.json b/packages/backend/test/fixtures/primary-domains.json index b1cb0a99..23e2666d 100644 --- a/packages/backend/test/fixtures/primary-domains.json +++ b/packages/backend/test/fixtures/primary-domains.json @@ -1,37 +1,37 @@ [ { "projectIndex": 0, - "name": "randomurl.snowballtools.xyz", + "name": "example.snowballtools.xyz", "status": "Live", "branch": "main" }, { "projectIndex": 0, - "name": "saugatt.com", + "name": "example.org", "status": "Pending", "branch": "test" }, { "projectIndex": 1, - "name": "randomurl.snowballtools.xyz", + "name": "example.snowballtools.xyz", "status": "Live", "branch": "main" }, { "projectIndex": 1, - "name": "saugatt.com", + "name": "example.org", "status": "Pending", "branch": "test" }, { "projectIndex": 2, - "name": "randomurl.snowballtools.xyz", + "name": "example.snowballtools.xyz", "status": "Live", "branch": "main" }, { "projectIndex": 2, - "name": "saugatt.com", + "name": "example.org", "status": "Pending", "branch": "test" }, diff --git a/packages/backend/test/fixtures/projects.json b/packages/backend/test/fixtures/projects.json index f2def3f2..55c5ee23 100644 --- a/packages/backend/test/fixtures/projects.json +++ b/packages/backend/test/fixtures/projects.json @@ -10,8 +10,8 @@ "framework": "test", "webhooks": [], "icon": "", - "registryRecordId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationDeploymentRequestId": "hbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationDeploymentRequestData": {}, "subDomain": "testProject.snowball.xyz" }, { @@ -25,8 +25,8 @@ "framework": "test-2", "webhooks": [], "icon": "", - "registryRecordId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationDeploymentRequestId": "gbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationDeploymentRequestData": {}, "subDomain": "testProject-2.snowball.xyz" }, { @@ -40,8 +40,8 @@ "framework": "test-3", "webhooks": [], "icon": "", - "registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationDeploymentRequestId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationDeploymentRequestData": {}, "subDomain": "iglootools.snowball.xyz" }, { @@ -55,8 +55,8 @@ "framework": "test-4", "webhooks": [], "icon": "", - "registryRecordId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationDeploymentRequestId": "qbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationDeploymentRequestData": {}, "subDomain": "iglootools-2.snowball.xyz" }, { @@ -70,8 +70,8 @@ "framework": "test-5", "webhooks": [], "icon": "", - "registryRecordId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", - "registryRecordData": {}, + "applicationDeploymentRequestId": "xbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", + "applicationDeploymentRequestData": {}, "subDomain": "snowball-2.snowball.xyz" } ] diff --git a/packages/backend/test/fixtures/redirected-domains.json b/packages/backend/test/fixtures/redirected-domains.json index d526bde3..64fa9736 100644 --- a/packages/backend/test/fixtures/redirected-domains.json +++ b/packages/backend/test/fixtures/redirected-domains.json @@ -1,21 +1,21 @@ [ { "projectIndex": 0, - "name": "www.saugatt.com", + "name": "www.example.org", "status": "Pending", "redirectToIndex": 1, "branch": "test" }, { "projectIndex": 1, - "name": "www.saugatt.com", + "name": "www.example.org", "status": "Pending", "redirectToIndex": 3, "branch": "test" }, { "projectIndex": 2, - "name": "www.saugatt.com", + "name": "www.example.org", "status": "Pending", "redirectToIndex": 5, "branch": "test" diff --git a/packages/backend/test/fixtures/user-orgnizations.json b/packages/backend/test/fixtures/user-organizations.json similarity index 100% rename from packages/backend/test/fixtures/user-orgnizations.json rename to packages/backend/test/fixtures/user-organizations.json diff --git a/packages/backend/test/fixtures/users.json b/packages/backend/test/fixtures/users.json index aacd7b6b..118beba5 100644 --- a/packages/backend/test/fixtures/users.json +++ b/packages/backend/test/fixtures/users.json @@ -1,20 +1,20 @@ [ { "id": "59f4355d-9549-4aac-9b54-eeefceeabef0", - "name": "Saugat Yadav", - "email": "saugaty@airfoil.studio", + "name": "Snowball", + "email": "snowball@snowballtools.xyz", "isVerified": true }, { "id": "e505b212-8da6-48b2-9614-098225dab34b", - "name": "Gideon Low", - "email": "gideonl@airfoil.studio", + "name": "Alice Anderson", + "email": "alice@snowballtools.xyz", "isVerified": true }, { "id": "cd892fad-9138-4aa2-a62c-414a32776ea7", - "name": "Sushan Yadav", - "email": "sushany@airfoil.studio", + "name": "Bob Banner", + "email": "bob@snowballtools.xyz", "isVerified": true } ] diff --git a/packages/backend/test/initialize-db.ts b/packages/backend/test/initialize-db.ts index fdfeeedb..0aade587 100644 --- a/packages/backend/test/initialize-db.ts +++ b/packages/backend/test/initialize-db.ts @@ -20,7 +20,7 @@ const log = debug('snowball:initialize-database'); const USER_DATA_PATH = './fixtures/users.json'; const PROJECT_DATA_PATH = './fixtures/projects.json'; const ORGANIZATION_DATA_PATH = './fixtures/organizations.json'; -const USER_ORGANIZATION_DATA_PATH = './fixtures/user-orgnizations.json'; +const USER_ORGANIZATION_DATA_PATH = './fixtures/user-organizations.json'; const PROJECT_MEMBER_DATA_PATH = './fixtures/project-members.json'; const PRIMARY_DOMAIN_DATA_PATH = './fixtures/primary-domains.json'; const DEPLOYMENT_DATA_PATH = './fixtures/deployments.json'; diff --git a/packages/backend/test/publish-deploy-records.ts b/packages/backend/test/publish-deploy-records.ts new file mode 100644 index 00000000..f5636c4e --- /dev/null +++ b/packages/backend/test/publish-deploy-records.ts @@ -0,0 +1,77 @@ +import debug from 'debug'; +import { DataSource } from 'typeorm'; +import path from 'path'; + +import { Registry } from '@cerc-io/laconic-sdk'; + +import { Config } from '../src/config'; +import { DEFAULT_CONFIG_FILE_PATH, PROJECT_DOMAIN } from '../src/constants'; +import { getConfig } from '../src/utils'; +import { Deployment, DeploymentStatus } from '../src/entity/Deployment'; + +const log = debug('snowball:publish-deploy-records'); + +async function main () { + const { registryConfig, database } = await getConfig(DEFAULT_CONFIG_FILE_PATH); + + const registry = new Registry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId); + + const dataSource = new DataSource({ + type: 'better-sqlite3', + database: database.dbPath, + synchronize: true, + entities: [path.join(__dirname, '../src/entity/*')] + }); + + await dataSource.initialize(); + + const deploymentRepository = dataSource.getRepository(Deployment); + const deployments = await deploymentRepository.find({ + relations: { + project: true + }, + where: { + status: DeploymentStatus.Building + } + }); + + for await (const deployment of deployments) { + const url = `${deployment.project.name}-${deployment.id}.${PROJECT_DOMAIN}`; + + const applicationDeploymentRecord = { + type: 'ApplicationDeploymentRecord', + version: '0.0.1', + name: deployment.applicationRecordData.name, + application: deployment.applicationRecordId, + + // TODO: Create DNS record + dns: 'bafyreihlymqggsgqiqawvehkpr2imt4l3u6q7um7xzjrux5rhsvwnuyewm', + + // Using dummy values + meta: JSON.stringify({ + config: 'da39a3ee5e6b4b0d3255bfef95601890afd80709', + so: '66fcfa49a1664d4cb4ce4f72c1c0e151' + }), + + request: deployment.project.applicationDeploymentRequestId, + url + }; + + const result = await registry.setRecord( + { + privateKey: registryConfig.privateKey, + record: applicationDeploymentRecord, + bondId: registryConfig.bondId + }, + '', + registryConfig.fee + ); + + log('Application deployment record data:', applicationDeploymentRecord); + log(`Application deployment record published: ${result.data.id}`); + } +} + +main().catch((err) => { + log(err); +}); diff --git a/packages/frontend/src/assets/domains.json b/packages/frontend/src/assets/domains.json index e17d1738..11020b61 100644 --- a/packages/frontend/src/assets/domains.json +++ b/packages/frontend/src/assets/domains.json @@ -2,7 +2,7 @@ { "id": 1, "projectid": 1, - "name": "randomurl.snowballtools.xyz", + "name": "example.snowballtools.xyz", "status": "live", "record": null, "isRedirectedto": false @@ -10,7 +10,7 @@ { "id": 2, "projectid": 1, - "name": "saugatt.com", + "name": "example.org", "status": "pending", "record": { "type": "A", @@ -22,7 +22,7 @@ { "id": 3, "projectid": 1, - "name": "www.saugatt.com", + "name": "www.example.org", "status": "pending", "record": { "type": "CNAME", diff --git a/packages/frontend/src/assets/members.json b/packages/frontend/src/assets/members.json deleted file mode 100644 index 6e38602c..00000000 --- a/packages/frontend/src/assets/members.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "name": "Saugat Yadav", - "email": "saugaty@airfoil.studio", - "id": 1 - }, - { - "name": "Gideon Low", - "email": "gideonl@airfoil.studio", - "id": 2 - }, - { - "name": "Sushan Yadav", - "email": "sushany@airfoil.studio", - "id": 3 - } -] diff --git a/packages/frontend/src/assets/process-logs.json b/packages/frontend/src/assets/process-logs.json index 34c7e24b..097a0fa9 100644 --- a/packages/frontend/src/assets/process-logs.json +++ b/packages/frontend/src/assets/process-logs.json @@ -1,6 +1,6 @@ [ "[20:50:03.502] Running build in Washington, D.C., USA (East) – iad1", - "[20:50:03.641] Cloning github.com/saugatyadav11/nextjs2 (Branch: main, Commit: 4a5f47f)", + "[20:50:03.641] Cloning github.com/cerc-io/nextjs2 (Branch: main, Commit: 4a5f47f)", "[20:50:04.004] Previous build cache not available", "[20:50:04.118] Cloning completed: 480.574ms", "[20:50:04.382] Running 'vercel build'", diff --git a/packages/frontend/src/assets/projects.json b/packages/frontend/src/assets/projects.json deleted file mode 100644 index a344fdf7..00000000 --- a/packages/frontend/src/assets/projects.json +++ /dev/null @@ -1,190 +0,0 @@ -[ - { - "id": 1, - "icon": "^", - "name": "iglootools", - "title": "Iglootools", - "domain": null, - "organization": "Airfoil", - "url": "iglootools.co", - "createdAt": "2023-12-07T04:20:00", - "createdBy": "Alice", - "deployment": "iglootools.snowballtools.co", - "source": "feature/add-remote-control", - "latestCommit": { - "message": "subscription added", - "createdAt": "2023-12-11T04:20:00", - "branch": "main" - }, - "repositoryId": 1, - "members": [ - { - "id": 1, - "permissions": [] - }, - { - "id": 2, - "permissions": ["view", "edit"] - }, - { - "id": 3, - "permissions": ["view"] - } - ], - "ownerId": 1 - }, - { - "id": 2, - "icon": "^", - "name": "snowball-starter-kit", - "title": "Snowball Starter Kit", - "domain": null, - "organization": "Snowball", - "url": "starterkit.snowballtools.com", - "createdAt": "2023-12-04T04:20:00", - "createdBy": "Bob", - "deployment": "deploy.snowballtools.com", - "source": "prod/add-docker-compose", - "latestCommit": { - "message": "component updates", - "createdAt": "2023-12-11T04:20:00", - "branch": "staging" - }, - "repositoryId": 1, - "members": [ - { - "id": 2, - "permissions": [] - }, - { - "id": 3, - "permissions": ["view"] - } - ], - "ownerId": 2 - }, - { - "id": 3, - "icon": "^", - "name": "web3-android", - "title": "Web3 Android", - "domain": null, - "organization": "Personal", - "url": "web3fordroids.com", - "createdAt": "2023-12-01T04:20:00", - "createdBy": "Charlie", - "deployment": "deploy.web3fordroids.com", - "source": "dev/style-page", - "latestCommit": { - "message": "No repo connected", - "createdAt": "2023-12-01T04:20:00", - "branch": "main" - }, - "repositoryId": 1, - "members": [ - { - "id": 1, - "permissions": [] - }, - { - "id": 2, - "permissions": ["view", "edit"] - }, - { - "id": 3, - "permissions": ["view"] - } - ], - "ownerId": 1 - }, - { - "id": 4, - "icon": "^", - "name": "passkeys-demo", - "title": "Passkeys Demo", - "domain": null, - "organization": "Airfoil", - "url": "passkeys.iglootools.xyz", - "createdAt": "2023-12-01T04:20:00", - "createdBy": "David", - "deployment": "demo.passkeys.xyz", - "source": "dev/style-page", - "latestCommit": { - "message": "hello world", - "createdAt": "2023-12-01T04:20:00", - "branch": "main" - }, - "repositoryId": 1, - "members": [ - { - "id": 1, - "permissions": [] - }, - { - "id": 2, - "permissions": ["view", "edit"] - }, - { - "id": 3, - "permissions": ["view"] - } - ], - "ownerId": 1 - }, - { - "id": 5, - "icon": "^", - "name": "iglootools", - "title": "Iglootools", - "domain": null, - "organization": "Airfoil", - "url": "iglootools.xyz", - "createdAt": "2023-12-11T04:20:00", - "createdBy": "Erin", - "deployment": "staging.snowballtools.com", - "source": "prod/fix-error", - "latestCommit": { - "message": "404 added", - "createdAt": "2023-12-09T04:20:00", - "branch": "main" - }, - "repositoryId": 1, - "members": [ - { - "id": 3, - "permissions": [] - } - ], - "ownerId": 3 - }, - { - "id": 6, - "icon": "^", - "name": "iglootools", - "title": "Iglootools", - "domain": null, - "organization": "Airfoil", - "url": "iglootools.xyz", - "createdAt": "2023-12-11T04:20:00", - "createdBy": "Frank", - "deployment": "iglootools.snowballtools.com", - "source": "prod/fix-error", - "latestCommit": { - "message": "design system integrated", - "createdAt": "2023-12-09T04:20:00", - "branch": "prod" - }, - "repositoryId": 1, - "members": [ - { - "id": 2, - "permissions": [] - }, - { - "id": 3, - "permissions": ["view"] - } - ], - "ownerId": 2 - } -] diff --git a/packages/frontend/src/components/projects/create/RepositoryList.tsx b/packages/frontend/src/components/projects/create/RepositoryList.tsx index ad69e776..d0f360bb 100644 --- a/packages/frontend/src/components/projects/create/RepositoryList.tsx +++ b/packages/frontend/src/components/projects/create/RepositoryList.tsx @@ -3,11 +3,12 @@ import { Octokit } from 'octokit'; import assert from 'assert'; import { useDebounce } from 'usehooks-ts'; -import { Button, Typography, Option, Select } from '@material-tailwind/react'; +import { Button, Typography, Option } from '@material-tailwind/react'; import SearchBar from '../../SearchBar'; import ProjectRepoCard from './ProjectRepoCard'; import { GitOrgDetails, GitRepositoryDetails } from '../../../types'; +import AsyncSelect from '../../shared/AsyncSelect'; const DEFAULT_SEARCHED_REPO = ''; const REPOS_PER_PAGE = 5; @@ -109,8 +110,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
- {/* TODO: Fix selection of Git user at start */} - +
- {deployment.url} + {deployment.url && ( + {deployment.url} + )} ... - - ^ Visit + + ^ Visit setAssignDomainDialog(!assignDomainDialog)} diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx index 65a5a4b6..e4388bf8 100644 --- a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx +++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx @@ -28,9 +28,11 @@ const DeploymentDialogBodyCard = ({ color={chip.color} /> )} - - {deployment.url} - + {deployment.url && ( + + {deployment.url} + + )} ^ {deployment.branch} ^{' '} {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '} diff --git a/packages/frontend/src/components/projects/project/settings/DomainCard.tsx b/packages/frontend/src/components/projects/project/settings/DomainCard.tsx index 09eba5e0..4cfd4967 100644 --- a/packages/frontend/src/components/projects/project/settings/DomainCard.tsx +++ b/packages/frontend/src/components/projects/project/settings/DomainCard.tsx @@ -12,7 +12,6 @@ import { Card, } from '@material-tailwind/react'; -import { RepositoryDetails } from '../../../../types'; import ConfirmDialog from '../../../shared/ConfirmDialog'; import EditDomainDialog from './EditDomainDialog'; import { useGQLClient } from '../../../../context/GQLClientContext'; @@ -27,7 +26,7 @@ enum RefreshStatus { interface DomainCardProps { domains: Domain[]; domain: Domain; - repo: RepositoryDetails; + branches: string[]; project: Project; onUpdate: () => Promise; } @@ -44,7 +43,7 @@ const DOMAIN_RECORD = { const DomainCard = ({ domains, domain, - repo, + branches, project, onUpdate, }: DomainCardProps) => { @@ -188,7 +187,7 @@ const DomainCard = ({ domains={domains} open={editDialogOpen} domain={domain} - repo={repo} + branches={branches} onUpdate={onUpdate} /> diff --git a/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx b/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx index 78e4aceb..b9d2866c 100644 --- a/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx +++ b/packages/frontend/src/components/projects/project/settings/EditDomainDialog.tsx @@ -15,7 +15,6 @@ import { Option, } from '@material-tailwind/react'; -import { RepositoryDetails } from '../../../../types'; import { useGQLClient } from '../../../../context/GQLClientContext'; const DEFAULT_REDIRECT_OPTIONS = ['none']; @@ -25,7 +24,7 @@ interface EditDomainDialogProp { open: boolean; handleOpen: () => void; domain: Domain; - repo: RepositoryDetails; + branches: string[]; onUpdate: () => Promise; } @@ -40,7 +39,7 @@ const EditDomainDialog = ({ open, handleOpen, domain, - repo, + branches, onUpdate, }: EditDomainDialogProp) => { const client = useGQLClient(); @@ -120,7 +119,7 @@ const EditDomainDialog = ({ branch: domain.branch, redirectedTo: getRedirectUrl(domain), }); - }, [domain, repo]); + }, [domain]); return ( @@ -166,9 +165,13 @@ const EditDomainDialog = ({ repo.branch.includes(value), + validate: (value) => + Boolean(branches.length) ? branches.includes(value) : true, })} - disabled={watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]} + disabled={ + !Boolean(branches.length) || + watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0] + } /> {!isValid && ( diff --git a/packages/frontend/src/context/OctokitContext.tsx b/packages/frontend/src/context/OctokitContext.tsx index 0eed37ce..21ffe246 100644 --- a/packages/frontend/src/context/OctokitContext.tsx +++ b/packages/frontend/src/context/OctokitContext.tsx @@ -14,13 +14,13 @@ import { useGQLClient } from './GQLClientContext'; const UNAUTHORIZED_ERROR_CODE = 401; interface ContextValue { - octokit: Octokit | null; + octokit: Octokit; isAuth: boolean; updateAuth: () => void; } const OctokitContext = createContext({ - octokit: null, + octokit: new Octokit(), isAuth: false, updateAuth: () => {}, }); diff --git a/packages/frontend/src/pages/org-slug/projects/create/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/index.tsx index 8658d18c..a48f4dd4 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/index.tsx @@ -24,7 +24,7 @@ const NewProject = () => { })}
Import a repository
- + ) : ( diff --git a/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx b/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx index 7932f83f..958382ac 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx @@ -66,8 +66,10 @@ const DeploymentsTabPanel = () => { const dateMatch = !filterValue.updateAtRange || - (new Date(deployment.updatedAt) >= filterValue.updateAtRange!.from! && - new Date(deployment.updatedAt) <= filterValue.updateAtRange!.to!); + (new Date(Number(deployment.createdAt)) >= + filterValue.updateAtRange!.from! && + new Date(Number(deployment.createdAt)) <= + filterValue.updateAtRange!.to!); return branchMatch && statusMatch && dateMatch; }); diff --git a/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx b/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx index c5db5c34..a5f1ad73 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx @@ -24,10 +24,6 @@ const OverviewTabPanel = () => { const { project } = useOutletContext(); useEffect(() => { - if (!octokit) { - return; - } - // TODO: Save repo commits in DB and avoid using GitHub API in frontend // TODO: Figure out fetching latest commits for all branches const fetchRepoActivity = async () => { diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/Domains.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/Domains.tsx index f912e8fa..24457b1a 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/Domains.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/Domains.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useState } from 'react'; +import { RequestError } from 'octokit'; +import React, { useCallback, useEffect, useState } from 'react'; import { Link, useOutletContext } from 'react-router-dom'; import { Domain } from 'gql-client'; @@ -6,14 +7,41 @@ import { Button, Typography } from '@material-tailwind/react'; import DomainCard from '../../../../../components/projects/project/settings/DomainCard'; import { useGQLClient } from '../../../../../context/GQLClientContext'; -import repositories from '../../../../../assets/repositories.json'; import { OutletContextType } from '../../../../../types'; +import { useOctokit } from '../../../../../context/OctokitContext'; const Domains = () => { const client = useGQLClient(); + const { octokit } = useOctokit(); const { project } = useOutletContext(); const [domains, setDomains] = useState([]); + const [branches, setBranches] = useState([]); + + const fetchBranches = useCallback(async () => { + const [owner, repo] = project.repository.split('/'); + + try { + const result = await octokit.rest.repos.listBranches({ + owner, + repo, + }); + + const branches = result.data.map((repo) => repo.name); + + setBranches(branches); + } catch (err) { + if (!(err instanceof RequestError && err.status === 404)) { + throw err; + } + + console.error(err); + } + }, []); + + useEffect(() => { + fetchBranches(); + }, []); const fetchDomains = async () => { if (project === undefined) { @@ -46,7 +74,7 @@ const Domains = () => { domain={domain} key={domain.id} // TODO: Use github API for getting linked repository - repo={repositories[0]!} + branches={branches} project={project} onUpdate={fetchDomains} /> diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx index 0ae3932b..c0eb59b9 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx @@ -171,7 +171,8 @@ export const EnvironmentVariablesTabPanel = () => { > + Add variable -
diff --git a/packages/frontend/src/types.ts b/packages/frontend/src/types.ts index 1ece61d3..2ca22da6 100644 --- a/packages/frontend/src/types.ts +++ b/packages/frontend/src/types.ts @@ -6,15 +6,6 @@ export interface GitOrgDetails { avatar_url: string; } -// TODO: Use GitRepositoryDetails -export interface RepositoryDetails { - title: string; - updatedAt: string; - user: string; - private: boolean; - branch: string[]; -} - export interface GitRepositoryDetails { id: number; name: string; diff --git a/packages/gql-client/src/types.ts b/packages/gql-client/src/types.ts index e75d0aba..71c44c00 100644 --- a/packages/gql-client/src/types.ts +++ b/packages/gql-client/src/types.ts @@ -63,7 +63,7 @@ export type Deployment = { branch: string commitHash: string commitMessage: string - url: string + url?: string environment: Environment isCurrent: boolean status: DeploymentStatus From e70bb34190ea1c7f7a0c1d111c8d9646d9b5fd9f Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 15:42:35 +0700 Subject: [PATCH 36/82] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20build:=20reorder=20p?= =?UTF-8?q?ackages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/package.json | 3 +- yarn.lock | 62 +++++----------------------------- 2 files changed, 11 insertions(+), 54 deletions(-) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 7e369024..1c917e9f 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -5,8 +5,8 @@ "dependencies": { "@fontsource/inter": "^5.0.16", "@material-tailwind/react": "^2.1.7", - "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-checkbox": "^1.0.4", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -15,6 +15,7 @@ "@types/react": "^18.2.42", "@types/react-dom": "^18.2.17", "assert": "^2.1.0", + "clsx": "^2.1.0", "date-fns": "^3.3.1", "downshift": "^8.2.3", "eslint-config-react-app": "^7.0.1", diff --git a/yarn.lock b/yarn.lock index 87b34483..d451b22d 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.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.3.1": +"@babel/runtime@^7.10.4", "@babel/runtime@^7.13.10", "@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== @@ -3277,6 +3277,13 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@radix-ui/primitive@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" + integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-avatar@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623" @@ -3288,57 +3295,6 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-layout-effect" "1.0.1" -"@radix-ui/react-compose-refs@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" - integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-context@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" - integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-primitive@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" - integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-slot" "1.0.2" - -"@radix-ui/react-slot@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" - integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - -"@radix-ui/react-use-callback-ref@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" - integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-use-layout-effect@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" - integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/primitive@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" - integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-checkbox@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz#98f22c38d5010dd6df4c5744cac74087e3275f4b" @@ -6314,7 +6270,7 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -clsx@^2.0.0: +clsx@^2.0.0, clsx@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== From 237e9e5cb9120d22899c0b9a2611d888d0e0e565 Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 15:43:09 +0700 Subject: [PATCH 37/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20implement=20class?= =?UTF-8?q?names=20utils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/utils/classnames.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/frontend/src/utils/classnames.ts diff --git a/packages/frontend/src/utils/classnames.ts b/packages/frontend/src/utils/classnames.ts new file mode 100644 index 00000000..7c5139f9 --- /dev/null +++ b/packages/frontend/src/utils/classnames.ts @@ -0,0 +1,13 @@ +import { clsx } from 'clsx'; +import type { ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +/** + * Returns a merged class name string by merging and processing multiple class names and Tailwind CSS styles. + * + * @param {...string[]} args - One or more class names and/or Tailwind CSS styles to be merged. + * @returns {string} - The merged class name string. + */ +export function cn(...args: ClassValue[]): string { + return twMerge(clsx(args)); +} From 636f68d7a47b4527231f41457a597f744470bb36 Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 15:43:32 +0700 Subject: [PATCH 38/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20more=20icon?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/CustomIcon/CrossIcon.tsx | 21 +++++++++++++++++++ .../shared/CustomIcon/SearchIcon.tsx | 21 +++++++++++++++++++ .../shared/CustomIcon/WarningIcon.tsx | 21 +++++++++++++++++++ .../src/components/shared/CustomIcon/index.ts | 3 +++ 4 files changed, 66 insertions(+) create mode 100644 packages/frontend/src/components/shared/CustomIcon/CrossIcon.tsx create mode 100644 packages/frontend/src/components/shared/CustomIcon/SearchIcon.tsx create mode 100644 packages/frontend/src/components/shared/CustomIcon/WarningIcon.tsx diff --git a/packages/frontend/src/components/shared/CustomIcon/CrossIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/CrossIcon.tsx new file mode 100644 index 00000000..8d649f0c --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/CrossIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const CrossIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/SearchIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/SearchIcon.tsx new file mode 100644 index 00000000..7688adcd --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/SearchIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const SearchIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/WarningIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/WarningIcon.tsx new file mode 100644 index 00000000..1fd27fb4 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/WarningIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const WarningIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index 34b7f485..aeb7c87e 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -4,3 +4,6 @@ export * from './CheckIcon'; export * from './ChevronGrabberHorizontal'; export * from './ChevronLeft'; export * from './ChevronRight'; +export * from './WarningIcon'; +export * from './SearchIcon'; +export * from './CrossIcon'; From 33b61915399d8c7572136bedb50cd155d370b135 Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 15:43:53 +0700 Subject: [PATCH 39/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20implement=20?= =?UTF-8?q?Input=20Field=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Input/Input.theme.ts | 68 ++++++++++++ .../src/components/shared/Input/Input.tsx | 100 ++++++++++++++++++ .../src/components/shared/Input/index.ts | 2 + 3 files changed, 170 insertions(+) create mode 100644 packages/frontend/src/components/shared/Input/Input.theme.ts create mode 100644 packages/frontend/src/components/shared/Input/Input.tsx create mode 100644 packages/frontend/src/components/shared/Input/index.ts diff --git a/packages/frontend/src/components/shared/Input/Input.theme.ts b/packages/frontend/src/components/shared/Input/Input.theme.ts new file mode 100644 index 00000000..e0ea2c5f --- /dev/null +++ b/packages/frontend/src/components/shared/Input/Input.theme.ts @@ -0,0 +1,68 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const inputTheme = tv( + { + slots: { + container: [ + 'flex items-center rounded-lg relative', + 'placeholder:text-elements-disabled', + 'disabled:cursor-not-allowed disabled:bg-controls-disabled', + ], + label: ['text-sm', 'text-elements-high-em'], + description: ['text-xs', 'text-elements-low-em'], + input: [ + 'focus-ring', + 'block w-full h-full rounded-lg text-elements-mid-em', + 'shadow-sm', + 'border border-border-interactive', + 'disabled:shadow-none', + 'disabled:border-none', + ], + icon: ['text-elements-mid-em'], + iconContainer: [ + 'absolute inset-y-0 flex items-center z-10 cursor-pointer', + ], + helperIcon: [], + helperText: ['flex gap-2 items-center text-elements-danger'], + }, + variants: { + state: { + default: { + input: '', + }, + error: { + input: [ + 'outline outline-offset-0 outline-border-danger', + 'shadow-none', + ], + helperText: ['text-elements-danger'], + }, + }, + size: { + md: { + container: 'h-11', + input: 'text-sm pl-4 pr-4', + icon: 'h-[18px] w-[18px]', + helperText: 'text-sm', + helperIcon: 'h-5 w-5', + }, + sm: { + container: 'h-8', + input: 'text-xs pl-3 pr-3', + icon: 'h-4 w-4', + helperText: 'text-xs', + helperIcon: 'h-4 w-4', + }, + }, + }, + defaultVariants: { + size: 'md', + state: 'default', + }, + }, + { + responsiveVariants: true, + }, +); + +export type InputTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Input/Input.tsx b/packages/frontend/src/components/shared/Input/Input.tsx new file mode 100644 index 00000000..52b398e1 --- /dev/null +++ b/packages/frontend/src/components/shared/Input/Input.tsx @@ -0,0 +1,100 @@ +import React, { ReactNode, useMemo } from 'react'; +import { ComponentPropsWithoutRef } from 'react'; +import { InputTheme, inputTheme } from './Input.theme'; +import { cloneIcon } from 'utils/cloneIcon'; +import { cn } from 'utils/classnames'; +import { WarningIcon } from '../CustomIcon'; + +export interface InputProps + extends InputTheme, + Omit, 'size'> { + label?: string; + description?: string; + leftIcon?: ReactNode; + rightIcon?: ReactNode; + helperText?: string; +} + +export const Input = ({ + className, + label, + description, + leftIcon, + rightIcon, + helperText, + size, + state, + ...props +}: InputProps) => { + const styleProps = (({ size = 'md', state }) => ({ + size, + state, + }))({ size, state }); + + const { + container: containerCls, + label: labelCls, + description: descriptionCls, + input: inputCls, + icon: iconCls, + iconContainer: iconContainerCls, + helperText: helperTextCls, + helperIcon: helperIconCls, + } = inputTheme({ ...styleProps }); + + const renderLabels = useMemo( + () => ( +
+

{label}

+

{description}

+
+ ), + [labelCls, descriptionCls, label, description], + ); + + const renderLeftIcon = useMemo(() => { + return ( +
+ {cloneIcon(leftIcon, { className: iconCls(), ariaHidden: true })} +
+ ); + }, [iconCls, leftIcon]); + + const renderRightIcon = useMemo(() => { + return ( +
+ {cloneIcon(rightIcon, { className: iconCls(), ariaHidden: true })} +
+ ); + }, [rightIcon, iconCls]); + + const renderHelperText = useMemo( + () => ( +
+ {state && + cloneIcon(, { + ariaHidden: true, + })} +

{helperText}

+
+ ), + [state, helperText, helperTextCls], + ); + + return ( +
+ {renderLabels} +
+ {leftIcon && renderLeftIcon} + + {rightIcon && renderRightIcon} +
+ {renderHelperText} +
+ ); +}; diff --git a/packages/frontend/src/components/shared/Input/index.ts b/packages/frontend/src/components/shared/Input/index.ts new file mode 100644 index 00000000..9ffcc229 --- /dev/null +++ b/packages/frontend/src/components/shared/Input/index.ts @@ -0,0 +1,2 @@ +export * from './Input'; +export * from './Input.theme'; From 7d5963d77658e17a8f366d7035acbc1b08bab47d Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 15:44:21 +0700 Subject: [PATCH 40/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20input=20to?= =?UTF-8?q?=20page/components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 456f433e..8cf092d5 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -3,7 +3,8 @@ import { Badge, BadgeProps } from 'components/shared/Badge'; import { Button, ButtonOrLinkProps } from 'components/shared/Button'; import { Calendar } from 'components/shared/Calendar'; import { Checkbox } from 'components/shared/Checkbox'; -import { PlusIcon } from 'components/shared/CustomIcon'; +import { SearchIcon, CrossIcon, PlusIcon } from 'components/shared/CustomIcon'; +import { Input } from 'components/shared/Input'; import React, { useState } from 'react'; import { Value } from 'react-calendar/dist/cjs/shared/types'; @@ -53,6 +54,59 @@ const Page = () => { {/* Insert Components here */}
+

Input

+
+
+ } + rightIcon={} + placeholder="Placeholder text" + /> + + +
+
+ } + rightIcon={} + description="Additional information or context" + placeholder="Placeholder text" + size="sm" + /> + + +
+
+ +
+

Button

{['primary', 'secondary', 'tertiary', 'danger'].map( From ea44efa0f22e556033de3003cbb79fccfb7f1c3f Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Wed, 21 Feb 2024 16:13:16 +0700 Subject: [PATCH 41/82] [T-4867: feat] Horizontal and vertical tabs component (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: create tabs component * ♻️ refactor: avoid big conflict on the example page * 🔧 chore: upgrade tailwindcss and install `@radix-ui/react-tabs` * ⚡️ feat: create globe icon component * 🎨 style: adjust vertical tab theme * 📝 docs: add vertical tabs to the example page --- packages/frontend/package.json | 5 +- .../shared/CustomIcon/GlobeIcon.tsx | 20 ++ .../src/components/shared/CustomIcon/index.ts | 1 + .../src/components/shared/Tabs/Tabs.theme.ts | 80 ++++++++ .../src/components/shared/Tabs/Tabs.tsx | 51 ++++++ .../shared/Tabs/TabsContent/TabsContent.tsx | 26 +++ .../shared/Tabs/TabsContent/index.ts | 3 + .../shared/Tabs/TabsList/TabsList.tsx | 25 +++ .../components/shared/Tabs/TabsList/index.ts | 3 + .../components/shared/Tabs/TabsProvider.tsx | 47 +++++ .../shared/Tabs/TabsTrigger/TabsTrigger.tsx | 59 ++++++ .../shared/Tabs/TabsTrigger/index.ts | 3 + .../src/components/shared/Tabs/index.ts | 1 + .../frontend/src/pages/components/index.tsx | 171 +++++------------- .../src/pages/components/renders/avatar.tsx | 19 ++ .../src/pages/components/renders/badge.tsx | 20 ++ .../src/pages/components/renders/button.tsx | 49 +++++ .../src/pages/components/renders/checkbox.tsx | 27 +++ .../src/pages/components/renders/tabs.tsx | 56 ++++++ yarn.lock | 147 +++++++++------ 20 files changed, 630 insertions(+), 183 deletions(-) create mode 100644 packages/frontend/src/components/shared/CustomIcon/GlobeIcon.tsx create mode 100644 packages/frontend/src/components/shared/Tabs/Tabs.theme.ts create mode 100644 packages/frontend/src/components/shared/Tabs/Tabs.tsx create mode 100644 packages/frontend/src/components/shared/Tabs/TabsContent/TabsContent.tsx create mode 100644 packages/frontend/src/components/shared/Tabs/TabsContent/index.ts create mode 100644 packages/frontend/src/components/shared/Tabs/TabsList/TabsList.tsx create mode 100644 packages/frontend/src/components/shared/Tabs/TabsList/index.ts create mode 100644 packages/frontend/src/components/shared/Tabs/TabsProvider.tsx create mode 100644 packages/frontend/src/components/shared/Tabs/TabsTrigger/TabsTrigger.tsx create mode 100644 packages/frontend/src/components/shared/Tabs/TabsTrigger/index.ts create mode 100644 packages/frontend/src/components/shared/Tabs/index.ts create mode 100644 packages/frontend/src/pages/components/renders/avatar.tsx create mode 100644 packages/frontend/src/pages/components/renders/badge.tsx create mode 100644 packages/frontend/src/pages/components/renders/button.tsx create mode 100644 packages/frontend/src/pages/components/renders/checkbox.tsx create mode 100644 packages/frontend/src/pages/components/renders/tabs.tsx diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 7e369024..a9674426 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -5,8 +5,9 @@ "dependencies": { "@fontsource/inter": "^5.0.16", "@material-tailwind/react": "^2.1.7", - "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-tabs": "^1.0.4", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -76,6 +77,6 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "prettier": "^3.1.0", - "tailwindcss": "^3.3.6" + "tailwindcss": "^3.4.1" } } diff --git a/packages/frontend/src/components/shared/CustomIcon/GlobeIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/GlobeIcon.tsx new file mode 100644 index 00000000..f2a23cab --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/GlobeIcon.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const GlobeIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index 34b7f485..c1ec6410 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -4,3 +4,4 @@ export * from './CheckIcon'; export * from './ChevronGrabberHorizontal'; export * from './ChevronLeft'; export * from './ChevronRight'; +export * from './GlobeIcon'; diff --git a/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts b/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts new file mode 100644 index 00000000..4667fe83 --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts @@ -0,0 +1,80 @@ +import { tv, type VariantProps } from 'tailwind-variants'; + +export type TabsVariants = VariantProps; + +export const tabsTheme = tv({ + slots: { + root: ['flex', 'data-[orientation=horizontal]:w-full'], + triggerWrapper: [ + // Horizontal – default + 'px-1', + 'pb-5', + 'text-elements-low-em', + 'border-b-2', + 'border-transparent', + 'hover:border-border-interactive/10', + 'hover:text-elements-mid-em', + 'focus-within:border-border-interactive/10', + 'data-[state=active]:font-medium', + 'data-[state=active]:text-elements-high-em', + 'data-[state=active]:border-elements-high-em', + // Vertical + 'data-[orientation=vertical]:px-3', + 'data-[orientation=vertical]:py-3', + 'data-[orientation=vertical]:min-w-[240px]', + 'data-[orientation=vertical]:focus-ring', + 'data-[orientation=vertical]:rounded-xl', + 'data-[orientation=vertical]:border-transparent', + 'data-[orientation=vertical]:hover:bg-base-bg-emphasized', + 'data-[orientation=vertical]:hover:text-elements-mid-em', + 'data-[orientation=vertical]:hover:border-transparent', + 'data-[orientation=vertical]:focus-visible:border-transparent', + 'data-[orientation=vertical]:focus-visible:bg-base-bg-emphasized', + 'data-[orientation=vertical]:focus-visible:text-elements-mid-em', + 'data-[orientation=vertical]:data-[state=active]:font-normal', + 'data-[orientation=vertical]:data-[state=active]:bg-base-bg-emphasized', + 'data-[orientation=vertical]:data-[state=active]:border-transparent', + 'data-[orientation=vertical]:data-[state=active]:hover:text-elements-high-em', + 'data-[orientation=vertical]:data-[state=active]:focus-visible:text-elements-high-em', + ], + trigger: [ + 'flex', + 'gap-1.5', + 'cursor-default', + 'select-none', + 'items-center', + 'justify-center', + 'outline-none', + 'leading-none', + 'tracking-[-0.006em]', + 'rounded-md', + // Horizontal – default + 'data-[orientation=horizontal]:focus-ring', + // Vertical + 'data-[orientation=vertical]:gap-2', + ], + triggerList: [ + 'flex', + 'shrink-0', + 'gap-5', + 'border-b', + 'border-transparent', + // Horizontal – default + 'data-[orientation=horizontal]:border-border-interactive/10', + // Vertical + 'data-[orientation=vertical]:flex-col', + 'data-[orientation=vertical]:gap-0.5', + ], + content: ['text-elements-high-em', 'grow', 'outline-none', 'tab-content'], + }, + variants: { + fillWidth: { + true: { + trigger: ['flex-1'], + }, + }, + }, + defaultVariants: { + fillWidth: false, + }, +}); diff --git a/packages/frontend/src/components/shared/Tabs/Tabs.tsx b/packages/frontend/src/components/shared/Tabs/Tabs.tsx new file mode 100644 index 00000000..af1c65db --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/Tabs.tsx @@ -0,0 +1,51 @@ +import React, { type ComponentPropsWithoutRef } from 'react'; +import { Root as TabsRoot } from '@radix-ui/react-tabs'; + +import { tabsTheme } from './Tabs.theme'; +import TabsContent from './TabsContent'; +import TabsList from './TabsList'; +import TabsTrigger from './TabsTrigger'; +import TabsProvider, { TabsProviderProps } from './TabsProvider'; + +export interface TabsProps extends ComponentPropsWithoutRef { + /** + * The configuration for the tabs component. + */ + config?: TabsProviderProps; +} + +/** + * A component that allows users to switch between different tabs. + * @returns JSX element representing the tabs component. + */ +export const Tabs = ({ + config, + className, + orientation = 'horizontal', + ...props +}: TabsProps) => { + const { root } = tabsTheme(config); + + return ( + + + + ); +}; + +/** + * Assigns the TabsTrigger class to the Trigger property of the Tabs object. + */ +Tabs.Trigger = TabsTrigger; +/** + * Assigns the TabsList object to the List property of the Tabs object. + */ +Tabs.List = TabsList; +/** + * Assigns the TabsContent component to the Content property of the Tabs component. + */ +Tabs.Content = TabsContent; diff --git a/packages/frontend/src/components/shared/Tabs/TabsContent/TabsContent.tsx b/packages/frontend/src/components/shared/Tabs/TabsContent/TabsContent.tsx new file mode 100644 index 00000000..21200991 --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/TabsContent/TabsContent.tsx @@ -0,0 +1,26 @@ +import React, { + forwardRef, + type ComponentPropsWithoutRef, + type ElementRef, +} from 'react'; +import { Content } from '@radix-ui/react-tabs'; + +import { tabsTheme } from '../Tabs.theme'; + +export interface TabsContentProps + extends ComponentPropsWithoutRef {} + +/** + * A component that represents the content of the tabs component. + */ +const TabsContent = forwardRef, TabsContentProps>( + ({ className, ...props }, ref) => { + const { content } = tabsTheme(); + return ; + }, +); + +// Assigns the display name to the TabsContent component. +TabsContent.displayName = 'TabsContent'; + +export { TabsContent }; diff --git a/packages/frontend/src/components/shared/Tabs/TabsContent/index.ts b/packages/frontend/src/components/shared/Tabs/TabsContent/index.ts new file mode 100644 index 00000000..0e80ffbd --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/TabsContent/index.ts @@ -0,0 +1,3 @@ +import { TabsContent } from './TabsContent'; + +export default TabsContent; diff --git a/packages/frontend/src/components/shared/Tabs/TabsList/TabsList.tsx b/packages/frontend/src/components/shared/Tabs/TabsList/TabsList.tsx new file mode 100644 index 00000000..f5eb1498 --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/TabsList/TabsList.tsx @@ -0,0 +1,25 @@ +import React, { + forwardRef, + type ComponentPropsWithoutRef, + type ElementRef, +} from 'react'; +import { List } from '@radix-ui/react-tabs'; + +import { tabsTheme } from 'components/shared/Tabs/Tabs.theme'; + +export interface TabsListProps extends ComponentPropsWithoutRef {} + +/** + * A component that represents the list of tabs. + */ +const TabsList = forwardRef, TabsListProps>( + ({ className, ...props }, ref) => { + const { triggerList } = tabsTheme({ className }); + return ; + }, +); + +// Assigns the display name to the TabsList component. +TabsList.displayName = 'TabsList'; + +export { TabsList }; diff --git a/packages/frontend/src/components/shared/Tabs/TabsList/index.ts b/packages/frontend/src/components/shared/Tabs/TabsList/index.ts new file mode 100644 index 00000000..f02fd4e5 --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/TabsList/index.ts @@ -0,0 +1,3 @@ +import { TabsList } from './TabsList'; + +export default TabsList; diff --git a/packages/frontend/src/components/shared/Tabs/TabsProvider.tsx b/packages/frontend/src/components/shared/Tabs/TabsProvider.tsx new file mode 100644 index 00000000..b51c680b --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/TabsProvider.tsx @@ -0,0 +1,47 @@ +import React, { + createContext, + useContext, + type PropsWithChildren, + ComponentPropsWithoutRef, +} from 'react'; +import { TabsVariants } from './Tabs.theme'; +import { Root as TabsRoot } from '@radix-ui/react-tabs'; + +export interface TabsProviderProps + extends Partial, + ComponentPropsWithoutRef {} + +type TabsProviderContext = ReturnType; + +const TabsContext = createContext>({}); + +// For inferring return type +const useTabsValues = (props: TabsProviderProps) => { + return props; +}; + +/** + * A provider component that allows users to switch between different tabs. + * @returns JSX element representing the tabs provider component. + */ +export const TabsProvider = ({ + children, + ...props +}: PropsWithChildren): JSX.Element => { + const values = useTabsValues(props); + return {children}; +}; + +/** + * A hook that returns the context of the tabs provider. + * @returns The context of the tabs provider. + */ +export const useTabs = () => { + const context = useContext(TabsContext); + if (context === undefined) { + throw new Error('useTabs was used outside of its Provider'); + } + return context; +}; + +export default TabsProvider; diff --git a/packages/frontend/src/components/shared/Tabs/TabsTrigger/TabsTrigger.tsx b/packages/frontend/src/components/shared/Tabs/TabsTrigger/TabsTrigger.tsx new file mode 100644 index 00000000..61093bff --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/TabsTrigger/TabsTrigger.tsx @@ -0,0 +1,59 @@ +import React, { + forwardRef, + type ComponentPropsWithoutRef, + type ElementRef, + type PropsWithChildren, + type ReactNode, +} from 'react'; +import { Trigger } from '@radix-ui/react-tabs'; + +import { tabsTheme } from 'components/shared/Tabs/Tabs.theme'; +import { useTabs } from 'components/shared/Tabs/TabsProvider'; + +export interface TabsTriggerProps + extends ComponentPropsWithoutRef { + /** + * The icon to display in the trigger. + */ + icon?: ReactNode; +} + +/** + * A component that represents the trigger for the tabs component. + */ +const TabsTrigger = forwardRef< + ElementRef, + PropsWithChildren +>(({ className, icon, children, ...props }, ref) => { + const config = useTabs(); + const { triggerWrapper, trigger } = tabsTheme(config); + const orientation = config.orientation; + + return ( + + {/* Need to add button in the trigger children because there's focus state inside the children */} + + + ); +}); + +TabsTrigger.displayName = 'TabsTrigger'; + +export { TabsTrigger }; diff --git a/packages/frontend/src/components/shared/Tabs/TabsTrigger/index.ts b/packages/frontend/src/components/shared/Tabs/TabsTrigger/index.ts new file mode 100644 index 00000000..57b0b3e6 --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/TabsTrigger/index.ts @@ -0,0 +1,3 @@ +import { TabsTrigger } from './TabsTrigger'; + +export default TabsTrigger; diff --git a/packages/frontend/src/components/shared/Tabs/index.ts b/packages/frontend/src/components/shared/Tabs/index.ts new file mode 100644 index 00000000..856dbbb3 --- /dev/null +++ b/packages/frontend/src/components/shared/Tabs/index.ts @@ -0,0 +1 @@ +export * from './Tabs'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 456f433e..c1c46f6c 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,43 +1,23 @@ -import { Avatar, AvatarVariants } from 'components/shared/Avatar'; -import { Badge, BadgeProps } from 'components/shared/Badge'; -import { Button, ButtonOrLinkProps } from 'components/shared/Button'; -import { Calendar } from 'components/shared/Calendar'; -import { Checkbox } from 'components/shared/Checkbox'; -import { PlusIcon } from 'components/shared/CustomIcon'; import React, { useState } from 'react'; +import { Calendar } from 'components/shared/Calendar'; import { Value } from 'react-calendar/dist/cjs/shared/types'; - -const avatarSizes: AvatarVariants['size'][] = [18, 20, 24, 28, 32, 36, 40, 44]; -const avatarVariants: AvatarVariants['type'][] = ['gray', 'orange', 'blue']; +import { + renderCheckbox, + renderCheckboxWithDescription, +} from './renders/checkbox'; +import { avatars, avatarsFallback } from './renders/avatar'; +import { renderBadges } from './renders/badge'; +import { renderButtonIcons, renderButtons } from './renders/button'; +import { + renderTabWithBadges, + renderTabs, + renderVerticalTabs, +} from './renders/tabs'; const Page = () => { const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); - const avatars = avatarSizes.map((size) => { - return ( - - ); - }); - - const avatarsFallback = avatarVariants.map((color) => { - return avatarSizes.map((size) => { - return ( - - ); - }); - }); - return (
@@ -51,114 +31,36 @@ const Page = () => {
- {/* Insert Components here */} + {/* Button */}

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) => ( - - ))} -
- ))} + {renderButtons()} + {renderButtonIcons()}
+ {/* Badge */}

Badge

-
- {['primary', 'secondary', 'tertiary', 'inset'].map( - (variant, index) => ( -
- {['sm', 'xs'].map((size) => ( - - 1 - - ))} -
- ), - )} -
+
{renderBadges()}
+ {/* Checkbox */}

Checkbox

+
{renderCheckbox()}
- {Array.from({ length: 5 }).map((_, index) => ( - - ))} -
-
- {Array.from({ length: 2 }).map((_, index) => ( - - ))} + {renderCheckboxWithDescription()}
+ {/* Calendar */}

Calendar

@@ -185,18 +87,31 @@ const Page = () => { />
+
-
+
- {/* Avatar */} -
-

Avatar

-
- {avatars} - {avatarsFallback} -
+ {/* Avatar */} +
+

Avatar

+
+ {avatars} + {avatarsFallback}
+ +
+ + {/* Tabs */} +
+

Tabs

+
+ {renderTabs()} + {renderTabWithBadges()} +
+

Vertical Tabs

+ {renderVerticalTabs()} +
diff --git a/packages/frontend/src/pages/components/renders/avatar.tsx b/packages/frontend/src/pages/components/renders/avatar.tsx new file mode 100644 index 00000000..9ebaa03f --- /dev/null +++ b/packages/frontend/src/pages/components/renders/avatar.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Avatar, AvatarVariants } from 'components/shared/Avatar'; + +const avatarSizes: AvatarVariants['size'][] = [18, 20, 24, 28, 32, 36, 40, 44]; +const avatarVariants: AvatarVariants['type'][] = ['gray', 'orange', 'blue']; + +export const avatars = avatarSizes.map((size) => { + return ( + + ); +}); + +export const avatarsFallback = avatarVariants.map((color) => { + return avatarSizes.map((size) => { + return ( + + ); + }); +}); diff --git a/packages/frontend/src/pages/components/renders/badge.tsx b/packages/frontend/src/pages/components/renders/badge.tsx new file mode 100644 index 00000000..e4eb3114 --- /dev/null +++ b/packages/frontend/src/pages/components/renders/badge.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { Badge } from 'components/shared/Badge'; +import { BadgeTheme } from 'components/shared/Badge/Badge.theme'; + +export const renderBadges = () => { + return ['primary', 'secondary', 'tertiary', 'inset'].map((variant, index) => ( +
+ {['sm', 'xs'].map((size) => ( + + 1 + + ))} +
+ )); +}; diff --git a/packages/frontend/src/pages/components/renders/button.tsx b/packages/frontend/src/pages/components/renders/button.tsx new file mode 100644 index 00000000..c1c69c48 --- /dev/null +++ b/packages/frontend/src/pages/components/renders/button.tsx @@ -0,0 +1,49 @@ +import { Button, ButtonTheme } from 'components/shared/Button'; +import { PlusIcon } from 'components/shared/CustomIcon'; +import React from 'react'; + +export const renderButtons = () => { + return ['primary', 'secondary', 'tertiary', 'danger'].map( + (variant, index) => ( +
+ {['lg', 'md', 'sm', 'xs', 'disabled'].map((size) => ( + + ))} +
+ ), + ); +}; + +export const renderButtonIcons = () => { + return [ + 'primary', + 'secondary', + 'tertiary', + 'ghost', + 'danger', + 'danger-ghost', + ].map((variant, index) => ( +
+ {['lg', 'md', 'sm', 'xs', 'disabled'].map((size) => ( + + ))} +
+ )); +}; diff --git a/packages/frontend/src/pages/components/renders/checkbox.tsx b/packages/frontend/src/pages/components/renders/checkbox.tsx new file mode 100644 index 00000000..66c9eef5 --- /dev/null +++ b/packages/frontend/src/pages/components/renders/checkbox.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Checkbox } from 'components/shared/Checkbox'; + +export const renderCheckbox = () => { + return Array.from({ length: 5 }).map((_, index) => ( + + )); +}; + +export const renderCheckboxWithDescription = () => { + return Array.from({ length: 2 }).map((_, index) => ( + + )); +}; diff --git a/packages/frontend/src/pages/components/renders/tabs.tsx b/packages/frontend/src/pages/components/renders/tabs.tsx new file mode 100644 index 00000000..d344317e --- /dev/null +++ b/packages/frontend/src/pages/components/renders/tabs.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { Tabs } from 'components/shared/Tabs'; +import { Badge } from 'components/shared/Badge'; +import { GlobeIcon } from 'components/shared/CustomIcon'; + +const tabs = Array.from({ length: 8 }); + +export const renderTabs = () => { + return ( + + + {tabs.map((_, index) => ( + + Tab item {index} + + ))} + + + ); +}; + +export const renderTabWithBadges = () => { + return ( + + + {tabs.map((_, index) => ( + {index}} + > + Tab item + + ))} + + + ); +}; + +export const renderVerticalTabs = () => { + return ( + + + {tabs.slice(0, 4).map((_, index) => ( + } + value={index.toString()} + > + Tab item {index} + + ))} + + + ); +}; diff --git a/yarn.lock b/yarn.lock index 87b34483..d24c5a8a 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.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.13.10", "@babel/runtime@^7.23.7", "@babel/runtime@^7.3.1": +"@babel/runtime@^7.10.4", "@babel/runtime@^7.13.10", "@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== @@ -3277,6 +3277,13 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@radix-ui/primitive@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" + integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-avatar@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623" @@ -3288,57 +3295,6 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-layout-effect" "1.0.1" -"@radix-ui/react-compose-refs@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" - integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-context@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" - integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-primitive@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" - integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-slot" "1.0.2" - -"@radix-ui/react-slot@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" - integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-compose-refs" "1.0.1" - -"@radix-ui/react-use-callback-ref@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" - integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-use-layout-effect@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" - integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/primitive@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" - integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/react-checkbox@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz#98f22c38d5010dd6df4c5744cac74087e3275f4b" @@ -3354,6 +3310,17 @@ "@radix-ui/react-use-previous" "1.0.1" "@radix-ui/react-use-size" "1.0.1" +"@radix-ui/react-collection@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" + integrity sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-compose-refs@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" @@ -3368,6 +3335,21 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-direction@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" + integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-id@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" + integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-presence@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" @@ -3385,6 +3367,22 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-roving-focus@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" + integrity sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-slot@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" @@ -3393,6 +3391,21 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-tabs@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2" + integrity sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-roving-focus" "1.0.4" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-callback-ref@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" @@ -15239,7 +15252,7 @@ tailwind-variants@^0.2.0: dependencies: tailwind-merge "^2.2.0" -tailwindcss@^3.0.2, tailwindcss@^3.3.6: +tailwindcss@^3.0.2: version "3.3.6" resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz" integrity sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw== @@ -15267,6 +15280,34 @@ tailwindcss@^3.0.2, tailwindcss@^3.3.6: resolve "^1.22.2" sucrase "^3.32.0" +tailwindcss@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" + integrity sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.5.3" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.0" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.19.1" + lilconfig "^2.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.23" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.1" + postcss-nested "^6.0.1" + postcss-selector-parser "^6.0.11" + resolve "^1.22.2" + sucrase "^3.32.0" + tapable@^1.0.0: version "1.1.3" resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz" From b6630670356afb699f242454bd7161c5a5ad5e99 Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 17:07:30 +0700 Subject: [PATCH 42/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20use=20absolute=20?= =?UTF-8?q?import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Input/Input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Input/Input.tsx b/packages/frontend/src/components/shared/Input/Input.tsx index 52b398e1..0905f556 100644 --- a/packages/frontend/src/components/shared/Input/Input.tsx +++ b/packages/frontend/src/components/shared/Input/Input.tsx @@ -1,9 +1,9 @@ import React, { ReactNode, useMemo } from 'react'; import { ComponentPropsWithoutRef } from 'react'; import { InputTheme, inputTheme } from './Input.theme'; +import { WarningIcon } from 'components/shared/CustomIcon'; import { cloneIcon } from 'utils/cloneIcon'; import { cn } from 'utils/classnames'; -import { WarningIcon } from '../CustomIcon'; export interface InputProps extends InputTheme, From 5f6723fad634cb8fe15209004c9bd2603fb250bb Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 17:07:59 +0700 Subject: [PATCH 43/82] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20create?= =?UTF-8?q?=20new=20renderInput=20for=20component=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/components/renders/input.tsx | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/frontend/src/pages/components/renders/input.tsx diff --git a/packages/frontend/src/pages/components/renders/input.tsx b/packages/frontend/src/pages/components/renders/input.tsx new file mode 100644 index 00000000..bc0b79ff --- /dev/null +++ b/packages/frontend/src/pages/components/renders/input.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { Input } from 'components/shared/Input'; +import { SearchIcon, CrossIcon } from 'components/shared/CustomIcon'; + +export const renderInputs = () => { + return ( + <> +
+ } + rightIcon={} + placeholder="Placeholder text" + /> + + +
+
+ } + rightIcon={} + description="Additional information or context" + placeholder="Placeholder text" + size="sm" + /> + + +
+ + ); +}; From 807a4b11974c4795a725f14c454ec4045ac23d7c Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 17:08:14 +0700 Subject: [PATCH 44/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 57 +------------------ 1 file changed, 2 insertions(+), 55 deletions(-) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 1a76f3af..dcd8941a 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,11 +1,5 @@ import React, { useState } from 'react'; -import { Avatar, AvatarVariants } from 'components/shared/Avatar'; -import { Badge, BadgeProps } from 'components/shared/Badge'; -import { Button, ButtonOrLinkProps } from 'components/shared/Button'; import { Calendar } from 'components/shared/Calendar'; -import { Checkbox } from 'components/shared/Checkbox'; -import { SearchIcon, CrossIcon, PlusIcon } from 'components/shared/CustomIcon'; -import { Input } from 'components/shared/Input'; import { Value } from 'react-calendar/dist/cjs/shared/types'; import { renderCheckbox, @@ -19,6 +13,7 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { renderInputs } from './renders/input'; const Page = () => { const [singleDate, setSingleDate] = useState(); @@ -40,55 +35,7 @@ const Page = () => { {/* Button */}

Input

-
-
- } - rightIcon={} - placeholder="Placeholder text" - /> - - -
-
- } - rightIcon={} - description="Additional information or context" - placeholder="Placeholder text" - size="sm" - /> - - -
-
+
{renderInputs()}
From 195d957251b48a8da64ada7568f99af2e5dc39be Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 17:08:33 +0700 Subject: [PATCH 45/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20restructure=20tai?= =?UTF-8?q?lwind=20classnames?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Input/Input.theme.ts | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/frontend/src/components/shared/Input/Input.theme.ts b/packages/frontend/src/components/shared/Input/Input.theme.ts index e0ea2c5f..65dbdfb6 100644 --- a/packages/frontend/src/components/shared/Input/Input.theme.ts +++ b/packages/frontend/src/components/shared/Input/Input.theme.ts @@ -4,26 +4,40 @@ export const inputTheme = tv( { slots: { container: [ - 'flex items-center rounded-lg relative', + 'flex', + 'items-center', + 'rounded-lg', + 'relative', 'placeholder:text-elements-disabled', - 'disabled:cursor-not-allowed disabled:bg-controls-disabled', + 'disabled:cursor-not-allowed', + 'disabled:bg-controls-disabled', ], label: ['text-sm', 'text-elements-high-em'], description: ['text-xs', 'text-elements-low-em'], input: [ 'focus-ring', - 'block w-full h-full rounded-lg text-elements-mid-em', + 'block', + 'w-full', + 'h-full', + 'rounded-lg', + 'text-elements-mid-em', 'shadow-sm', - 'border border-border-interactive', + 'border', + 'border-border-interactive', 'disabled:shadow-none', 'disabled:border-none', ], icon: ['text-elements-mid-em'], iconContainer: [ - 'absolute inset-y-0 flex items-center z-10 cursor-pointer', + 'absolute', + 'inset-y-0', + 'flex', + 'items-center', + 'z-10', + 'cursor-pointer', ], helperIcon: [], - helperText: ['flex gap-2 items-center text-elements-danger'], + helperText: ['flex', 'gap-2', 'items-center', 'text-elements-danger'], }, variants: { state: { @@ -32,10 +46,12 @@ export const inputTheme = tv( }, error: { input: [ - 'outline outline-offset-0 outline-border-danger', + 'outline', + 'outline-offset-0', + 'outline-border-danger', 'shadow-none', ], - helperText: ['text-elements-danger'], + helperText: 'text-elements-danger', }, }, size: { From 448d0ceb7c75ec3ce4034bc9def6d6d66b45c6b6 Mon Sep 17 00:00:00 2001 From: Andre H Date: Wed, 21 Feb 2024 17:10:45 +0700 Subject: [PATCH 46/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=201=20classname=20p?= =?UTF-8?q?er=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/shared/Input/Input.theme.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/shared/Input/Input.theme.ts b/packages/frontend/src/components/shared/Input/Input.theme.ts index 65dbdfb6..8def6a0d 100644 --- a/packages/frontend/src/components/shared/Input/Input.theme.ts +++ b/packages/frontend/src/components/shared/Input/Input.theme.ts @@ -57,17 +57,17 @@ export const inputTheme = tv( size: { md: { container: 'h-11', - input: 'text-sm pl-4 pr-4', - icon: 'h-[18px] w-[18px]', + input: ['text-sm pl-4 pr-4'], + icon: ['h-[18px] w-[18px]'], helperText: 'text-sm', - helperIcon: 'h-5 w-5', + helperIcon: ['h-5 w-5'], }, sm: { container: 'h-8', - input: 'text-xs pl-3 pr-3', - icon: 'h-4 w-4', + input: ['text-xs pl-3 pr-3'], + icon: ['h-4 w-4'], helperText: 'text-xs', - helperIcon: 'h-4 w-4', + helperIcon: ['h-4 w-4'], }, }, }, From da2f7ede426025e9657cad31b62c3df524c937db Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 09:15:17 +0700 Subject: [PATCH 47/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20dependency?= =?UTF-8?q?=20to=20usememo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Input/Input.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/shared/Input/Input.tsx b/packages/frontend/src/components/shared/Input/Input.tsx index 0905f556..f5bbdca5 100644 --- a/packages/frontend/src/components/shared/Input/Input.tsx +++ b/packages/frontend/src/components/shared/Input/Input.tsx @@ -58,7 +58,7 @@ export const Input = ({ {cloneIcon(leftIcon, { className: iconCls(), ariaHidden: true })}
); - }, [iconCls, leftIcon]); + }, [cloneIcon, iconCls, iconContainerCls, leftIcon]); const renderRightIcon = useMemo(() => { return ( @@ -66,7 +66,7 @@ export const Input = ({ {cloneIcon(rightIcon, { className: iconCls(), ariaHidden: true })}
); - }, [rightIcon, iconCls]); + }, [cloneIcon, iconCls, iconContainerCls, rightIcon]); const renderHelperText = useMemo( () => ( @@ -78,7 +78,7 @@ export const Input = ({

{helperText}

), - [state, helperText, helperTextCls], + [cloneIcon, state, helperIconCls, helperText, helperTextCls], ); return ( From eb6a72742565d36ef71f9dfe207a08873e7bb127 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Thu, 22 Feb 2024 09:17:22 +0700 Subject: [PATCH 48/82] [T-4862: feat] Link component (#88) --- .../components/shared/Button/Button.theme.ts | 23 +++++++++++++++++++ .../frontend/src/pages/components/index.tsx | 16 ++++++++++++- .../src/pages/components/renders/button.tsx | 16 +++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Button/Button.theme.ts b/packages/frontend/src/components/shared/Button/Button.theme.ts index 897fdfbc..6c9614c6 100644 --- a/packages/frontend/src/components/shared/Button/Button.theme.ts +++ b/packages/frontend/src/components/shared/Button/Button.theme.ts @@ -118,6 +118,29 @@ export const buttonTheme = tv( 'disabled:border-transparent', 'disabled:shadow-none', ], + link: [ + 'p-0', + 'gap-1.5', + 'text-elements-link', + 'rounded', + 'focus-ring', + 'hover:underline', + 'hover:text-elements-link-hovered', + 'disabled:text-controls-disabled', + 'disabled:hover:no-underline', + ], + 'link-emphasized': [ + 'p-0', + 'gap-1.5', + 'text-elements-high-em', + 'rounded', + 'underline', + 'focus-ring', + 'hover:text-elements-link-hovered', + 'disabled:text-controls-disabled', + 'disabled:hover:no-underline', + 'dark:text-elements-on-high-contrast', + ], unstyled: [], }, }, diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index c1c46f6c..507563a0 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -7,7 +7,11 @@ import { } from './renders/checkbox'; import { avatars, avatarsFallback } from './renders/avatar'; import { renderBadges } from './renders/badge'; -import { renderButtonIcons, renderButtons } from './renders/button'; +import { + renderButtonIcons, + renderButtons, + renderLinks, +} from './renders/button'; import { renderTabWithBadges, renderTabs, @@ -112,6 +116,16 @@ const Page = () => {

Vertical Tabs

{renderVerticalTabs()}
+ +
+ + {/* Link */} +
+

Link

+
+ {renderLinks()} +
+
diff --git a/packages/frontend/src/pages/components/renders/button.tsx b/packages/frontend/src/pages/components/renders/button.tsx index c1c69c48..f5272d3e 100644 --- a/packages/frontend/src/pages/components/renders/button.tsx +++ b/packages/frontend/src/pages/components/renders/button.tsx @@ -47,3 +47,19 @@ export const renderButtonIcons = () => {
)); }; + +export const renderLinks = () => { + return ['link', 'link-emphasized', 'disabled'].map((variant) => ( + + )); +}; From 6df094bf2e3d18719816d32e3dbc7e423064c803 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Thu, 22 Feb 2024 10:45:19 +0700 Subject: [PATCH 49/82] [T-4861: feat] Inline notification component (#86) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: create info square icon component * ⚡️ feat: create inline notification component * 📝 docs: add js doc comment and add inline notification component to the example page * 🐛 fix: use the right method of `useCallback --- .../shared/CustomIcon/InfoSquareIcon.tsx | 21 +++++ .../src/components/shared/CustomIcon/index.ts | 1 + .../InlineNotification.theme.ts | 78 +++++++++++++++++++ .../InlineNotification/InlineNotification.tsx | 68 ++++++++++++++++ .../shared/InlineNotification/index.ts | 1 + .../frontend/src/pages/components/index.tsx | 19 ++++- .../renders/inlineNotifications.tsx | 43 ++++++++++ 7 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 packages/frontend/src/components/shared/CustomIcon/InfoSquareIcon.tsx create mode 100644 packages/frontend/src/components/shared/InlineNotification/InlineNotification.theme.ts create mode 100644 packages/frontend/src/components/shared/InlineNotification/InlineNotification.tsx create mode 100644 packages/frontend/src/components/shared/InlineNotification/index.ts create mode 100644 packages/frontend/src/pages/components/renders/inlineNotifications.tsx diff --git a/packages/frontend/src/components/shared/CustomIcon/InfoSquareIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/InfoSquareIcon.tsx new file mode 100644 index 00000000..273131d6 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/InfoSquareIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const InfoSquareIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index 6479a1dd..18dbf249 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -4,6 +4,7 @@ export * from './CheckIcon'; export * from './ChevronGrabberHorizontal'; export * from './ChevronLeft'; export * from './ChevronRight'; +export * from './InfoSquareIcon'; export * from './WarningIcon'; export * from './SearchIcon'; export * from './CrossIcon'; diff --git a/packages/frontend/src/components/shared/InlineNotification/InlineNotification.theme.ts b/packages/frontend/src/components/shared/InlineNotification/InlineNotification.theme.ts new file mode 100644 index 00000000..43536b55 --- /dev/null +++ b/packages/frontend/src/components/shared/InlineNotification/InlineNotification.theme.ts @@ -0,0 +1,78 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const inlineNotificationTheme = tv({ + slots: { + wrapper: ['rounded-xl', 'flex', 'gap-2', 'items-start', 'w-full', 'border'], + content: ['flex', 'flex-col', 'gap-1'], + title: [], + description: [], + icon: ['flex', 'items-start'], + }, + variants: { + variant: { + info: { + wrapper: ['border-border-info-light', 'bg-base-bg-emphasized-info'], + title: ['text-elements-on-emphasized-info'], + description: ['text-elements-on-emphasized-info'], + icon: ['text-elements-info'], + }, + danger: { + wrapper: ['border-border-danger-light', 'bg-base-bg-emphasized-danger'], + title: ['text-elements-on-emphasized-danger'], + description: ['text-elements-on-emphasized-danger'], + icon: ['text-elements-danger'], + }, + warning: { + wrapper: [ + 'border-border-warning-light', + 'bg-base-bg-emphasized-warning', + ], + title: ['text-elements-on-emphasized-warning'], + description: ['text-elements-on-emphasized-warning'], + icon: ['text-elements-warning'], + }, + success: { + wrapper: [ + 'border-border-success-light', + 'bg-base-bg-emphasized-success', + ], + title: ['text-elements-on-emphasized-success'], + description: ['text-elements-on-emphasized-success'], + icon: ['text-elements-success'], + }, + generic: { + wrapper: ['border-border-separator', 'bg-base-bg-emphasized'], + title: ['text-elements-high-em'], + description: ['text-elements-on-emphasized-info'], + icon: ['text-elements-high-em'], + }, + }, + size: { + sm: { + wrapper: ['px-2', 'py-2'], + title: ['leading-4', 'text-xs'], + description: ['leading-4', 'text-xs'], + icon: ['h-4', 'w-4'], + }, + md: { + wrapper: ['px-3', 'py-3'], + title: ['leading-5', 'tracking-[-0.006em]', 'text-sm'], + description: ['leading-5', 'tracking-[-0.006em]', 'text-sm'], + icon: ['h-5', 'w-5'], + }, + }, + hasDescription: { + true: { + title: ['font-medium'], + }, + }, + }, + defaultVariants: { + variant: 'generic', + size: 'md', + }, +}); + +export type InlineNotificationTheme = VariantProps< + typeof inlineNotificationTheme +>; diff --git a/packages/frontend/src/components/shared/InlineNotification/InlineNotification.tsx b/packages/frontend/src/components/shared/InlineNotification/InlineNotification.tsx new file mode 100644 index 00000000..b3b2845c --- /dev/null +++ b/packages/frontend/src/components/shared/InlineNotification/InlineNotification.tsx @@ -0,0 +1,68 @@ +import React, { ReactNode, useCallback } from 'react'; +import { ComponentPropsWithoutRef } from 'react'; +import { + InlineNotificationTheme, + inlineNotificationTheme, +} from './InlineNotification.theme'; +import { InfoSquareIcon } from 'components/shared/CustomIcon'; +import { cloneIcon } from 'utils/cloneIcon'; + +export interface InlineNotificationProps + extends ComponentPropsWithoutRef<'div'>, + InlineNotificationTheme { + /** + * The title of the notification + */ + title: string; + /** + * The description of the notification + */ + description?: string; + /** + * The icon to display in the notification + * @default + */ + icon?: ReactNode; +} + +/** + * A notification that is displayed inline with the content + * + * @example + * ```tsx + * + * ``` + */ +export const InlineNotification = ({ + className, + title, + description, + size, + variant, + icon, + ...props +}: InlineNotificationProps) => { + const { + wrapper, + content, + title: titleClass, + description: descriptionClass, + icon: iconClass, + } = inlineNotificationTheme({ size, variant, hasDescription: !!description }); + + // Render custom icon or default icon + const renderIcon = useCallback(() => { + if (!icon) return ; + return cloneIcon(icon, { className: iconClass() }); + }, [icon]); + + return ( +
+ {renderIcon()} +
+

{title}

+ {description &&

{description}

} +
+
+ ); +}; diff --git a/packages/frontend/src/components/shared/InlineNotification/index.ts b/packages/frontend/src/components/shared/InlineNotification/index.ts new file mode 100644 index 00000000..fbe7bbd4 --- /dev/null +++ b/packages/frontend/src/components/shared/InlineNotification/index.ts @@ -0,0 +1 @@ +export * from './InlineNotification'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index ab6be949..b2c81d31 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -17,6 +17,10 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { + renderInlineNotificationWithDescriptions, + renderInlineNotifications, +} from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; const Page = () => { @@ -25,7 +29,7 @@ const Page = () => { return (
-
+

Manual Storybook

Get started by editing{' '} @@ -125,6 +129,19 @@ const Page = () => {

+ {/* Inline notification */} +
+

Inline Notification

+
+ {renderInlineNotifications()} +
+
+ {renderInlineNotificationWithDescriptions()} +
+
+ +
+ {/* Link */}

Link

diff --git a/packages/frontend/src/pages/components/renders/inlineNotifications.tsx b/packages/frontend/src/pages/components/renders/inlineNotifications.tsx new file mode 100644 index 00000000..fc36b914 --- /dev/null +++ b/packages/frontend/src/pages/components/renders/inlineNotifications.tsx @@ -0,0 +1,43 @@ +import { InlineNotification } from 'components/shared/InlineNotification'; +import { InlineNotificationTheme } from 'components/shared/InlineNotification/InlineNotification.theme'; +import React from 'react'; + +const inlineNotificationVariants = [ + 'info', + 'danger', + 'warning', + 'success', + 'generic', +]; +const inlineNotificationSizes = ['md', 'sm']; + +export const renderInlineNotifications = () => { + return inlineNotificationVariants.map((variant) => ( +
+ {inlineNotificationSizes.map((size) => ( + + ))} +
+ )); +}; + +export const renderInlineNotificationWithDescriptions = () => { + return inlineNotificationVariants.map((variant) => ( +
+ {inlineNotificationSizes.map((size) => ( + + ))} +
+ )); +}; From 075cc05db530ea4a255fe4a8afc2eac6766241ac Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:43:31 +0700 Subject: [PATCH 50/82] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20build:=20add=20@radi?= =?UTF-8?q?x-ui/react-toast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/package.json | 1 + yarn.lock | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index a9674426..0e22ca7b 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -8,6 +8,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toast": "^1.1.5", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/yarn.lock b/yarn.lock index d24c5a8a..98e99a0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3342,6 +3342,18 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-dismissable-layer@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" + integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-escape-keydown" "1.0.3" + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -3350,6 +3362,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-portal@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" + integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-presence@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" @@ -3406,6 +3426,25 @@ "@radix-ui/react-roving-focus" "1.0.4" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-toast@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.1.5.tgz#f5788761c0142a5ae9eb97f0051fd3c48106d9e6" + integrity sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-visually-hidden" "1.0.3" + "@radix-ui/react-use-callback-ref@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" @@ -3421,6 +3460,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-escape-keydown@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" + integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" @@ -3443,6 +3490,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-visually-hidden@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac" + integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@remix-run/router@1.13.1": version "1.13.1" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.1.tgz#07e2a8006f23a3bc898b3f317e0a58cc8076b86e" From 3928e2610fe2b866484ec51a045b5bcacc20a6e7 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:45:01 +0700 Subject: [PATCH 51/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20zIndex=20to?= =?UTF-8?q?ast=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/tailwind.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 4bcb0b01..8279056b 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -157,6 +157,9 @@ export default withMT({ 3.25: '0.8125rem', 3.5: '0.875rem', }, + zIndex: { + toast: '9999', + }, }, }, plugins: [], From 98a4d7be07c3bfc67aeccd327ffb71087c87bf1a Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:45:35 +0700 Subject: [PATCH 52/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20implement=20?= =?UTF-8?q?toast=20contexts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Toast/ToastProvider.tsx | 15 ++ .../src/components/shared/Toast/Toaster.tsx | 25 +++ .../src/components/shared/Toast/useToast.tsx | 190 ++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 packages/frontend/src/components/shared/Toast/ToastProvider.tsx create mode 100644 packages/frontend/src/components/shared/Toast/Toaster.tsx create mode 100644 packages/frontend/src/components/shared/Toast/useToast.tsx diff --git a/packages/frontend/src/components/shared/Toast/ToastProvider.tsx b/packages/frontend/src/components/shared/Toast/ToastProvider.tsx new file mode 100644 index 00000000..57ecf2b6 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/ToastProvider.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { + Provider, + Viewport, + type ToastProviderProps, +} from '@radix-ui/react-toast'; + +export const ToastProvider = ({ children, ...props }: ToastProviderProps) => { + return ( + + {children} + + + ); +}; diff --git a/packages/frontend/src/components/shared/Toast/Toaster.tsx b/packages/frontend/src/components/shared/Toast/Toaster.tsx new file mode 100644 index 00000000..85222167 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/Toaster.tsx @@ -0,0 +1,25 @@ +import React, { ComponentPropsWithoutRef, useMemo } from 'react'; +import { Provider, Viewport } from '@radix-ui/react-toast'; +import { SimpleToast, SimpleToastProps } from './SimpleToast'; +import { useToast } from './useToast'; + +interface ToasterProps extends ComponentPropsWithoutRef<'div'> {} + +export const Toaster = ({}: ToasterProps) => { + const { toasts } = useToast(); + + const renderToasts = useMemo( + () => + toasts.map(({ id, ...props }) => ( + + )), + [toasts], + ); + + return ( + + {renderToasts} + + + ); +}; diff --git a/packages/frontend/src/components/shared/Toast/useToast.tsx b/packages/frontend/src/components/shared/Toast/useToast.tsx new file mode 100644 index 00000000..ad088be4 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/useToast.tsx @@ -0,0 +1,190 @@ +// Inspired by react-hot-toast library +import React from 'react'; +import { type ToastProps } from '@radix-ui/react-toast'; + +const TOAST_LIMIT = 3; +const TOAST_REMOVE_DELAY_DEFAULT = 7000; + +type ToasterToast = ToastProps & { + id: string; +}; + +const actionTypes = { + ADD_TOAST: 'ADD_TOAST', + UPDATE_TOAST: 'UPDATE_TOAST', + DISMISS_TOAST: 'DISMISS_TOAST', + REMOVE_TOAST: 'REMOVE_TOAST', +} as const; + +let count = 0; + +const genId = () => { + count = (count + 1) % Number.MAX_VALUE; + return count.toString(); +}; + +type ActionType = typeof actionTypes; + +type Action = + | { + type: ActionType['ADD_TOAST']; + toast: ToasterToast; + } + | { + type: ActionType['UPDATE_TOAST']; + toast: Partial; + } + | { + type: ActionType['DISMISS_TOAST']; + toastId?: ToasterToast['id']; + duration?: ToasterToast['duration']; + } + | { + type: ActionType['REMOVE_TOAST']; + toastId?: ToasterToast['id']; + }; + +interface State { + toasts: ToasterToast[]; +} + +const toastTimeouts = new Map>(); + +const addToRemoveQueue = (toastId: string, duration: number) => { + if (toastTimeouts.has(toastId)) { + return; + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId); + dispatch({ + type: 'REMOVE_TOAST', + toastId: toastId, + }); + }, duration ?? TOAST_REMOVE_DELAY_DEFAULT); + + toastTimeouts.set(toastId, timeout); +}; + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case 'ADD_TOAST': + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + }; + + case 'UPDATE_TOAST': + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id + ? ({ ...t, ...action.toast } as ToasterToast) + : t, + ), + }; + + case 'DISMISS_TOAST': { + const { toastId, duration = TOAST_REMOVE_DELAY_DEFAULT } = action; + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId, duration); + } else { + state.toasts.forEach((_toast) => { + addToRemoveQueue(_toast.id, duration); + }); + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ), + }; + } + case 'REMOVE_TOAST': + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + }; + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + }; + } +}; + +const listeners: Array<(_state: State) => void> = []; + +let memoryState: State = { toasts: [] }; + +const dispatch = (action: Action) => { + memoryState = reducer(memoryState, action); + listeners.forEach((listener) => { + listener(memoryState); + }); +}; + +const toast = (props: ToasterToast) => { + if (!props.duration) { + props.duration = 20000; + } + const id = genId(); + + const update = (_props: ToasterToast) => + dispatch({ + type: 'UPDATE_TOAST', + toast: { ..._props, id }, + }); + const dismiss = () => + dispatch({ type: 'DISMISS_TOAST', toastId: id, duration: props.duration }); + + dispatch({ + type: 'ADD_TOAST', + toast: { + ...props, + id, + open: true, + onOpenChange: (open: boolean) => { + if (!open) dismiss(); + }, + }, + }); + + return { + id: id, + dismiss, + update, + }; +}; + +const useToast = () => { + const [state, setState] = React.useState(memoryState); + + React.useEffect(() => { + listeners.push(setState); + return () => { + const index = listeners.indexOf(setState); + if (index > -1) { + listeners.splice(index, 1); + } + }; + }, [state]); + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }), + }; +}; + +export { toast, useToast }; From d3013719e69b71738f30f2f969e49fd0d90bb857 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:45:55 +0700 Subject: [PATCH 53/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20wip=20simple=20to?= =?UTF-8?q?ast=20render?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/Toast/SimpleToast.theme.ts | 48 +++++++++++++++++++ .../components/shared/Toast/SimpleToast.tsx | 43 +++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts create mode 100644 packages/frontend/src/components/shared/Toast/SimpleToast.tsx diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts b/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts new file mode 100644 index 00000000..0d8ae24b --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts @@ -0,0 +1,48 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const simpleToastTheme = tv( + { + slots: { + wrapper: [ + 'flex', + 'py-2', + 'pl-2', + 'pr-1.5', + 'gap-2', + 'rounded-full', + 'mx-auto', + 'mt-3', + 'w-fit', + 'overflow-hidden', + 'bg-surface-high-contrast', + 'shadow-sm', + ], + icon: ['flex', 'items-center', 'justify-center', 'w-5', 'h-5'], + title: ['text-sm', 'text-elements-on-high-contrast'], + }, + variants: { + variant: { + success: { + icon: ['text-elements-success'], + }, + error: { + icon: ['text-elements-danger'], + }, + warning: { + icon: ['text-elements-warning'], + }, + info: { + icon: ['text-elements-info'], + }, + loading: { + icon: ['text-elements-info'], + }, + }, + }, + }, + { + responsiveVariants: true, + }, +); + +export type SimpleToastTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx new file mode 100644 index 00000000..e134d647 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -0,0 +1,43 @@ +import React, { useMemo } from 'react'; +import * as ToastPrimitive from '@radix-ui/react-toast'; +import { ToastProps } from '@radix-ui/react-toast'; +import { simpleToastTheme, type SimpleToastTheme } from './SimpleToast.theme'; +import { CheckIcon, CheckRoundFilledIcon } from 'components/shared/CustomIcon'; +import { cloneIcon } from 'utils/cloneIcon'; + +export interface SimpleToastProps extends ToastProps { + title: string; + variant?: SimpleToastTheme['variant']; +} + +export const SimpleToast = ({ + className, + title, + variant = 'success', + ...props +}: SimpleToastProps) => { + const { + wrapper: wrapperCls, + icon: iconCls, + title: titleCls, + } = simpleToastTheme({ variant }); + + const Icon = useMemo(() => { + if (variant === 'success') return ; + if (variant === 'error') return ; + if (variant === 'warning') return ; + if (variant === 'info') return ; + return ; // variant === 'loading' + }, [variant]); + + return ( + +
+ {cloneIcon(Icon, { className: iconCls() })} + +

{title}

+
+
+
+ ); +}; From 22c581dd332e536e33e4639cfefa59a3d810e7db Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:46:17 +0700 Subject: [PATCH 54/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomIcon/CheckRoundFilledIcon.tsx | 21 +++++++++++++++++++ .../src/components/shared/CustomIcon/index.ts | 1 + 2 files changed, 22 insertions(+) create mode 100644 packages/frontend/src/components/shared/CustomIcon/CheckRoundFilledIcon.tsx diff --git a/packages/frontend/src/components/shared/CustomIcon/CheckRoundFilledIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/CheckRoundFilledIcon.tsx new file mode 100644 index 00000000..145e68e1 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/CheckRoundFilledIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const CheckRoundFilledIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index c1ec6410..5e246238 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -5,3 +5,4 @@ export * from './ChevronGrabberHorizontal'; export * from './ChevronLeft'; export * from './ChevronRight'; export * from './GlobeIcon'; +export * from './CheckRoundFilledIcon'; From fa96b6e73478f6dc2317709a7f9a7cf6b2b22d3d Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:46:39 +0700 Subject: [PATCH 55/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20to=20compon?= =?UTF-8?q?ents=20render=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/components/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index c1c46f6c..82fa69f3 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -13,8 +13,12 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { useToast } from 'components/shared/Toast/useToast'; +import { Button } from 'components/shared/Button'; const Page = () => { + const { toast } = useToast(); + const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); @@ -31,6 +35,13 @@ const Page = () => {
+ {/* Toast */} +
+

Toasts

+ +
{/* Button */}

Button

From 9dec48d67c6a0920e2713a041cd9b673748defa9 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Thu, 22 Feb 2024 12:24:25 +0700 Subject: [PATCH 56/82] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20add=20di?= =?UTF-8?q?sabled=20button=20example=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 32 ++++--- .../src/pages/components/renders/button.tsx | 90 ++++++++++++------- 2 files changed, 78 insertions(+), 44 deletions(-) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index b2c81d31..67e68e0b 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -10,6 +10,7 @@ import { renderBadges } from './renders/badge'; import { renderButtonIcons, renderButtons, + renderDisabledButtons, renderLinks, } from './renders/button'; import { @@ -53,6 +54,27 @@ const Page = () => { {renderButtonIcons()}
+ {/* Link */} +
+

Link

+
+ {renderLinks()} +
+
+ + {/* Disabled button, icon only, and link */} +
+
+

Disabled

+

+ Button – icon only – link +

+
+
+ {renderDisabledButtons()} +
+
+
{/* Badge */} @@ -139,16 +161,6 @@ const Page = () => { {renderInlineNotificationWithDescriptions()}
- -
- - {/* Link */} -
-

Link

-
- {renderLinks()} -
-
diff --git a/packages/frontend/src/pages/components/renders/button.tsx b/packages/frontend/src/pages/components/renders/button.tsx index f5272d3e..0b86aaaf 100644 --- a/packages/frontend/src/pages/components/renders/button.tsx +++ b/packages/frontend/src/pages/components/renders/button.tsx @@ -2,44 +2,45 @@ import { Button, ButtonTheme } from 'components/shared/Button'; import { PlusIcon } from 'components/shared/CustomIcon'; import React from 'react'; +const buttonVariants = ['primary', 'secondary', 'tertiary', 'danger'] as const; +const buttonSizes = ['lg', 'md', 'sm', 'xs'] as const; +const iconOnlyVariants = [ + 'primary', + 'secondary', + 'tertiary', + 'ghost', + 'danger', + 'danger-ghost', +] as const; +const linkVariants = ['link', 'link-emphasized'] as const; + export const renderButtons = () => { - return ['primary', 'secondary', 'tertiary', 'danger'].map( - (variant, index) => ( -
- {['lg', 'md', 'sm', 'xs', 'disabled'].map((size) => ( - - ))} -
- ), - ); + return buttonVariants.map((variant, index) => ( +
+ {buttonSizes.map((size) => ( + + ))} +
+ )); }; export const renderButtonIcons = () => { - return [ - 'primary', - 'secondary', - 'tertiary', - 'ghost', - 'danger', - 'danger-ghost', - ].map((variant, index) => ( + return iconOnlyVariants.map((variant, index) => (
- {['lg', 'md', 'sm', 'xs', 'disabled'].map((size) => ( + {buttonSizes.map((size) => ( @@ -49,17 +50,38 @@ export const renderButtonIcons = () => { }; export const renderLinks = () => { - return ['link', 'link-emphasized', 'disabled'].map((variant) => ( + return linkVariants.map((variant) => ( )); }; + +export const renderDisabledButtons = () => { + return ( + <> + {/* Button variants */} + + {/* Icon only variants */} + + {/* Link variantws */} + + + ); +}; From ef9f4df28aa514743e5adaad7cbb7957fbfc78ca Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:13:10 +0700 Subject: [PATCH 57/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20ghost=20button=20?= =?UTF-8?q?is=20white,=20not=20transparent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Button/Button.theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Button/Button.theme.ts b/packages/frontend/src/components/shared/Button/Button.theme.ts index 6c9614c6..194c6550 100644 --- a/packages/frontend/src/components/shared/Button/Button.theme.ts +++ b/packages/frontend/src/components/shared/Button/Button.theme.ts @@ -62,7 +62,7 @@ export const buttonTheme = tv( 'text-elements-on-tertiary', 'border', 'border-border-interactive/10', - 'bg-transparent', + 'bg-controls-tertiary', 'hover:bg-controls-tertiary-hovered', 'hover:border-border-interactive-hovered', 'hover:border-border-interactive-hovered/[0.14]', From 028dd35db1df49f60e5f6dbabda973107c562cff Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:13:27 +0700 Subject: [PATCH 58/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/CustomIcon/InfoRoundFilledIcon.tsx | 21 +++++++++++++++++++ .../shared/CustomIcon/LoadingIcon.tsx | 21 +++++++++++++++++++ .../src/components/shared/CustomIcon/index.ts | 2 ++ 3 files changed, 44 insertions(+) create mode 100644 packages/frontend/src/components/shared/CustomIcon/InfoRoundFilledIcon.tsx create mode 100644 packages/frontend/src/components/shared/CustomIcon/LoadingIcon.tsx diff --git a/packages/frontend/src/components/shared/CustomIcon/InfoRoundFilledIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/InfoRoundFilledIcon.tsx new file mode 100644 index 00000000..d7eb24c4 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/InfoRoundFilledIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const InfoRoundFilledIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/LoadingIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/LoadingIcon.tsx new file mode 100644 index 00000000..012fa986 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/LoadingIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const LoadingIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index 7b84840e..cb5f9f37 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -10,3 +10,5 @@ export * from './SearchIcon'; export * from './CrossIcon'; export * from './GlobeIcon'; export * from './CheckRoundFilledIcon'; +export * from './InfoRoundFilledIcon'; +export * from './LoadingIcon'; From 5836779403f7004ff3be8b6c6a4b25c4a00dac24 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:13:47 +0700 Subject: [PATCH 59/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20export=20buttonBa?= =?UTF-8?q?seProps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Button/Button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Button/Button.tsx b/packages/frontend/src/components/shared/Button/Button.tsx index af845e0c..6033a440 100644 --- a/packages/frontend/src/components/shared/Button/Button.tsx +++ b/packages/frontend/src/components/shared/Button/Button.tsx @@ -9,7 +9,7 @@ import { cloneIcon } from 'utils/cloneIcon'; /** * Represents the properties of a base button component. */ -interface ButtonBaseProps { +export interface ButtonBaseProps { /** * The optional left icon element for a component. * @type {ReactNode} From cb928c3360303255a1b86f937a415f5ea42a8943 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:16:21 +0700 Subject: [PATCH 60/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20use=20buttonbasep?= =?UTF-8?q?rops,=20duration=202000=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/components/shared/Toast/useToast.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/useToast.tsx b/packages/frontend/src/components/shared/Toast/useToast.tsx index ad088be4..bd0be17e 100644 --- a/packages/frontend/src/components/shared/Toast/useToast.tsx +++ b/packages/frontend/src/components/shared/Toast/useToast.tsx @@ -1,13 +1,15 @@ // Inspired by react-hot-toast library import React from 'react'; import { type ToastProps } from '@radix-ui/react-toast'; +import { SimpleToastProps } from './SimpleToast'; const TOAST_LIMIT = 3; const TOAST_REMOVE_DELAY_DEFAULT = 7000; -type ToasterToast = ToastProps & { - id: string; -}; +type ToasterToast = ToastProps & + SimpleToastProps & { + id: string; + }; const actionTypes = { ADD_TOAST: 'ADD_TOAST', @@ -136,7 +138,7 @@ const dispatch = (action: Action) => { const toast = (props: ToasterToast) => { if (!props.duration) { - props.duration = 20000; + props.duration = 2000; } const id = genId(); From 675112079c5676a4dcce1cde3c1ceaed3f8083d5 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:16:47 +0700 Subject: [PATCH 61/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20implement=20?= =?UTF-8?q?toast=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/Toast/SimpleToast.theme.ts | 10 ++++ .../components/shared/Toast/SimpleToast.tsx | 51 ++++++++++++++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts b/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts index 0d8ae24b..bac805ce 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts @@ -5,6 +5,7 @@ export const simpleToastTheme = tv( slots: { wrapper: [ 'flex', + 'items-center', 'py-2', 'pl-2', 'pr-1.5', @@ -18,6 +19,15 @@ export const simpleToastTheme = tv( 'shadow-sm', ], icon: ['flex', 'items-center', 'justify-center', 'w-5', 'h-5'], + closeIcon: [ + 'cursor-pointer', + 'flex', + 'items-center', + 'justify-center', + 'w-6', + 'h-6', + 'text-elements-on-high-contrast', + ], title: ['text-sm', 'text-elements-on-high-contrast'], }, variants: { diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index e134d647..b3bafc21 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -2,41 +2,80 @@ import React, { useMemo } from 'react'; import * as ToastPrimitive from '@radix-ui/react-toast'; import { ToastProps } from '@radix-ui/react-toast'; import { simpleToastTheme, type SimpleToastTheme } from './SimpleToast.theme'; -import { CheckIcon, CheckRoundFilledIcon } from 'components/shared/CustomIcon'; +import { + LoadingIcon, + CheckRoundFilledIcon, + CrossIcon, + InfoRoundFilledIcon, + WarningIcon, +} from 'components/shared/CustomIcon'; +import { Button, ButtonBaseProps, ButtonTheme } from 'components/shared/Button'; import { cloneIcon } from 'utils/cloneIcon'; +import { cn } from 'utils/classnames'; +interface CtaProps extends ButtonBaseProps, ButtonTheme { + buttonLabel: string; +} export interface SimpleToastProps extends ToastProps { title: string; variant?: SimpleToastTheme['variant']; + cta?: CtaProps[]; + onDismiss: (id?: string) => void; } export const SimpleToast = ({ className, title, variant = 'success', + cta = [], + onDismiss, ...props }: SimpleToastProps) => { + const hasCta = cta.length > 0; const { wrapper: wrapperCls, icon: iconCls, + closeIcon: closeIconCls, title: titleCls, } = simpleToastTheme({ variant }); const Icon = useMemo(() => { if (variant === 'success') return ; - if (variant === 'error') return ; - if (variant === 'warning') return ; - if (variant === 'info') return ; - return ; // variant === 'loading' + if (variant === 'error') return ; + if (variant === 'warning') return ; + if (variant === 'info') return ; + return ; // variant === 'loading' }, [variant]); + const renderCta = useMemo( + () => + hasCta ? ( +
+ {cta.map(({ buttonLabel, ...props }, index) => ( + + ))} +
+ ) : null, + [], + ); + + const renderCloseButton = () => ( +
onDismiss(props.id)} className={closeIconCls()}> + +
+ ); + return ( -
+
{cloneIcon(Icon, { className: iconCls() })}

{title}

+ {renderCta} + {renderCloseButton()}
); From bb7a88f7e97e7e71e82f94fee1d73c264d690792 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:17:02 +0700 Subject: [PATCH 62/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20toast=20ren?= =?UTF-8?q?der=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 13 ++-- .../src/pages/components/renders/toast.tsx | 66 +++++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 packages/frontend/src/pages/components/renders/toast.tsx diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 5866cdde..89403520 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -17,17 +17,14 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; -import { useToast } from 'components/shared/Toast/useToast'; -import { Button } from 'components/shared/Button'; import { renderInlineNotificationWithDescriptions, renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; +import { renderToast, renderToastsWithCta } from './renders/toast'; const Page = () => { - const { toast } = useToast(); - const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); @@ -47,10 +44,12 @@ const Page = () => { {/* Toast */}

Toasts

- + {renderToastsWithCta()} + {renderToast()}
+ +
+ {/* Button */}

Input

diff --git a/packages/frontend/src/pages/components/renders/toast.tsx b/packages/frontend/src/pages/components/renders/toast.tsx new file mode 100644 index 00000000..9b066e8c --- /dev/null +++ b/packages/frontend/src/pages/components/renders/toast.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Button } from 'components/shared/Button'; +import { useToast } from 'components/shared/Toast/useToast'; + +export const renderToastsWithCta = () => { + const { toast, dismiss } = useToast(); + + return ( +
+ {(['success', 'error', 'warning', 'info', 'loading'] as const).map( + (variant, index) => ( + + ), + )} +
+ ); +}; + +export const renderToast = () => { + const { toast, dismiss } = useToast(); + + return ( +
+ {(['success', 'error', 'warning', 'info', 'loading'] as const).map( + (variant, index) => ( + + ), + )} +
+ ); +}; From c8a153ad279271583b3f6288d72d11bd1f219098 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:20:26 +0700 Subject: [PATCH 63/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20usememo=20depende?= =?UTF-8?q?ncies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/shared/Toast/SimpleToast.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index b3bafc21..405cff0d 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -58,13 +58,16 @@ export const SimpleToast = ({ ))}
) : null, - [], + [cta], ); - const renderCloseButton = () => ( -
onDismiss(props.id)} className={closeIconCls()}> - -
+ const renderCloseButton = useMemo( + () => ( +
onDismiss(props.id)} className={closeIconCls()}> + +
+ ), + [], ); return ( @@ -75,7 +78,7 @@ export const SimpleToast = ({

{title}

{renderCta} - {renderCloseButton()} + {renderCloseButton}
); From 1cd60d84dd6728ac81d555bd2e3968a005d8bee7 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:22:29 +0700 Subject: [PATCH 64/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20use=20framer-moti?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/shared/Toast/SimpleToast.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index 405cff0d..001ac32b 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import * as ToastPrimitive from '@radix-ui/react-toast'; import { ToastProps } from '@radix-ui/react-toast'; +import { motion } from 'framer-motion'; import { simpleToastTheme, type SimpleToastTheme } from './SimpleToast.theme'; import { LoadingIcon, @@ -11,7 +12,6 @@ import { } from 'components/shared/CustomIcon'; import { Button, ButtonBaseProps, ButtonTheme } from 'components/shared/Button'; import { cloneIcon } from 'utils/cloneIcon'; -import { cn } from 'utils/classnames'; interface CtaProps extends ButtonBaseProps, ButtonTheme { buttonLabel: string; @@ -72,14 +72,22 @@ export const SimpleToast = ({ return ( -
+ {cloneIcon(Icon, { className: iconCls() })}

{title}

{renderCta} {renderCloseButton} -
+
); }; From fffc370d40636adae9700a001f64ea1cb31fab9f Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:58:57 +0700 Subject: [PATCH 65/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20rework=20usememo?= =?UTF-8?q?=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/components/shared/Toast/SimpleToast.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index 001ac32b..d780640f 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -47,9 +47,9 @@ export const SimpleToast = ({ return ; // variant === 'loading' }, [variant]); - const renderCta = useMemo( - () => - hasCta ? ( + const renderCta = useMemo(() => { + if (!hasCta) return null; + return (
{cta.map(({ buttonLabel, ...props }, index) => ( ))}
- ) : null, - [cta], ); + }, [cta]); const renderCloseButton = useMemo( () => ( From 5be29a9745aa08310edc3ebfcd2dc4112367d5c7 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:59:12 +0700 Subject: [PATCH 66/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20id=20to=20a?= =?UTF-8?q?llow=20singular=20toast=20deletion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Toast/SimpleToast.tsx | 24 ++++++++++--------- .../src/components/shared/Toast/Toaster.tsx | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index d780640f..c15f2d45 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -17,13 +17,15 @@ interface CtaProps extends ButtonBaseProps, ButtonTheme { buttonLabel: string; } export interface SimpleToastProps extends ToastProps { + id: string; title: string; variant?: SimpleToastTheme['variant']; cta?: CtaProps[]; - onDismiss: (id?: string) => void; + onDismiss: (toastId: string) => void; } export const SimpleToast = ({ + id, className, title, variant = 'success', @@ -50,23 +52,23 @@ export const SimpleToast = ({ const renderCta = useMemo(() => { if (!hasCta) return null; return ( -
- {cta.map(({ buttonLabel, ...props }, index) => ( - - ))} -
- ); +
+ {cta.map(({ buttonLabel, ...props }, index) => ( + + ))} +
+ ); }, [cta]); const renderCloseButton = useMemo( () => ( -
onDismiss(props.id)} className={closeIconCls()}> +
onDismiss(id)} className={closeIconCls()}>
), - [], + [id], ); return ( diff --git a/packages/frontend/src/components/shared/Toast/Toaster.tsx b/packages/frontend/src/components/shared/Toast/Toaster.tsx index 85222167..66556956 100644 --- a/packages/frontend/src/components/shared/Toast/Toaster.tsx +++ b/packages/frontend/src/components/shared/Toast/Toaster.tsx @@ -11,7 +11,7 @@ export const Toaster = ({}: ToasterProps) => { const renderToasts = useMemo( () => toasts.map(({ id, ...props }) => ( - + )), [toasts], ); From 4d646e1ac08b4e5ab4e1f0fc8c7ef1886b09aaca Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 15:03:21 +0700 Subject: [PATCH 67/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20extend=20to=20but?= =?UTF-8?q?tonOrLinkProps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/components/shared/Toast/SimpleToast.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index c15f2d45..7c10f855 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -10,12 +10,12 @@ import { InfoRoundFilledIcon, WarningIcon, } from 'components/shared/CustomIcon'; -import { Button, ButtonBaseProps, ButtonTheme } from 'components/shared/Button'; +import { Button, type ButtonOrLinkProps } from 'components/shared/Button'; import { cloneIcon } from 'utils/cloneIcon'; -interface CtaProps extends ButtonBaseProps, ButtonTheme { +type CtaProps = ButtonOrLinkProps & { buttonLabel: string; -} +}; export interface SimpleToastProps extends ToastProps { id: string; title: string; From ba87c6f22a037b43c35815e3092de9bfd3d30bf6 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 16:11:37 +0700 Subject: [PATCH 68/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20replace=20react-h?= =?UTF-8?q?ot-toast=20with=20custom=20toast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx index da29f575..cc0281f1 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import assert from 'assert'; -import { Toaster } from 'react-hot-toast'; import { GQLClient } from 'gql-client'; import { ThemeProvider } from '@material-tailwind/react'; @@ -11,6 +10,7 @@ import '@fontsource/inter'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { GQLClientProvider } from './context/GQLClientContext'; +import { Toaster } from 'components/shared/Toast/Toaster'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, @@ -26,7 +26,7 @@ root.render( - + , From 200ea3ca7bd6714cb1e7a223def942cc979d312a Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 16:14:27 +0700 Subject: [PATCH 69/82] =?UTF-8?q?=F0=9F=90=9B=20fix:=20aria-hidden=20namin?= =?UTF-8?q?g=20on=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Input/Input.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/shared/Input/Input.tsx b/packages/frontend/src/components/shared/Input/Input.tsx index f5bbdca5..d2d5459a 100644 --- a/packages/frontend/src/components/shared/Input/Input.tsx +++ b/packages/frontend/src/components/shared/Input/Input.tsx @@ -55,7 +55,7 @@ export const Input = ({ const renderLeftIcon = useMemo(() => { return (
- {cloneIcon(leftIcon, { className: iconCls(), ariaHidden: true })} + {cloneIcon(leftIcon, { className: iconCls(), 'aria-hidden': true })}
); }, [cloneIcon, iconCls, iconContainerCls, leftIcon]); @@ -63,7 +63,7 @@ export const Input = ({ const renderRightIcon = useMemo(() => { return (
- {cloneIcon(rightIcon, { className: iconCls(), ariaHidden: true })} + {cloneIcon(rightIcon, { className: iconCls(), 'aria-hidden': true })}
); }, [cloneIcon, iconCls, iconContainerCls, rightIcon]); @@ -73,7 +73,7 @@ export const Input = ({
{state && cloneIcon(, { - ariaHidden: true, + 'aria-hidden': true, })}

{helperText}

From fb932eeb0404c59501eb04eed6b903ba04281a62 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 17:01:36 +0700 Subject: [PATCH 70/82] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20implement=20?= =?UTF-8?q?tags=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/shared/Tags/Tags.theme.ts | 93 +++++++++++++++++++ .../src/components/shared/Tags/Tags.tsx | 75 +++++++++++++++ .../src/components/shared/Tags/index.ts | 2 + 3 files changed, 170 insertions(+) create mode 100644 packages/frontend/src/components/shared/Tags/Tags.theme.ts create mode 100644 packages/frontend/src/components/shared/Tags/Tags.tsx create mode 100644 packages/frontend/src/components/shared/Tags/index.ts diff --git a/packages/frontend/src/components/shared/Tags/Tags.theme.ts b/packages/frontend/src/components/shared/Tags/Tags.theme.ts new file mode 100644 index 00000000..299f31ea --- /dev/null +++ b/packages/frontend/src/components/shared/Tags/Tags.theme.ts @@ -0,0 +1,93 @@ +import { tv } from 'tailwind-variants'; +import type { VariantProps } from 'tailwind-variants'; + +export const tagsTheme = tv( + { + slots: { + wrapper: ['flex', 'gap-1.5', 'rounded-lg', 'border'], + icon: ['h-4', 'w-4'], + label: ['font-inter', 'text-xs'], + }, + variants: { + type: { + attention: { + icon: ['text-elements-warning'], + }, + negative: { + icon: ['text-elements-danger'], + }, + positive: { + icon: ['text-elements-success'], + }, + emphasized: { + icon: ['text-elements-on-secondary'], + }, + neutral: { + icon: ['text-elements-mid-em'], + }, + }, + style: { + default: {}, + minimal: { + wrapper: ['border-border-interactive', 'bg-controls-tertiary'], + label: ['text-elements-high-em'], + }, + }, + size: { + sm: { + wrapper: ['px-2', 'py-2'], + }, + xs: { + wrapper: ['px-2', 'py-1.5'], + }, + }, + }, + compoundVariants: [ + { + type: 'attention', + style: 'default', + class: { + wrapper: ['border-orange-200', 'bg-orange-50'], + }, + }, + { + type: 'negative', + style: 'default', + class: { + wrapper: ['border-rose-200', 'bg-rose-50'], + }, + }, + { + type: 'positive', + style: 'default', + class: { + wrapper: ['border-emerald-200', 'bg-emerald-50'], + }, + }, + { + type: 'emphasized', + style: 'default', + class: { + wrapper: ['border-snowball-200', 'bg-snowball-50'], + }, + }, + { + type: 'neutral', + style: 'default', + class: { + wrapper: ['border-gray-200', 'bg-gray-50'], + }, + }, + ], + defaultVariants: { + type: 'attention', + style: 'default', + size: 'sm', + }, + }, + { + responsiveVariants: true, + }, +); + +export type TagsTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Tags/Tags.tsx b/packages/frontend/src/components/shared/Tags/Tags.tsx new file mode 100644 index 00000000..9cdb1231 --- /dev/null +++ b/packages/frontend/src/components/shared/Tags/Tags.tsx @@ -0,0 +1,75 @@ +import React, { + type ReactNode, + type ComponentPropsWithoutRef, + useMemo, +} from 'react'; +import { TagsTheme, tagsTheme } from './Tags.theme'; +import { cloneIcon } from 'utils/cloneIcon'; + +type TagsProps = ComponentPropsWithoutRef<'div'> & + TagsTheme & { + /** + * The optional left icon element for a component. + * @type {ReactNode} + */ + leftIcon?: ReactNode; + /** + * The optional right icon element to display. + * @type {ReactNode} + */ + rightIcon?: ReactNode; + /** + * The optional type of the tags component. + * @type {TagsTheme['type']} + **/ + type?: TagsTheme['type']; + /** + * The optional style of the tags component. + * @type {TagsTheme['style']} + */ + style?: TagsTheme['style']; + /** + * The optional size of the tags component. + * @type {TagsTheme['size']} + */ + size?: TagsTheme['size']; + }; + +export const Tags = ({ + children, + leftIcon, + rightIcon, + type = 'attention', + style = 'default', + size = 'sm', +}: TagsProps) => { + const { + wrapper: wrapperCls, + icon: iconCls, + label: labelCls, + } = tagsTheme({ + type, + style, + size, + }); + + const renderLeftIcon = useMemo(() => { + if (!leftIcon) return null; + return
{cloneIcon(leftIcon, { size: 16 })}
; + }, [iconCls, leftIcon]); + + const renderRightIcon = useMemo(() => { + if (!rightIcon) return null; + return ( +
{cloneIcon(rightIcon, { size: 16 })}
+ ); + }, [iconCls, rightIcon]); + + return ( +
+ {renderLeftIcon} +

{children}

+ {renderRightIcon} +
+ ); +}; diff --git a/packages/frontend/src/components/shared/Tags/index.ts b/packages/frontend/src/components/shared/Tags/index.ts new file mode 100644 index 00000000..de1af198 --- /dev/null +++ b/packages/frontend/src/components/shared/Tags/index.ts @@ -0,0 +1,2 @@ +export * from './Tags'; +export * from './Tags.theme'; From 596889b5e2b70feab7a0c1ccddfb8daed6895b48 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 17:01:58 +0700 Subject: [PATCH 71/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20render=20ta?= =?UTF-8?q?gs=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 14 +++++ .../src/pages/components/renders/tags.tsx | 63 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 packages/frontend/src/pages/components/renders/tags.tsx diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 67e68e0b..2da8cc9e 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -23,6 +23,7 @@ import { renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; +import { renderDefaultTags, renderMinimalTags } from './renders/tags'; const Page = () => { const [singleDate, setSingleDate] = useState(); @@ -41,6 +42,19 @@ const Page = () => {
+ {/* Tags */} +
+
+

Tags

+
+ {renderDefaultTags()} + {renderMinimalTags()} +
+
+
+ +
+ {/* Button */}

Input

diff --git a/packages/frontend/src/pages/components/renders/tags.tsx b/packages/frontend/src/pages/components/renders/tags.tsx new file mode 100644 index 00000000..760b7313 --- /dev/null +++ b/packages/frontend/src/pages/components/renders/tags.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Tags } from 'components/shared/Tags'; +import { PlusIcon } from 'components/shared/CustomIcon'; + +export const renderDefaultTags = () => + (['default'] as const).map((style) => ( +
+ {( + ['attention', 'negative', 'positive', 'emphasized', 'neutral'] as const + ).map((type) => ( +
+ } + rightIcon={} + style={style} + type={type} + size="sm" + > + Label + + } + rightIcon={} + size="xs" + style={style} + type={type} + > + Label + +
+ ))} +
+ )); + +export const renderMinimalTags = () => + (['minimal'] as const).map((style) => ( +
+ {( + ['attention', 'negative', 'positive', 'emphasized', 'neutral'] as const + ).map((type) => ( +
+ } + rightIcon={} + style={style} + type={type} + size="sm" + > + Label + + } + rightIcon={} + size="xs" + style={style} + type={type} + > + Label + +
+ ))} +
+ )); From 32918b793016d3835b0243c5735c28180f6c7308 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 17:07:15 +0700 Subject: [PATCH 72/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20index=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Toast/index.ts | 2 ++ packages/frontend/src/index.tsx | 3 +-- packages/frontend/src/pages/components/renders/toast.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 packages/frontend/src/components/shared/Toast/index.ts diff --git a/packages/frontend/src/components/shared/Toast/index.ts b/packages/frontend/src/components/shared/Toast/index.ts new file mode 100644 index 00000000..6404e342 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/index.ts @@ -0,0 +1,2 @@ +export * from './Toaster'; +export * from './useToast'; diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx index cc0281f1..bdfe6f6d 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -9,8 +9,7 @@ import './index.css'; import '@fontsource/inter'; import App from './App'; import reportWebVitals from './reportWebVitals'; -import { GQLClientProvider } from './context/GQLClientContext'; -import { Toaster } from 'components/shared/Toast/Toaster'; +import { Toaster } from 'components/shared/Toast'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, diff --git a/packages/frontend/src/pages/components/renders/toast.tsx b/packages/frontend/src/pages/components/renders/toast.tsx index 9b066e8c..fb19e33a 100644 --- a/packages/frontend/src/pages/components/renders/toast.tsx +++ b/packages/frontend/src/pages/components/renders/toast.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Button } from 'components/shared/Button'; -import { useToast } from 'components/shared/Toast/useToast'; +import { useToast } from 'components/shared/Toast'; export const renderToastsWithCta = () => { const { toast, dismiss } = useToast(); From 8952393b31468520a2069e880eb24052da97573b Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 17:08:05 +0700 Subject: [PATCH 73/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20remove=20not=20wo?= =?UTF-8?q?rking=20swipe=20down=20gesture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Toast/ToastProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Toast/ToastProvider.tsx b/packages/frontend/src/components/shared/Toast/ToastProvider.tsx index 57ecf2b6..d45d6e89 100644 --- a/packages/frontend/src/components/shared/Toast/ToastProvider.tsx +++ b/packages/frontend/src/components/shared/Toast/ToastProvider.tsx @@ -7,7 +7,7 @@ import { export const ToastProvider = ({ children, ...props }: ToastProviderProps) => { return ( - + {children} From 0e7343da6f016db569ec99bc54d6a46efe9503f1 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 17:11:03 +0700 Subject: [PATCH 74/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20gqlclientprovider?= =?UTF-8?q?=20import?= 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 bdfe6f6d..d0c0d9b7 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -9,6 +9,7 @@ import './index.css'; import '@fontsource/inter'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import { GQLClientProvider } from './context/GQLClientContext'; import { Toaster } from 'components/shared/Toast'; const root = ReactDOM.createRoot( From 30bbe4d766de1741000a1573fc28b0355a9eb36e Mon Sep 17 00:00:00 2001 From: Zachery Date: Thu, 22 Feb 2024 18:25:04 +0800 Subject: [PATCH 75/82] [T-4870] Tooltip component (#96) * fix: button forwardRef * feat: tooltip component --- packages/frontend/package.json | 1 + .../src/components/shared/Button/Button.tsx | 20 ++- .../shared/Tooltip/Tooltip.theme.ts | 14 ++ .../src/components/shared/Tooltip/Tooltip.tsx | 47 +++++++ .../components/shared/Tooltip/TooltipBase.tsx | 38 ++++++ .../Tooltip/TooltipContent/TooltipContent.tsx | 46 +++++++ .../shared/Tooltip/TooltipContent/index.ts | 1 + .../Tooltip/TooltipTrigger/TooltipTrigger.tsx | 12 ++ .../shared/Tooltip/TooltipTrigger/index.ts | 1 + .../src/components/shared/Tooltip/index.ts | 2 + .../frontend/src/pages/components/index.tsx | 11 +- .../src/pages/components/renders/tooltip.tsx | 30 +++++ packages/frontend/tailwind.config.js | 9 +- yarn.lock | 122 ++++++++++++++++++ 14 files changed, 344 insertions(+), 10 deletions(-) create mode 100644 packages/frontend/src/components/shared/Tooltip/Tooltip.theme.ts create mode 100644 packages/frontend/src/components/shared/Tooltip/Tooltip.tsx create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipBase.tsx create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipContent/TooltipContent.tsx create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipContent/index.ts create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipTrigger/TooltipTrigger.tsx create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipTrigger/index.ts create mode 100644 packages/frontend/src/components/shared/Tooltip/index.ts create mode 100644 packages/frontend/src/pages/components/renders/tooltip.tsx diff --git a/packages/frontend/package.json b/packages/frontend/package.json index f31fa586..8daa876c 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -8,6 +8,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-tooltip": "^1.0.7", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/packages/frontend/src/components/shared/Button/Button.tsx b/packages/frontend/src/components/shared/Button/Button.tsx index af845e0c..968cca72 100644 --- a/packages/frontend/src/components/shared/Button/Button.tsx +++ b/packages/frontend/src/components/shared/Button/Button.tsx @@ -98,19 +98,29 @@ const Button = forwardRef< href, ...baseLinkProps, }; - return {_children}; + return ( + // @ts-expect-error - ref + + {_children} + + ); } // Internal link return ( - + // @ts-expect-error - ref + {_children} ); } else { const { ...buttonProps } = _props; - // @ts-expect-error - as prop is not a valid prop for button elements - return ; + return ( + // @ts-expect-error - as prop is not a valid prop for button elements + + ); } }, [], @@ -161,8 +171,6 @@ const Button = forwardRef< return ( {cloneIcon(leftIcon, { ...iconSize })} diff --git a/packages/frontend/src/components/shared/Tooltip/Tooltip.theme.ts b/packages/frontend/src/components/shared/Tooltip/Tooltip.theme.ts new file mode 100644 index 00000000..c4c1c5e0 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/Tooltip.theme.ts @@ -0,0 +1,14 @@ +import { tv } from 'tailwind-variants'; + +export const tooltipTheme = tv({ + slots: { + content: [ + 'z-tooltip', + 'rounded-md', + 'bg-surface-high-contrast', + 'p-2', + 'text-elements-on-high-contrast', + ], + arrow: ['fill-surface-high-contrast'], + }, +}); diff --git a/packages/frontend/src/components/shared/Tooltip/Tooltip.tsx b/packages/frontend/src/components/shared/Tooltip/Tooltip.tsx new file mode 100644 index 00000000..75d73c78 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/Tooltip.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import type { + TooltipContentProps, + TooltipTriggerProps, +} from '@radix-ui/react-tooltip'; +import { ReactNode, useState } from 'react'; + +import { TooltipBase, type TooltipBaseProps } from './TooltipBase'; + +export interface TooltipProps extends TooltipBaseProps { + triggerProps?: TooltipTriggerProps; + contentProps?: TooltipContentProps; + content?: ReactNode; +} + +// https://github.com/radix-ui/primitives/issues/955#issuecomment-1798201143 +// Wrap on top of Tooltip base to make tooltip open on mobile via click +export const Tooltip = ({ + children, + triggerProps, + contentProps, + content, + ...props +}: TooltipProps) => { + const [isTooltipVisible, setIsTooltipVisible] = useState(false); + + return ( + + setIsTooltipVisible(false)} + onClick={() => setIsTooltipVisible((prevOpen) => !prevOpen)} + onFocus={() => setTimeout(() => setIsTooltipVisible(true), 0)} + {...triggerProps} + > + {triggerProps?.children ?? children} + + + {content ?? contentProps?.children ?? 'Coming soon'} + + + ); +}; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipBase.tsx b/packages/frontend/src/components/shared/Tooltip/TooltipBase.tsx new file mode 100644 index 00000000..273a9cbe --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipBase.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { + Provider, + TooltipProps as RadixTooltipProps, + Root, + type TooltipProviderProps, +} from '@radix-ui/react-tooltip'; +import { useState, type PropsWithChildren } from 'react'; + +import { TooltipContent } from './TooltipContent'; +import { TooltipTrigger } from './TooltipTrigger'; + +export interface TooltipBaseProps extends RadixTooltipProps { + providerProps?: TooltipProviderProps; +} + +export const TooltipBase = ({ + children, + providerProps, + ...props +}: PropsWithChildren) => { + const [isTooltipVisible, setIsTooltipVisible] = useState(false); + + return ( + + + {children} + + + ); +}; + +TooltipBase.Trigger = TooltipTrigger; +TooltipBase.Content = TooltipContent; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipContent/TooltipContent.tsx b/packages/frontend/src/components/shared/Tooltip/TooltipContent/TooltipContent.tsx new file mode 100644 index 00000000..57678206 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipContent/TooltipContent.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { + Arrow, + Content, + Portal, + type TooltipArrowProps, + type TooltipContentProps, +} from '@radix-ui/react-tooltip'; + +import { tooltipTheme } from '../Tooltip.theme'; + +export interface ContentProps extends TooltipContentProps { + hasArrow?: boolean; + arrowProps?: TooltipArrowProps; +} + +export const TooltipContent = ({ + children, + arrowProps, + className, + hasArrow = true, + ...props +}: ContentProps) => { + const { content, arrow } = tooltipTheme(); + return ( + + + {hasArrow && ( + + )} + {children} + + + ); +}; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipContent/index.ts b/packages/frontend/src/components/shared/Tooltip/TooltipContent/index.ts new file mode 100644 index 00000000..1675248c --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipContent/index.ts @@ -0,0 +1 @@ +export * from './TooltipContent'; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/TooltipTrigger.tsx b/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/TooltipTrigger.tsx new file mode 100644 index 00000000..c27993ed --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/TooltipTrigger.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Trigger, type TooltipTriggerProps } from '@radix-ui/react-tooltip'; + +export type TriggerProps = TooltipTriggerProps; + +export const TooltipTrigger = ({ children, ...props }: TriggerProps) => { + return ( + + {children} + + ); +}; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/index.ts b/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/index.ts new file mode 100644 index 00000000..e73d66c6 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/index.ts @@ -0,0 +1 @@ +export * from './TooltipTrigger'; diff --git a/packages/frontend/src/components/shared/Tooltip/index.ts b/packages/frontend/src/components/shared/Tooltip/index.ts new file mode 100644 index 00000000..a51f2a31 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/index.ts @@ -0,0 +1,2 @@ +export * from './Tooltip'; +export * from './TooltipBase'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 67e68e0b..bfe02385 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -23,6 +23,7 @@ import { renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; +import { renderTooltips } from './renders/tooltip'; const Page = () => { const [singleDate, setSingleDate] = useState(); @@ -41,13 +42,21 @@ const Page = () => {
- {/* Button */}
+

Tooltip

+
+ {renderTooltips()} +
+ +
+ + {/* Input */}

Input

{renderInputs()}
+ {/* Button */}

Button

{renderButtons()} diff --git a/packages/frontend/src/pages/components/renders/tooltip.tsx b/packages/frontend/src/pages/components/renders/tooltip.tsx new file mode 100644 index 00000000..7114f4db --- /dev/null +++ b/packages/frontend/src/pages/components/renders/tooltip.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Button } from 'components/shared/Button'; +import { Tooltip } from 'components/shared/Tooltip'; +import { ContentProps } from 'components/shared/Tooltip/TooltipContent'; + +const alignments: ContentProps['align'][] = ['start', 'center', 'end']; +const sides: ContentProps['side'][] = ['left', 'top', 'bottom', 'right']; + +export const renderTooltips = () => { + const tooltips = sides.map((side) => { + return alignments.map((align) => { + return ( + + + + ); + }); + }); + return tooltips; +}; diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 4bcb0b01..9a814026 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -8,10 +8,13 @@ export default withMT({ '../../node_modules/@material-tailwind/react/theme/components/**/*.{js,ts,jsx,tsx}', ], theme: { - fontFamily: { - sans: ['Inter', 'sans-serif'], - }, extend: { + zIndex: { + tooltip: '52', + }, + fontFamily: { + sans: ['Inter', 'sans-serif'], + }, fontSize: { '2xs': '0.625rem', '3xs': '0.5rem', diff --git a/yarn.lock b/yarn.lock index c8881430..f4d59093 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2032,6 +2032,13 @@ link-module-alias "^1.2.0" shx "^0.3.4" +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== + dependencies: + "@floating-ui/utils" "^0.2.1" + "@floating-ui/core@^1.4.2": version "1.5.2" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.2.tgz#53a0f7a98c550e63134d504f26804f6b83dbc071" @@ -2047,6 +2054,14 @@ "@floating-ui/core" "^1.4.2" "@floating-ui/utils" "^0.1.3" +"@floating-ui/dom@^1.6.1": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + "@floating-ui/react-dom@^1.2.2": version "1.3.0" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3" @@ -2054,6 +2069,13 @@ dependencies: "@floating-ui/dom" "^1.2.1" +"@floating-ui/react-dom@^2.0.0": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== + dependencies: + "@floating-ui/dom" "^1.6.1" + "@floating-ui/react@0.19.0": version "0.19.0" resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.19.0.tgz#d8e19a3fcfaa0684d5ec3f335232b4e0ac0c87e1" @@ -2068,6 +2090,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== + "@fontsource/inter@^5.0.16": version "5.0.16" resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.0.16.tgz#b858508cdb56dcbbf3166903122851e2fbd16b50" @@ -3284,6 +3311,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-arrow@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" + integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-avatar@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623" @@ -3342,6 +3377,18 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-dismissable-layer@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" + integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-escape-keydown" "1.0.3" + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -3350,6 +3397,31 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-popper@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" + integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w== + dependencies: + "@babel/runtime" "^7.13.10" + "@floating-ui/react-dom" "^2.0.0" + "@radix-ui/react-arrow" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-use-rect" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/rect" "1.0.1" + +"@radix-ui/react-portal@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" + integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-presence@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" @@ -3406,6 +3478,25 @@ "@radix-ui/react-roving-focus" "1.0.4" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-tooltip@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz#8f55070f852e7e7450cc1d9210b793d2e5a7686e" + integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-visually-hidden" "1.0.3" + "@radix-ui/react-use-callback-ref@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" @@ -3421,6 +3512,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-escape-keydown@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" + integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" @@ -3435,6 +3534,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-rect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2" + integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/rect" "1.0.1" + "@radix-ui/react-use-size@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2" @@ -3443,6 +3550,21 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-visually-hidden@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac" + integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + +"@radix-ui/rect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f" + integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@remix-run/router@1.13.1": version "1.13.1" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.1.tgz#07e2a8006f23a3bc898b3f317e0a58cc8076b86e" From d2ca4df35a27f2cb424bef31923808a3eae277e8 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Thu, 22 Feb 2024 17:30:33 +0700 Subject: [PATCH 76/82] [T-4864: feat] Radio component (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎨 style: make the cursor of the tab trigger wrapper to default * ⚡️ feat: create radio component * 📝 docs: add radio component to the example page * 🔧 chore: install `@radix-ui/react-radio-group` --- packages/frontend/package.json | 1 + .../components/shared/Radio/Radio.theme.ts | 54 ++++++++++++++ .../src/components/shared/Radio/Radio.tsx | 63 ++++++++++++++++ .../src/components/shared/Radio/RadioItem.tsx | 74 +++++++++++++++++++ .../src/components/shared/Radio/index.ts | 2 + .../src/components/shared/Tabs/Tabs.theme.ts | 2 + .../frontend/src/pages/components/index.tsx | 24 ++++++ .../src/pages/components/renders/radio.ts | 16 ++++ yarn.lock | 17 +++++ 9 files changed, 253 insertions(+) create mode 100644 packages/frontend/src/components/shared/Radio/Radio.theme.ts create mode 100644 packages/frontend/src/components/shared/Radio/Radio.tsx create mode 100644 packages/frontend/src/components/shared/Radio/RadioItem.tsx create mode 100644 packages/frontend/src/components/shared/Radio/index.ts create mode 100644 packages/frontend/src/pages/components/renders/radio.ts diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 8daa876c..2e040901 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -7,6 +7,7 @@ "@material-tailwind/react": "^2.1.7", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@testing-library/jest-dom": "^5.17.0", diff --git a/packages/frontend/src/components/shared/Radio/Radio.theme.ts b/packages/frontend/src/components/shared/Radio/Radio.theme.ts new file mode 100644 index 00000000..0b84601e --- /dev/null +++ b/packages/frontend/src/components/shared/Radio/Radio.theme.ts @@ -0,0 +1,54 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const radioTheme = tv({ + slots: { + root: ['flex', 'gap-3', 'flex-wrap'], + wrapper: ['flex', 'items-center', 'gap-2', 'group'], + label: ['text-sm', 'tracking-[-0.006em]', 'text-elements-high-em'], + radio: [ + 'w-5', + 'h-5', + 'rounded-full', + 'border', + 'group', + 'border-border-interactive/10', + 'shadow-button', + 'group-hover:border-border-interactive/[0.14]', + 'focus-ring', + // Checked + 'data-[state=checked]:bg-controls-primary', + 'data-[state=checked]:group-hover:bg-controls-primary-hovered', + ], + indicator: [ + 'flex', + 'items-center', + 'justify-center', + 'relative', + 'w-full', + 'h-full', + 'after:content-[""]', + 'after:block', + 'after:w-2.5', + 'after:h-2.5', + 'after:rounded-full', + 'after:bg-transparent', + 'after:group-hover:bg-controls-disabled', + 'after:group-focus-visible:bg-controls-disabled', + // Checked + 'after:data-[state=checked]:bg-elements-on-primary', + 'after:data-[state=checked]:group-hover:bg-elements-on-primary', + 'after:data-[state=checked]:group-focus-visible:bg-elements-on-primary', + ], + }, + variants: { + orientation: { + vertical: { root: ['flex-col'] }, + horizontal: { root: ['flex-row'] }, + }, + }, + defaultVariants: { + orientation: 'vertical', + }, +}); + +export type RadioTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Radio/Radio.tsx b/packages/frontend/src/components/shared/Radio/Radio.tsx new file mode 100644 index 00000000..96542493 --- /dev/null +++ b/packages/frontend/src/components/shared/Radio/Radio.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { + Root as RadixRoot, + RadioGroupProps, +} from '@radix-ui/react-radio-group'; +import { RadioTheme, radioTheme } from './Radio.theme'; +import { RadioItem, RadioItemProps } from './RadioItem'; + +export interface RadioOption extends RadioItemProps { + /** + * The label of the radio option. + */ + label: string; + /** + * The value of the radio option. + */ + value: string; +} + +export interface RadioProps extends RadioGroupProps, RadioTheme { + /** + * The options of the radio. + * @default [] + * @example + * ```tsx + * const options = [ + * { + * label: 'Label 1', + * value: '1', + * }, + * { + * label: 'Label 2', + * value: '2', + * }, + * { + * label: 'Label 3', + * value: '3', + * }, + * ]; + * ``` + */ + options: RadioOption[]; +} + +/** + * The Radio component is used to select one option from a list of options. + */ +export const Radio = ({ + className, + options, + orientation, + ...props +}: RadioProps) => { + const { root } = radioTheme({ orientation }); + + return ( + + {options.map((option) => ( + + ))} + + ); +}; diff --git a/packages/frontend/src/components/shared/Radio/RadioItem.tsx b/packages/frontend/src/components/shared/Radio/RadioItem.tsx new file mode 100644 index 00000000..177af9db --- /dev/null +++ b/packages/frontend/src/components/shared/Radio/RadioItem.tsx @@ -0,0 +1,74 @@ +import React, { ComponentPropsWithoutRef } from 'react'; +import { + Item as RadixRadio, + Indicator as RadixIndicator, + RadioGroupItemProps, + RadioGroupIndicatorProps, +} from '@radix-ui/react-radio-group'; +import { radioTheme } from './Radio.theme'; + +export interface RadioItemProps extends RadioGroupItemProps { + /** + * The wrapper props of the radio item. + * You can use this prop to customize the wrapper props. + */ + wrapperProps?: ComponentPropsWithoutRef<'div'>; + /** + * The label props of the radio item. + * You can use this prop to customize the label props. + */ + labelProps?: ComponentPropsWithoutRef<'label'>; + /** + * The indicator props of the radio item. + * You can use this prop to customize the indicator props. + */ + indicatorProps?: RadioGroupIndicatorProps; + /** + * The id of the radio item. + */ + id?: string; + /** + * The label of the radio item. + */ + label?: string; +} + +/** + * The RadioItem component is used to render a radio item. + */ +export const RadioItem = ({ + className, + wrapperProps, + labelProps, + indicatorProps, + label, + id, + ...props +}: RadioItemProps) => { + const { wrapper, label: labelClass, radio, indicator } = radioTheme(); + + // Generate a unique id for the radio item from the label if the id is not provided + const kebabCaseLabel = label?.toLowerCase().replace(/\s+/g, '-'); + const componentId = id ?? kebabCaseLabel; + + return ( +
+ + + + {label && ( + + )} +
+ ); +}; diff --git a/packages/frontend/src/components/shared/Radio/index.ts b/packages/frontend/src/components/shared/Radio/index.ts new file mode 100644 index 00000000..6d49f1a8 --- /dev/null +++ b/packages/frontend/src/components/shared/Radio/index.ts @@ -0,0 +1,2 @@ +export * from './Radio'; +export * from './RadioItem'; diff --git a/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts b/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts index 4667fe83..7c39b077 100644 --- a/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts +++ b/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts @@ -9,6 +9,8 @@ export const tabsTheme = tv({ // Horizontal – default 'px-1', 'pb-5', + 'cursor-default', + 'select-none', 'text-elements-low-em', 'border-b-2', 'border-transparent', diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index bfe02385..7e7f84db 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -18,6 +18,8 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { RADIO_OPTIONS } from './renders/radio'; +import { Radio } from 'components/shared/Radio'; import { renderInlineNotificationWithDescriptions, renderInlineNotifications, @@ -28,6 +30,7 @@ import { renderTooltips } from './renders/tooltip'; const Page = () => { const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); + const [selectedRadio, setSelectedRadio] = useState(''); return (
@@ -160,6 +163,27 @@ const Page = () => {
+ {/* Radio */} +
+

Radio

+
+ + +
+
+ +
+ {/* Inline notification */}

Inline Notification

diff --git a/packages/frontend/src/pages/components/renders/radio.ts b/packages/frontend/src/pages/components/renders/radio.ts new file mode 100644 index 00000000..ff262f66 --- /dev/null +++ b/packages/frontend/src/pages/components/renders/radio.ts @@ -0,0 +1,16 @@ +import { RadioOption } from 'components/shared/Radio'; + +export const RADIO_OPTIONS: RadioOption[] = [ + { + label: 'Label 1', + value: '1', + }, + { + label: 'Label 2', + value: '2', + }, + { + label: 'Label 3', + value: '3', + }, +]; diff --git a/yarn.lock b/yarn.lock index f4d59093..b789155b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3439,6 +3439,23 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-radio-group@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz#3197f5dcce143bcbf961471bf89320735c0212d3" + integrity sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-roving-focus" "1.0.4" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-previous" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/react-roving-focus@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" From 7d1810ebd9b07650c6bbda69a1262aea04d63322 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Thu, 22 Feb 2024 17:42:13 +0700 Subject: [PATCH 77/82] [T-4866: feat] Switch component (#92) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: create switch component * 📝 docs: add switch to the example page * 🔧 chore: install `@radix-ui/react-switch` * 🎨 style: add inset shadow * 🎨 style: addjust input outline when error and focus --- packages/frontend/package.json | 1 + .../components/shared/Input/Input.theme.ts | 1 + .../components/shared/Switch/Switch.theme.ts | 84 ++++++++++++++++++ .../src/components/shared/Switch/Switch.tsx | 85 +++++++++++++++++++ .../src/components/shared/Switch/index.ts | 1 + .../frontend/src/pages/components/index.tsx | 24 ++++++ packages/frontend/tailwind.config.js | 1 + yarn.lock | 14 +++ 8 files changed, 211 insertions(+) create mode 100644 packages/frontend/src/components/shared/Switch/Switch.theme.ts create mode 100644 packages/frontend/src/components/shared/Switch/Switch.tsx create mode 100644 packages/frontend/src/components/shared/Switch/index.ts diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 2e040901..131ada73 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -7,6 +7,7 @@ "@material-tailwind/react": "^2.1.7", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", diff --git a/packages/frontend/src/components/shared/Input/Input.theme.ts b/packages/frontend/src/components/shared/Input/Input.theme.ts index 8def6a0d..bf5ce915 100644 --- a/packages/frontend/src/components/shared/Input/Input.theme.ts +++ b/packages/frontend/src/components/shared/Input/Input.theme.ts @@ -50,6 +50,7 @@ export const inputTheme = tv( 'outline-offset-0', 'outline-border-danger', 'shadow-none', + 'focus:outline-border-danger', ], helperText: 'text-elements-danger', }, diff --git a/packages/frontend/src/components/shared/Switch/Switch.theme.ts b/packages/frontend/src/components/shared/Switch/Switch.theme.ts new file mode 100644 index 00000000..87fbd092 --- /dev/null +++ b/packages/frontend/src/components/shared/Switch/Switch.theme.ts @@ -0,0 +1,84 @@ +import { tv, type VariantProps } from 'tailwind-variants'; + +export const switchTheme = tv({ + slots: { + wrapper: ['flex', 'items-start', 'gap-4', 'w-[375px]'], + switch: [ + 'h-6', + 'w-12', + 'rounded-full', + 'transition-all', + 'duration-500', + 'relative', + 'cursor-default', + 'shadow-inset', + 'focus-ring', + 'outline-none', + ], + thumb: [ + 'block', + 'h-4', + 'w-4', + 'translate-x-1', + 'transition-transform', + 'duration-100', + 'will-change-transform', + 'rounded-full', + 'shadow-button', + 'data-[state=checked]:translate-x-7', + 'bg-controls-elevated', + ], + label: [ + 'flex', + 'flex-1', + 'flex-col', + 'px-1', + 'gap-1', + 'text-sm', + 'text-elements-high-em', + 'tracking-[-0.006em]', + ], + description: ['text-xs', 'text-elements-low-em'], + }, + variants: { + checked: { + true: { + switch: [ + 'bg-controls-primary', + 'hover:bg-controls-primary-hovered', + 'focus-visible:bg-controls-primary-hovered', + ], + }, + false: { + switch: [ + 'bg-controls-inset', + 'hover:bg-controls-inset-hovered', + 'focus-visible:bg-controls-inset-hovered', + ], + }, + }, + disabled: { + true: { + switch: ['bg-controls-disabled', 'cursor-not-allowed'], + thumb: ['bg-elements-on-disabled'], + }, + }, + fullWidth: { + true: { + wrapper: ['w-full', 'justify-between'], + }, + }, + }, + compoundVariants: [ + { + checked: true, + disabled: true, + class: { + switch: ['bg-controls-disabled-active'], + thumb: ['bg-snowball-900'], + }, + }, + ], +}); + +export type SwitchVariants = VariantProps; diff --git a/packages/frontend/src/components/shared/Switch/Switch.tsx b/packages/frontend/src/components/shared/Switch/Switch.tsx new file mode 100644 index 00000000..32d35957 --- /dev/null +++ b/packages/frontend/src/components/shared/Switch/Switch.tsx @@ -0,0 +1,85 @@ +import React, { type ComponentPropsWithoutRef } from 'react'; +import { type SwitchProps as SwitchRadixProps } from '@radix-ui/react-switch'; +import * as SwitchRadix from '@radix-ui/react-switch'; + +import { switchTheme, type SwitchVariants } from './Switch.theme'; + +interface SwitchProps + extends Omit, + SwitchVariants { + /** + * The label of the switch. + */ + label?: string; + /** + * The description of the switch. + */ + description?: string; + /** + * Custom wrapper props for the switch. + */ + wrapperProps?: ComponentPropsWithoutRef<'div'>; + /** + * Function that is called when the checked state of the switch changes. + * @param checked The new checked state of the switch. + */ + onCheckedChange?(checked: boolean): void; +} + +/** + * A switch is a component used for toggling between two states. + */ +export const Switch = ({ + className, + checked, + label, + description, + disabled, + name, + wrapperProps, + fullWidth, + ...props +}: SwitchProps) => { + const { + wrapper, + switch: switchClass, + thumb, + label: labelClass, + description: descriptionClass, + } = switchTheme({ + checked, + disabled, + fullWidth, + }); + + const switchComponent = ( + + + + ); + + // If a label is provided, wrap the switch in a label element. + if (label) { + return ( +
+ + {switchComponent} +
+ ); + } + + return switchComponent; +}; diff --git a/packages/frontend/src/components/shared/Switch/index.ts b/packages/frontend/src/components/shared/Switch/index.ts new file mode 100644 index 00000000..1b19c1d3 --- /dev/null +++ b/packages/frontend/src/components/shared/Switch/index.ts @@ -0,0 +1 @@ +export * from './Switch'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 7e7f84db..c3643c24 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -18,6 +18,7 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { Switch } from 'components/shared/Switch'; import { RADIO_OPTIONS } from './renders/radio'; import { Radio } from 'components/shared/Radio'; import { @@ -30,6 +31,7 @@ import { renderTooltips } from './renders/tooltip'; const Page = () => { const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); + const [switchValue, setSwitchValue] = useState(false); const [selectedRadio, setSelectedRadio] = useState(''); return ( @@ -163,6 +165,28 @@ const Page = () => {
+ {/* Switch */} +
+

Switch

+
+ + + + +
+
+ +
+ {/* Radio */}

Radio

diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 9a814026..cce3380a 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -154,6 +154,7 @@ export default withMT({ calendar: '0px 3px 20px rgba(8, 47, 86, 0.1), 0px 0px 4px rgba(8, 47, 86, 0.14)', field: '0px 1px 2px rgba(0, 0, 0, 0.04)', + inset: 'inset 0px 1px 0px rgba(8, 47, 86, 0.06)', }, spacing: { 2.5: '0.625rem', diff --git a/yarn.lock b/yarn.lock index b789155b..f6800333 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3480,6 +3480,20 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-switch@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.0.3.tgz#6119f16656a9eafb4424c600fdb36efa5ec5837e" + integrity sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-previous" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/react-tabs@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2" From 55848b1dd161aa2628259fa1497c04d85f8e7f40 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 22:46:10 +0700 Subject: [PATCH 78/82] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20rename?= =?UTF-8?q?=20tags=20to=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{Tags/Tags.theme.ts => Tag/Tag.theme.ts} | 4 +-- .../shared/{Tags/Tags.tsx => Tag/Tag.tsx} | 27 +++++-------------- .../src/components/shared/Tag/index.ts | 2 ++ .../src/components/shared/Tags/index.ts | 2 -- .../frontend/src/pages/components/index.tsx | 10 +++---- .../src/pages/components/renders/tags.tsx | 22 +++++++-------- 6 files changed, 26 insertions(+), 41 deletions(-) rename packages/frontend/src/components/shared/{Tags/Tags.theme.ts => Tag/Tag.theme.ts} (95%) rename packages/frontend/src/components/shared/{Tags/Tags.tsx => Tag/Tag.tsx} (66%) create mode 100644 packages/frontend/src/components/shared/Tag/index.ts delete mode 100644 packages/frontend/src/components/shared/Tags/index.ts diff --git a/packages/frontend/src/components/shared/Tags/Tags.theme.ts b/packages/frontend/src/components/shared/Tag/Tag.theme.ts similarity index 95% rename from packages/frontend/src/components/shared/Tags/Tags.theme.ts rename to packages/frontend/src/components/shared/Tag/Tag.theme.ts index 299f31ea..086b59b3 100644 --- a/packages/frontend/src/components/shared/Tags/Tags.theme.ts +++ b/packages/frontend/src/components/shared/Tag/Tag.theme.ts @@ -1,7 +1,7 @@ import { tv } from 'tailwind-variants'; import type { VariantProps } from 'tailwind-variants'; -export const tagsTheme = tv( +export const tagTheme = tv( { slots: { wrapper: ['flex', 'gap-1.5', 'rounded-lg', 'border'], @@ -90,4 +90,4 @@ export const tagsTheme = tv( }, ); -export type TagsTheme = VariantProps; +export type TagTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Tags/Tags.tsx b/packages/frontend/src/components/shared/Tag/Tag.tsx similarity index 66% rename from packages/frontend/src/components/shared/Tags/Tags.tsx rename to packages/frontend/src/components/shared/Tag/Tag.tsx index 9cdb1231..2d10012a 100644 --- a/packages/frontend/src/components/shared/Tags/Tags.tsx +++ b/packages/frontend/src/components/shared/Tag/Tag.tsx @@ -3,11 +3,11 @@ import React, { type ComponentPropsWithoutRef, useMemo, } from 'react'; -import { TagsTheme, tagsTheme } from './Tags.theme'; +import { tagTheme, type TagTheme } from './Tag.theme'; import { cloneIcon } from 'utils/cloneIcon'; -type TagsProps = ComponentPropsWithoutRef<'div'> & - TagsTheme & { +type TagProps = ComponentPropsWithoutRef<'div'> & + TagTheme & { /** * The optional left icon element for a component. * @type {ReactNode} @@ -18,36 +18,21 @@ type TagsProps = ComponentPropsWithoutRef<'div'> & * @type {ReactNode} */ rightIcon?: ReactNode; - /** - * The optional type of the tags component. - * @type {TagsTheme['type']} - **/ - type?: TagsTheme['type']; - /** - * The optional style of the tags component. - * @type {TagsTheme['style']} - */ - style?: TagsTheme['style']; - /** - * The optional size of the tags component. - * @type {TagsTheme['size']} - */ - size?: TagsTheme['size']; }; -export const Tags = ({ +export const Tag = ({ children, leftIcon, rightIcon, type = 'attention', style = 'default', size = 'sm', -}: TagsProps) => { +}: TagProps) => { const { wrapper: wrapperCls, icon: iconCls, label: labelCls, - } = tagsTheme({ + } = tagTheme({ type, style, size, diff --git a/packages/frontend/src/components/shared/Tag/index.ts b/packages/frontend/src/components/shared/Tag/index.ts new file mode 100644 index 00000000..e889934c --- /dev/null +++ b/packages/frontend/src/components/shared/Tag/index.ts @@ -0,0 +1,2 @@ +export * from './Tag'; +export * from './Tag.theme'; diff --git a/packages/frontend/src/components/shared/Tags/index.ts b/packages/frontend/src/components/shared/Tags/index.ts deleted file mode 100644 index de1af198..00000000 --- a/packages/frontend/src/components/shared/Tags/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './Tags'; -export * from './Tags.theme'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 2da8cc9e..b3dc6406 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -23,7 +23,7 @@ import { renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; -import { renderDefaultTags, renderMinimalTags } from './renders/tags'; +import { renderDefaultTag, renderMinimalTag } from './renders/Tag'; const Page = () => { const [singleDate, setSingleDate] = useState(); @@ -42,13 +42,13 @@ const Page = () => {
- {/* Tags */} + {/* Tag */}
-

Tags

+

Tag

- {renderDefaultTags()} - {renderMinimalTags()} + {renderDefaultTag()} + {renderMinimalTag()}
diff --git a/packages/frontend/src/pages/components/renders/tags.tsx b/packages/frontend/src/pages/components/renders/tags.tsx index 760b7313..f01c526b 100644 --- a/packages/frontend/src/pages/components/renders/tags.tsx +++ b/packages/frontend/src/pages/components/renders/tags.tsx @@ -1,15 +1,15 @@ import React from 'react'; -import { Tags } from 'components/shared/Tags'; +import { Tag } from 'components/shared/Tag'; import { PlusIcon } from 'components/shared/CustomIcon'; -export const renderDefaultTags = () => +export const renderDefaultTag = () => (['default'] as const).map((style) => (
{( ['attention', 'negative', 'positive', 'emphasized', 'neutral'] as const ).map((type) => (
- } rightIcon={} style={style} @@ -17,8 +17,8 @@ export const renderDefaultTags = () => size="sm" > Label - - + } rightIcon={} size="xs" @@ -26,20 +26,20 @@ export const renderDefaultTags = () => type={type} > Label - +
))}
)); -export const renderMinimalTags = () => +export const renderMinimalTag = () => (['minimal'] as const).map((style) => (
{( ['attention', 'negative', 'positive', 'emphasized', 'neutral'] as const ).map((type) => (
- } rightIcon={} style={style} @@ -47,8 +47,8 @@ export const renderMinimalTags = () => size="sm" > Label - - + } rightIcon={} size="xs" @@ -56,7 +56,7 @@ export const renderMinimalTags = () => type={type} > Label - +
))}
From 05e1ac6b0284c09ceb90940207e01904f241d7e6 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 22:48:31 +0700 Subject: [PATCH 79/82] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20change?= =?UTF-8?q?=20tags=20to=20tag=20for=20rencer=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/components/index.tsx | 2 +- .../frontend/src/pages/components/renders/{tags.tsx => tag.tsx} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/frontend/src/pages/components/renders/{tags.tsx => tag.tsx} (100%) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index b3dc6406..5c1708eb 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -23,7 +23,7 @@ import { renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; -import { renderDefaultTag, renderMinimalTag } from './renders/Tag'; +import { renderDefaultTag, renderMinimalTag } from './renders/tag'; const Page = () => { const [singleDate, setSingleDate] = useState(); diff --git a/packages/frontend/src/pages/components/renders/tags.tsx b/packages/frontend/src/pages/components/renders/tag.tsx similarity index 100% rename from packages/frontend/src/pages/components/renders/tags.tsx rename to packages/frontend/src/pages/components/renders/tag.tsx From 9b6c777f5f2abe67e1bc58b99423f0cd73b9f349 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Fri, 23 Feb 2024 10:20:08 +0700 Subject: [PATCH 80/82] [T-4865: feat] Segmented controls component (#91) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: create segmented controls component * 📝 docs: add segmented controls component to example page * ♻️ refactor: put the icon size to icon theme * 🐛 fix: remove `value` from `useCallback` dependency --- .../SegmentedControlItem.tsx | 77 +++++++++++++++ .../SegmentedControls.theme.ts | 76 +++++++++++++++ .../SegmentedControls/SegmentedControls.tsx | 93 +++++++++++++++++++ .../shared/SegmentedControls/index.ts | 2 + .../frontend/src/pages/components/index.tsx | 24 +++++ .../components/renders/segmentedControls.tsx | 44 +++++++++ 6 files changed, 316 insertions(+) create mode 100644 packages/frontend/src/components/shared/SegmentedControls/SegmentedControlItem.tsx create mode 100644 packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.theme.ts create mode 100644 packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.tsx create mode 100644 packages/frontend/src/components/shared/SegmentedControls/index.ts create mode 100644 packages/frontend/src/pages/components/renders/segmentedControls.tsx diff --git a/packages/frontend/src/components/shared/SegmentedControls/SegmentedControlItem.tsx b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControlItem.tsx new file mode 100644 index 00000000..3fe5bda6 --- /dev/null +++ b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControlItem.tsx @@ -0,0 +1,77 @@ +import React, { + forwardRef, + type ComponentPropsWithoutRef, + type ReactNode, +} from 'react'; + +import { + segmentedControlsTheme, + type SegmentedControlsVariants, +} from './SegmentedControls.theme'; +import { cloneIcon } from 'utils/cloneIcon'; + +/** + * Interface for the props of a segmented control item component. + */ +export interface SegmentedControlItemProps + extends Omit, 'type' | 'children'>, + SegmentedControlsVariants { + /** + * The optional left icon element for a component. + */ + leftIcon?: ReactNode; + /** + * The optional right icon element to display. + */ + rightIcon?: ReactNode; + /** + * Indicates whether the item is active or not. + */ + active?: boolean; + /** + * Optional prop that represents the children of a React component. + */ + children?: ReactNode; +} + +/** + * A functional component that represents an item in a segmented control. + * @returns The rendered segmented control item. + */ +const SegmentedControlItem = forwardRef< + HTMLButtonElement, + SegmentedControlItemProps +>( + ( + { + className, + children, + size, + type, + leftIcon, + rightIcon, + active = false, + ...props + }, + ref, + ) => { + const { item, icon } = segmentedControlsTheme({ size, type }); + + return ( + + ); + }, +); + +SegmentedControlItem.displayName = 'SegmentedControlItem'; + +export { SegmentedControlItem }; diff --git a/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.theme.ts b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.theme.ts new file mode 100644 index 00000000..ed54a072 --- /dev/null +++ b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.theme.ts @@ -0,0 +1,76 @@ +import { tv, type VariantProps } from 'tailwind-variants'; + +/** + * Defines the theme for a segmented controls. + */ +export const segmentedControlsTheme = tv({ + slots: { + parent: [ + 'flex', + 'items-center', + 'bg-base-bg-emphasized', + 'gap-0.5', + 'rounded-lg', + ], + item: [ + 'flex', + 'items-center', + 'justify-center', + 'gap-2', + 'text-elements-mid-em', + 'bg-transparent', + 'border', + 'border-transparent', + 'cursor-default', + 'whitespace-nowrap', + 'rounded-lg', + 'focus-ring', + 'hover:bg-controls-tertiary-hovered', + 'focus-visible:z-20', + 'focus-visible:bg-controls-tertiary-hovered', + 'disabled:text-controls-disabled', + 'disabled:bg-transparent', + 'disabled:cursor-not-allowed', + 'disabled:border-transparent', + 'data-[active=true]:bg-controls-tertiary', + 'data-[active=true]:text-elements-high-em', + 'data-[active=true]:border-border-interactive/10', + 'data-[active=true]:shadow-field', + 'data-[active=true]:hover:bg-controls-tertiary-hovered', + ], + icon: [], + }, + variants: { + size: { + sm: { + item: ['px-3', 'py-2', 'text-xs'], + icon: ['h-4', 'w-4'], + }, + md: { + item: ['px-4', 'py-3', 'text-sm', 'tracking-[-0.006em]'], + icon: ['h-5', 'w-5'], + }, + }, + type: { + 'fixed-width': { + parent: ['w-fit'], + item: ['w-fit'], + }, + 'full-width': { + parent: ['w-full'], + item: ['w-full'], + }, + }, + }, + defaultVariants: { + size: 'md', + type: 'fixed-width', + }, +}); + +/** + * Defines the type for the variants of a segmented controls. + */ +export type SegmentedControlsVariants = VariantProps< + typeof segmentedControlsTheme +>; diff --git a/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.tsx b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.tsx new file mode 100644 index 00000000..f96fe8c2 --- /dev/null +++ b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.tsx @@ -0,0 +1,93 @@ +import React, { + useCallback, + type ComponentPropsWithoutRef, + type ReactNode, +} from 'react'; + +import { + SegmentedControlItem, + type SegmentedControlItemProps, +} from './SegmentedControlItem'; +import { + segmentedControlsTheme, + type SegmentedControlsVariants, +} from './SegmentedControls.theme'; + +/** + * Represents an option for a segmented control. + */ +export interface SegmentedControlsOption + extends Omit { + /** + * The label of the item. + */ + label: ReactNode; + /** + * The value of the item. + * + */ + value: string; +} + +/** + * Represents the props for the SegmentedControls component. + */ +export interface SegmentedControlsProps + extends Omit, 'onChange'>, + SegmentedControlsVariants { + /** + * An array of options for a segmented control component. + */ + options: SegmentedControlsOption[]; + /** + * An optional string value. + */ + value?: T; + /** + * Optional callback function to handle changes in state. + */ + onChange?: (v: T) => void; +} + +/** + * A component that renders segmented controls with customizable options. + */ +export function SegmentedControls({ + className, + options, + value, + type, + size, + onChange, + ...props +}: SegmentedControlsProps) { + const { parent } = segmentedControlsTheme({ size, type }); + + /** + * Handles the change event for a given option. + */ + const handleChange = useCallback( + (option: T) => { + if (!option) return; + onChange?.(option); + }, + [onChange], + ); + + return ( +
+ {options.map((option, index) => ( + handleChange(option.value as T)} + {...option} + > + {option.label} + + ))} +
+ ); +} diff --git a/packages/frontend/src/components/shared/SegmentedControls/index.ts b/packages/frontend/src/components/shared/SegmentedControls/index.ts new file mode 100644 index 00000000..5d569509 --- /dev/null +++ b/packages/frontend/src/components/shared/SegmentedControls/index.ts @@ -0,0 +1,2 @@ +export * from './SegmentedControlItem'; +export * from './SegmentedControls'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index fc6acbec..e6dd56bc 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -18,6 +18,8 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { SegmentedControls } from 'components/shared/SegmentedControls'; +import { SEGMENTED_CONTROLS_OPTIONS } from './renders/segmentedControls'; import { Switch } from 'components/shared/Switch'; import { RADIO_OPTIONS } from './renders/radio'; import { Radio } from 'components/shared/Radio'; @@ -32,6 +34,8 @@ import { renderTooltips } from './renders/tooltip'; const Page = () => { const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); + const [selectedSegmentedControl, setSelectedSegmentedControl] = + useState('Test 1'); const [switchValue, setSwitchValue] = useState(false); const [selectedRadio, setSelectedRadio] = useState(''); @@ -176,6 +180,26 @@ const Page = () => {
+ {/* Segmented Controls */} +
+

Segmented Controls

+
+ + +
+
+ +
+ {/* Switch */}

Switch

diff --git a/packages/frontend/src/pages/components/renders/segmentedControls.tsx b/packages/frontend/src/pages/components/renders/segmentedControls.tsx new file mode 100644 index 00000000..8f4a019f --- /dev/null +++ b/packages/frontend/src/pages/components/renders/segmentedControls.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Badge } from 'components/shared/Badge'; +import { SegmentedControlsOption } from 'components/shared/SegmentedControls'; + +export const SEGMENTED_CONTROLS_OPTIONS: SegmentedControlsOption[] = [ + { label: 'Test 1', value: 'Test 1' }, + { + label: 'Test 2', + value: 'Test 2', + leftIcon: ( + + 1 + + ), + }, + { + label: 'Test 3', + value: 'Test 3', + rightIcon: ( + + 1 + + ), + }, + { + label: 'Test 4', + value: 'Test 4', + leftIcon: ( + + 1 + + ), + rightIcon: ( + + 1 + + ), + }, + { + label: 'Test 5', + value: 'Test 5', + disabled: true, + }, +]; From 12fba5ee0cc27324778511a4258097808ad1323a Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Fri, 23 Feb 2024 10:22:33 +0700 Subject: [PATCH 81/82] [T-4845: feat] Date picker component (#97) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: create calendar icon for date picker * ⚡️ feat: create date picker component * 📝 docs: add date picker to the example page * 🔧 chore: install `@radix-ui/react-popover` --- packages/frontend/package.json | 1 + .../shared/CustomIcon/CalendarIcon.tsx | 21 ++++ .../src/components/shared/CustomIcon/index.ts | 1 + .../shared/DatePicker/DatePicker.theme.ts | 9 ++ .../shared/DatePicker/DatePicker.tsx | 100 +++++++++++++++++ .../src/components/shared/DatePicker/index.ts | 1 + .../frontend/src/pages/components/index.tsx | 13 ++- yarn.lock | 101 +++++++++++++++++- 8 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 packages/frontend/src/components/shared/CustomIcon/CalendarIcon.tsx create mode 100644 packages/frontend/src/components/shared/DatePicker/DatePicker.theme.ts create mode 100644 packages/frontend/src/components/shared/DatePicker/DatePicker.tsx create mode 100644 packages/frontend/src/components/shared/DatePicker/index.ts diff --git a/packages/frontend/package.json b/packages/frontend/package.json index cbb0b2e4..53252773 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -7,6 +7,7 @@ "@material-tailwind/react": "^2.1.7", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-tabs": "^1.0.4", diff --git a/packages/frontend/src/components/shared/CustomIcon/CalendarIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/CalendarIcon.tsx new file mode 100644 index 00000000..6a51210a --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/CalendarIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const CalendarIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index cb5f9f37..46fd03ad 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -9,6 +9,7 @@ export * from './WarningIcon'; export * from './SearchIcon'; export * from './CrossIcon'; export * from './GlobeIcon'; +export * from './CalendarIcon'; export * from './CheckRoundFilledIcon'; export * from './InfoRoundFilledIcon'; export * from './LoadingIcon'; diff --git a/packages/frontend/src/components/shared/DatePicker/DatePicker.theme.ts b/packages/frontend/src/components/shared/DatePicker/DatePicker.theme.ts new file mode 100644 index 00000000..522bd348 --- /dev/null +++ b/packages/frontend/src/components/shared/DatePicker/DatePicker.theme.ts @@ -0,0 +1,9 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const datePickerTheme = tv({ + slots: { + input: [], + }, +}); + +export type DatePickerTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/DatePicker/DatePicker.tsx b/packages/frontend/src/components/shared/DatePicker/DatePicker.tsx new file mode 100644 index 00000000..69b44e84 --- /dev/null +++ b/packages/frontend/src/components/shared/DatePicker/DatePicker.tsx @@ -0,0 +1,100 @@ +import React, { useCallback, useState } from 'react'; +import { Input, InputProps } from 'components/shared/Input'; +import * as Popover from '@radix-ui/react-popover'; +import { datePickerTheme } from './DatePicker.theme'; +import { Calendar, CalendarProps } from 'components/shared/Calendar'; +import { CalendarIcon } from 'components/shared/CustomIcon'; +import { Value } from 'react-calendar/dist/cjs/shared/types'; +import { format } from 'date-fns'; + +export interface DatePickerProps + extends Omit { + /** + * The props for the calendar component. + */ + calendarProps?: CalendarProps; + /** + * Optional callback function that is called when the value of the input changes. + * @param {string} value - The new value of the input. + * @returns None + */ + onChange?: (value: Value) => void; + /** + * The value of the input. + */ + value?: Value; + /** + * Whether to allow the selection of a date range. + */ + selectRange?: boolean; +} + +/** + * A date picker component that allows users to select a date from a calendar. + * @param {DatePickerProps} props - The props for the date picker component. + * @returns The rendered date picker component. + */ +export const DatePicker = ({ + className, + calendarProps, + value, + onChange, + selectRange = false, + ...props +}: DatePickerProps) => { + const { input } = datePickerTheme(); + + const [open, setOpen] = useState(false); + + /** + * Renders the value of the date based on the current state of `props.value`. + * @returns {string | undefined} - The formatted date value or `undefined` if `props.value` is falsy. + */ + const renderValue = useCallback(() => { + if (!value) return undefined; + if (Array.isArray(value)) { + return value + .map((date) => format(date as Date, 'dd/MM/yyyy')) + .join(' - '); + } + return format(value, 'dd/MM/yyyy'); + }, [value]); + + /** + * Handles the selection of a date from the calendar. + */ + const handleSelect = useCallback( + (date: Value) => { + setOpen(false); + onChange?.(date); + }, + [setOpen, onChange], + ); + + return ( + + + setOpen(true)} />} + readOnly + placeholder="Select a date..." + value={renderValue()} + className={input({ className })} + onClick={() => setOpen(true)} + /> + + + setOpen(false)}> + setOpen(false)} + onSelect={handleSelect} + /> + + + + ); +}; diff --git a/packages/frontend/src/components/shared/DatePicker/index.ts b/packages/frontend/src/components/shared/DatePicker/index.ts new file mode 100644 index 00000000..a48b62e4 --- /dev/null +++ b/packages/frontend/src/components/shared/DatePicker/index.ts @@ -0,0 +1 @@ +export * from './DatePicker'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index e6dd56bc..9c1d2569 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -28,6 +28,7 @@ import { renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; +import { DatePicker } from 'components/shared/DatePicker'; import { renderToast, renderToastsWithCta } from './renders/toast'; import { renderTooltips } from './renders/tooltip'; @@ -61,7 +62,7 @@ const Page = () => {
- {/* Button */} + {/* Tooltip */}

Tooltip

@@ -152,6 +153,16 @@ const Page = () => { />
+ +

Date Picker

+
+ + +
diff --git a/yarn.lock b/yarn.lock index 476291c8..ce8ae5dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3389,6 +3389,23 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-escape-keydown" "1.0.3" +"@radix-ui/react-focus-guards@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" + integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-focus-scope@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" + integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -3397,6 +3414,28 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-popover@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.7.tgz#23eb7e3327330cb75ec7b4092d685398c1654e3c" + integrity sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-focus-guards" "1.0.1" + "@radix-ui/react-focus-scope" "1.0.4" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-controllable-state" "1.0.1" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.5" + "@radix-ui/react-popper@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" @@ -5452,7 +5491,7 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-hidden@^1.1.3: +aria-hidden@^1.1.1, aria-hidden@^1.1.3: version "1.2.3" resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954" integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ== @@ -7438,6 +7477,11 @@ detect-newline@^3.0.0: resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" @@ -9028,6 +9072,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz" @@ -9857,6 +9906,13 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -13919,6 +13975,25 @@ react-refresh@^0.11.0: resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-remove-scroll-bar@^2.3.3: + version "2.3.5" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.5.tgz#cd2543b3ed7716c7c5b446342d21b0e0b303f47c" + integrity sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw== + dependencies: + react-style-singleton "^2.2.1" + tslib "^2.0.0" + +react-remove-scroll@2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77" + integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw== + dependencies: + react-remove-scroll-bar "^2.3.3" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.0" + use-sidecar "^1.1.2" + react-router-dom@^6.20.1: version "6.20.1" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.20.1.tgz#e34f8075b9304221420de3609e072bb349824984" @@ -13989,6 +14064,15 @@ react-scripts@5.0.1: optionalDependencies: fsevents "^2.3.2" +react-style-singleton@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" + integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^2.0.0" + react-syntax-highlighter@^15.5.0: version "15.5.0" resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" @@ -16150,6 +16234,21 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +use-callback-ref@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.1.tgz#9be64c3902cbd72b07fe55e56408ae3a26036fd0" + integrity sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ== + dependencies: + tslib "^2.0.0" + +use-sidecar@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" + integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + usehooks-ts@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-2.10.0.tgz#ccd0d63168e4db95b061e26e585b0068d3fad0ed" From 5a5756bcf2eac1e9122edf0b8d6a54a4a5c228e7 Mon Sep 17 00:00:00 2001 From: Andre H Date: Fri, 23 Feb 2024 10:47:50 +0700 Subject: [PATCH 82/82] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20cloneIcon?= =?UTF-8?q?=20to=20dep=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Tag/Tag.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/shared/Tag/Tag.tsx b/packages/frontend/src/components/shared/Tag/Tag.tsx index 2d10012a..086d3515 100644 --- a/packages/frontend/src/components/shared/Tag/Tag.tsx +++ b/packages/frontend/src/components/shared/Tag/Tag.tsx @@ -41,14 +41,14 @@ export const Tag = ({ const renderLeftIcon = useMemo(() => { if (!leftIcon) return null; return
{cloneIcon(leftIcon, { size: 16 })}
; - }, [iconCls, leftIcon]); + }, [cloneIcon, iconCls, leftIcon]); const renderRightIcon = useMemo(() => { if (!rightIcon) return null; return (
{cloneIcon(rightIcon, { size: 16 })}
); - }, [iconCls, rightIcon]); + }, [cloneIcon, iconCls, rightIcon]); return (