diff --git a/packages/frontend/src/components/shared/CustomIcon/LinkChainIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/LinkChainIcon.tsx new file mode 100644 index 00000000..66ff9cd4 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/LinkChainIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const LinkChainIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index 096761a5..2137f956 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -40,6 +40,7 @@ export * from './GithubStrokeIcon'; export * from './BranchStrokeIcon'; export * from './StorageIcon'; export * from './LinkIcon'; +export * from './LinkChainIcon'; export * from './CursorBoxIcon'; export * from './CommitIcon'; export * from './RocketIcon'; diff --git a/packages/frontend/src/components/shared/Input/Input.tsx b/packages/frontend/src/components/shared/Input/Input.tsx index dd88bd80..e1c84e8b 100644 --- a/packages/frontend/src/components/shared/Input/Input.tsx +++ b/packages/frontend/src/components/shared/Input/Input.tsx @@ -47,15 +47,15 @@ export const Input = ({ helperIcon: helperIconCls, } = inputTheme({ ...styleProps }); - const renderLabels = useMemo( - () => ( -
+ const renderLabels = useMemo(() => { + if (!label && !description) return null; + return ( +

{label}

{description}

- ), - [labelCls, descriptionCls, label, description], - ); + ); + }, [labelCls, descriptionCls, label, description]); const renderLeftIcon = useMemo(() => { return ( @@ -73,8 +73,9 @@ export const Input = ({ ); }, [cloneIcon, iconCls, iconContainerCls, rightIcon]); - const renderHelperText = useMemo( - () => ( + const renderHelperText = useMemo(() => { + if (!helperText) return null; + return (
{state && cloneIcon(, { @@ -82,12 +83,11 @@ export const Input = ({ })}

{helperText}

- ), - [cloneIcon, state, helperIconCls, helperText, helperTextCls], - ); + ); + }, [cloneIcon, state, helperIconCls, helperText, helperTextCls]); return ( -
+
{renderLabels}
{leftIcon && renderLeftIcon} diff --git a/packages/frontend/src/components/shared/Radio/Radio.theme.ts b/packages/frontend/src/components/shared/Radio/Radio.theme.ts index 0b84601e..84d8fd01 100644 --- a/packages/frontend/src/components/shared/Radio/Radio.theme.ts +++ b/packages/frontend/src/components/shared/Radio/Radio.theme.ts @@ -2,7 +2,7 @@ import { VariantProps, tv } from 'tailwind-variants'; export const radioTheme = tv({ slots: { - root: ['flex', 'gap-3', 'flex-wrap'], + root: ['flex', 'gap-3'], wrapper: ['flex', 'items-center', 'gap-2', 'group'], label: ['text-sm', 'tracking-[-0.006em]', 'text-elements-high-em'], radio: [ @@ -39,15 +39,34 @@ export const radioTheme = tv({ 'after:data-[state=checked]:group-hover:bg-elements-on-primary', 'after:data-[state=checked]:group-focus-visible:bg-elements-on-primary', ], + icon: ['w-[18px]', 'h-[18px]'], }, variants: { orientation: { vertical: { root: ['flex-col'] }, horizontal: { root: ['flex-row'] }, }, + variant: { + unstyled: {}, + card: { + wrapper: [ + 'px-4', + 'py-3', + 'rounded-lg', + 'border', + 'border-border-interactive', + 'bg-controls-tertiary', + 'shadow-button', + 'w-full', + 'cursor-pointer', + ], + label: ['select-none', 'cursor-pointer'], + }, + }, }, defaultVariants: { orientation: 'vertical', + variant: 'unstyled', }, }); diff --git a/packages/frontend/src/components/shared/Radio/Radio.tsx b/packages/frontend/src/components/shared/Radio/Radio.tsx index 96542493..80468001 100644 --- a/packages/frontend/src/components/shared/Radio/Radio.tsx +++ b/packages/frontend/src/components/shared/Radio/Radio.tsx @@ -49,14 +49,15 @@ export const Radio = ({ className, options, orientation, + variant, ...props }: RadioProps) => { - const { root } = radioTheme({ orientation }); + const { root } = radioTheme({ orientation, variant }); return ( {options.map((option) => ( - + ))} ); diff --git a/packages/frontend/src/components/shared/Radio/RadioItem.tsx b/packages/frontend/src/components/shared/Radio/RadioItem.tsx index 177af9db..d77f4752 100644 --- a/packages/frontend/src/components/shared/Radio/RadioItem.tsx +++ b/packages/frontend/src/components/shared/Radio/RadioItem.tsx @@ -1,13 +1,16 @@ -import React, { ComponentPropsWithoutRef } from 'react'; +import React, { ReactNode, ComponentPropsWithoutRef } from 'react'; import { Item as RadixRadio, Indicator as RadixIndicator, RadioGroupItemProps, RadioGroupIndicatorProps, } from '@radix-ui/react-radio-group'; -import { radioTheme } from './Radio.theme'; +import { RadioTheme, radioTheme } from './Radio.theme'; +import { cloneIcon } from 'utils/cloneIcon'; -export interface RadioItemProps extends RadioGroupItemProps { +export interface RadioItemProps + extends RadioGroupItemProps, + Pick { /** * The wrapper props of the radio item. * You can use this prop to customize the wrapper props. @@ -27,6 +30,10 @@ export interface RadioItemProps extends RadioGroupItemProps { * The id of the radio item. */ id?: string; + /** + * The left icon of the radio item. + */ + leftIcon?: ReactNode; /** * The label of the radio item. */ @@ -41,18 +48,29 @@ export const RadioItem = ({ wrapperProps, labelProps, indicatorProps, + leftIcon, label, id, + variant, ...props }: RadioItemProps) => { - const { wrapper, label: labelClass, radio, indicator } = radioTheme(); + const { + wrapper, + label: labelClass, + radio, + indicator, + icon, + } = radioTheme({ variant }); // 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 ( -
+
+ ); }; diff --git a/packages/frontend/src/components/shared/Select/Select.tsx b/packages/frontend/src/components/shared/Select/Select.tsx index fa22428c..1de4efad 100644 --- a/packages/frontend/src/components/shared/Select/Select.tsx +++ b/packages/frontend/src/components/shared/Select/Select.tsx @@ -266,15 +266,15 @@ export const Select = ({ onClear?.(); }; - const renderLabels = useMemo( - () => ( -
+ const renderLabels = useMemo(() => { + if (!label && !description) return null; + return ( +

{label}

{description}

- ), - [theme, label, description], - ); + ); + }, [theme, label, description]); const renderLeftIcon = useMemo(() => { return ( @@ -302,8 +302,9 @@ export const Select = ({ ); }, [cloneIcon, theme, rightIcon]); - const renderHelperText = useMemo( - () => ( + const renderHelperText = useMemo(() => { + if (!helperText) return null; + return (
{error && cloneIcon(, { @@ -311,9 +312,8 @@ export const Select = ({ })}

{helperText}

- ), - [cloneIcon, error, theme, helperText], - ); + ); + }, [cloneIcon, error, theme, helperText]); const isMultipleHasValue = multiple && selectedItems.length > 0; const isMultipleHasValueButNotSearchable = diff --git a/packages/frontend/src/pages/org-slug/projects/create/Template.tsx b/packages/frontend/src/pages/org-slug/projects/create/Template.tsx index 0633540c..3d61792d 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/Template.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/Template.tsx @@ -6,9 +6,13 @@ import { useSearchParams, } from 'react-router-dom'; -import { Avatar } from '@material-tailwind/react'; - import templates from '../../../../assets/templates'; +import { + LinkChainIcon, + TemplateIcon, + TemplateIconType, +} from 'components/shared/CustomIcon'; +import { Heading } from 'components/shared/Heading'; import { Steps } from 'components/shared/Steps'; // TODO: Set dynamic route for template and load details from DB @@ -44,19 +48,24 @@ const CreateWithTemplate = () => { return (
-
- -
{template?.name}
+ diff --git a/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx index 9ff50c84..9fc8795e 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx @@ -1,15 +1,18 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { useForm, Controller, SubmitHandler } from 'react-hook-form'; +import { useForm, SubmitHandler, Controller } from 'react-hook-form'; import { useNavigate, useOutletContext, useParams } from 'react-router-dom'; import toast from 'react-hot-toast'; import assert from 'assert'; -import { Button, Option, Typography } from '@material-tailwind/react'; - import { useOctokit } from '../../../../../context/OctokitContext'; import { useGQLClient } from '../../../../../context/GQLClientContext'; -import AsyncSelect from '../../../../../components/shared/AsyncSelect'; import { Template } from '../../../../../types'; +import { Heading } from 'components/shared/Heading'; +import { Input } from 'components/shared/Input'; +import { Select, SelectOption } from 'components/shared/Select'; +import { ArrowRightCircleFilledIcon } from 'components/shared/CustomIcon'; +import { Checkbox } from 'components/shared/Checkbox'; +import { Button } from 'components/shared/Button'; type SubmitRepoValues = { framework: string; @@ -93,7 +96,7 @@ const CreateRepo = () => { fetchUserAndOrgs(); }, [octokit]); - const { register, handleSubmit, control, reset } = useForm({ + const { handleSubmit, control, reset } = useForm({ defaultValues: { framework: 'React', repoName: '', @@ -110,86 +113,67 @@ const CreateRepo = () => { return (
-
- - Create a repository - - - The project will be cloned into this repository - -
-
-
Framework
-
- - -
-
-
-
Git account
+
+ + Create a repository + + + The project will be cloned into this repository + +
+
+ Git account ( - - {gitAccounts.map((account, key) => ( - - ))} - + render={({ field: { value, onChange } }) => ( + + Name the repo + ( + + )} />
-
-
- -
-
- +
+
+ +
);