chore: UI implement changes feedback (#173)

* style: combobox add cursor pointer property

* chore: make repository row clickable

* style: fix scroll on repositories list

* chore: select main/master by deafult branch

* chore: add required icon on label form and max characters length

* Update ui/src/components/core/combobox/combobox.tsx

Co-authored-by: Felipe Mendes <zo.fmendes@gmail.com>

* chore: remove commented line

* chore: replace Form.MaxLength

* styles: fix styles for combobox icon

* chore: default branch based on repo config

---------

Co-authored-by: Felipe Mendes <zo.fmendes@gmail.com>
This commit is contained in:
Camila Sosa Morales 2023-03-13 15:56:17 -05:00 committed by GitHub
parent 5c43ebe0d0
commit 7985bb35bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 112 additions and 52 deletions

View File

@ -31,7 +31,7 @@ const ComboboxInput = ({
handleInputChange, handleInputChange,
handleInputClick, handleInputClick,
}: ComboboxInputProps) => ( }: ComboboxInputProps) => (
<div className="relative w-full cursor-default "> <div className="relative w-full">
<Icon <Icon
name={leftIcon} name={leftIcon}
size="sm" size="sm"
@ -45,7 +45,7 @@ const ComboboxInput = ({
/> />
<ComboboxLib.Input <ComboboxLib.Input
placeholder="Search" placeholder="Search"
className={`w-full border-solid border border-slate7 h-11 py-3 pl-10 pr-10 text-sm leading-5 text-slate11 outline-none ${ className={`w-full border-solid border border-slate7 h-11 py-3 px-10 text-sm bg-transparent leading-5 text-slate11 outline-none ${
open open
? 'border-b-0 rounded-t-xl bg-black border-slate6' ? 'border-b-0 rounded-t-xl bg-black border-slate6'
: 'rounded-xl bg-transparent' : 'rounded-xl bg-transparent'

View File

@ -11,9 +11,11 @@ import { AiOutlineTwitter } from '@react-icons/all-files/ai/AiOutlineTwitter';
import { ErrorIcon } from './custom/error'; import { ErrorIcon } from './custom/error';
import { IoClose } from '@react-icons/all-files/io5/IoClose'; import { IoClose } from '@react-icons/all-files/io5/IoClose';
import { AiFillCheckCircle } from '@react-icons/all-files/ai/AiFillCheckCircle'; import { AiFillCheckCircle } from '@react-icons/all-files/ai/AiFillCheckCircle';
import { BiGitBranch } from '@react-icons/all-files/bi/BiGitBranch';
export const IconLibrary = Object.freeze({ export const IconLibrary = Object.freeze({
back: IoArrowBackCircleSharp, back: IoArrowBackCircleSharp,
branch: BiGitBranch,
check: AiOutlineCheck, check: AiOutlineCheck,
'check-circle': IoCheckmarkCircleSharp, 'check-circle': IoCheckmarkCircleSharp,
'chevron-down': AiOutlineDown, 'chevron-down': AiOutlineDown,

View File

@ -33,6 +33,15 @@ export abstract class FormStyles {
}, },
}); });
static readonly RequiredLabel = styled('span', {
color: '$red11',
});
static readonly MaxLength = styled(FormStyles.Label, {
textAlign: 'right',
mt: '$1h',
});
static readonly ErrorMessage = styled('span', { static readonly ErrorMessage = styled('span', {
color: '$red11', color: '$red11',
fontSize: '0.625rem', fontSize: '0.625rem',

View File

@ -14,13 +14,24 @@ export abstract class Form {
); );
static readonly Label = forwardRef<HTMLLabelElement, Form.LabelProps>( static readonly Label = forwardRef<HTMLLabelElement, Form.LabelProps>(
({ children, ...props }, ref) => ( ({ children, isRequired, ...props }, ref) => (
<FormStyles.Label ref={ref} {...props}> <FormStyles.Label ref={ref} {...props}>
{children} {children}{' '}
{isRequired && <FormStyles.RequiredLabel>*</FormStyles.RequiredLabel>}
</FormStyles.Label> </FormStyles.Label>
) )
); );
static readonly MaxLength = forwardRef<HTMLLabelElement, Form.LabelProps>(
({ children, ...props }, ref) => {
return (
<FormStyles.MaxLength ref={ref} {...props}>
{children}
</FormStyles.MaxLength>
);
}
);
static readonly Error = forwardRef<HTMLDivElement, Form.ErrorProps>( static readonly Error = forwardRef<HTMLDivElement, Form.ErrorProps>(
({ children, ...props }, ref) => ( ({ children, ...props }, ref) => (
<FormStyles.ErrorMessage ref={ref} {...props}> <FormStyles.ErrorMessage ref={ref} {...props}>
@ -55,7 +66,9 @@ export namespace Form {
children: React.ReactNode; children: React.ReactNode;
} & React.ComponentProps<typeof FormStyles.Field>; } & React.ComponentProps<typeof FormStyles.Field>;
export type LabelProps = React.ComponentProps<typeof FormStyles.Label>; export type LabelProps = { isRequired?: boolean } & React.ComponentProps<
typeof FormStyles.Label
>;
export type ErrorProps = React.ComponentProps<typeof FormStyles.ErrorMessage>; export type ErrorProps = React.ComponentProps<typeof FormStyles.ErrorMessage>;

View File

@ -1,4 +1,4 @@
import { DropdownItem } from '@/components'; import { ComboboxItem } from '@/components';
import { githubActions, RootState } from '@/store'; import { githubActions, RootState } from '@/store';
import { AppLog } from '@/utils'; import { AppLog } from '@/utils';
import { createAsyncThunk } from '@reduxjs/toolkit'; import { createAsyncThunk } from '@reduxjs/toolkit';
@ -23,7 +23,7 @@ export const fetchBranchesThunk = createAsyncThunk<void, FetchBranches>(
const branches = await githubClient.fetchBranches(owner, repository); const branches = await githubClient.fetchBranches(owner, repository);
dispatch(githubActions.setBranches(branches as DropdownItem[])); dispatch(githubActions.setBranches(branches as ComboboxItem[]));
} catch (error) { } catch (error) {
AppLog.errorToast( AppLog.errorToast(
'We have a problem trying to get your branches. Please try again later.' 'We have a problem trying to get your branches. Please try again later.'

View File

@ -16,14 +16,7 @@ export const fetchRepositoriesThunk = createAsyncThunk(
const repositories = await githubClient.fetchRepos(url); const repositories = await githubClient.fetchRepos(url);
dispatch( dispatch(githubActions.setRepositories(repositories));
githubActions.setRepositories(
repositories.map((repo: any) => ({
name: repo.name,
url: repo.html_url,
}))
)
);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
dispatch(githubActions.setQueryState('failed')); dispatch(githubActions.setQueryState('failed'));

View File

@ -1,5 +1,6 @@
import { DropdownItem } from '@/components'; import { DropdownItem } from '@/components';
import { Octokit } from 'octokit'; import { Octokit } from 'octokit';
import { GithubState } from './github-slice';
export type UserData = { export type UserData = {
value: string; value: string;
@ -50,7 +51,14 @@ export class GithubClient {
}, },
}).then((res) => res.json()); }).then((res) => res.json());
return repos; return repos.map(
(repo: any) =>
({
name: repo.name,
url: repo.html_url,
defaultBranch: repo.default_branch,
} as GithubState.Repository)
);
} catch (error) { } catch (error) {
return error; return error;
} }

View File

@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '@/store'; import { RootState } from '@/store';
import { useAppSelector } from '@/store/hooks'; import { useAppSelector } from '@/store/hooks';
import * as asyncThunk from './async-thunk'; import * as asyncThunk from './async-thunk';
import { DropdownItem } from '@/components'; import { ComboboxItem } from '@/components';
import { UserData } from './github-client'; import { UserData } from './github-client';
export namespace GithubState { export namespace GithubState {
@ -23,11 +23,12 @@ export namespace GithubState {
export type Repository = { export type Repository = {
name: string; name: string;
url: string; url: string;
defaultBranch: string;
}; };
export type Repositories = Array<Repository>; export type Repositories = Array<Repository>;
export type Branches = Array<DropdownItem>; export type Branches = Array<ComboboxItem>;
} }
export interface GithubState { export interface GithubState {

View File

@ -1,7 +1,7 @@
import { Dropdown, DropdownItem, Flex, Form, Spinner } from '@/components'; import { useEffect } from 'react';
import { Combobox, ComboboxItem, Flex, Form, Spinner } from '@/components';
import { githubActions, useAppDispatch, useGithubStore } from '@/store'; import { githubActions, useAppDispatch, useGithubStore } from '@/store';
import { Mint } from '@/views/mint/mint.context'; import { Mint } from '@/views/mint/mint.context';
import { useEffect } from 'react';
export const RepoBranchCommitFields = () => { export const RepoBranchCommitFields = () => {
const { queryLoading, branches } = useGithubStore(); const { queryLoading, branches } = useGithubStore();
@ -27,7 +27,21 @@ export const RepoBranchCommitFields = () => {
} }
}, [queryLoading, dispatch]); }, [queryLoading, dispatch]);
const handleBranchChange = (dropdownOption: DropdownItem) => { useEffect(() => {
if (queryLoading === 'success' && branches.length > 0) {
const defaultBranch = branches.find(
(branch) =>
branch.label.toLowerCase() ===
repositoryName.defaultBranch.toLowerCase()
);
if (defaultBranch) {
setBranchName(defaultBranch);
setCommitHash(defaultBranch.value);
}
}
}, [queryLoading, branches]);
const handleBranchChange = (dropdownOption: ComboboxItem) => {
setBranchName(dropdownOption); setBranchName(dropdownOption);
setCommitHash(dropdownOption.value); setCommitHash(dropdownOption.value);
}; };
@ -54,7 +68,8 @@ export const RepoBranchCommitFields = () => {
<> <>
<Form.Field> <Form.Field>
<Form.Label>Git Branch</Form.Label> <Form.Label>Git Branch</Form.Label>
<Dropdown <Combobox
leftIcon="branch"
items={branches} items={branches}
selectedValue={branchName} selectedValue={branchName}
onChange={handleBranchChange} onChange={handleBranchChange}

View File

@ -1,6 +1,6 @@
import { Button, Card, Flex, Stepper } from '@/components'; import { Button, Card, Flex, Stepper } from '@/components';
import { Mint } from '@/views/mint/mint.context'; import { Mint } from '@/views/mint/mint.context';
import { RepoRow } from '../github-repository-selection'; import { RepoRow } from '../repository-row';
import { RepoBranchCommitFields } from './repo-branch-commit-fields'; import { RepoBranchCommitFields } from './repo-branch-commit-fields';
export const RepoConfigurationBody = () => { export const RepoConfigurationBody = () => {

View File

@ -20,27 +20,6 @@ export const Loading = () => (
</Flex> </Flex>
); );
type RepoRowProps = {
repo: string;
button: React.ReactNode;
} & React.ComponentProps<typeof Flex>;
export const RepoRow = forwardRef<HTMLDivElement, RepoRowProps>(
({ repo, button, ...props }, ref) => (
<Flex
{...props}
ref={ref}
css={{ justifyContent: 'space-between', my: '$4', ...props.css }}
>
<Flex css={{ alignItems: 'center' }}>
<Icon name="github" css={{ fontSize: '$2xl', mr: '$2' }} />
<span>{repo}</span>
</Flex>
{button}
</Flex>
)
);
export const GithubRepositoryConnection: React.FC = () => { export const GithubRepositoryConnection: React.FC = () => {
const { queryLoading, queryUserAndOrganizations } = useGithubStore(); const { queryLoading, queryUserAndOrganizations } = useGithubStore();
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
@ -64,14 +43,14 @@ export const GithubRepositoryConnection: React.FC = () => {
}; };
return ( return (
<Card.Container css={{ maxWidth: '$107h', maxHeight: '$95h', pb: '$0h' }}> <Card.Container css={{ maxWidth: '$107h', maxHeight: '$95h', pr: '$3h' }}>
<MintCardHeader <MintCardHeader
title="Select Repository" title="Select Repository"
onClickBack={handlePrevStepClick} onClickBack={handlePrevStepClick}
/> />
<Card.Body css={{ pt: '$4' }}> <Card.Body css={{ pt: '$4' }}>
<Grid css={{ rowGap: '$2' }}> <Grid css={{ rowGap: '$2' }}>
<Flex css={{ gap: '$4' }}> <Flex css={{ gap: '$4', pr: '$3h' }}>
<UserOrgsCombobox /> <UserOrgsCombobox />
<Input <Input
leftIcon="search" leftIcon="search"

View File

@ -39,6 +39,7 @@ export const RepositoriesList = ({ searchValue }: RepositoriesListProps) => {
overflowX: 'hidden', overflowX: 'hidden',
overflowY: 'scroll', overflowY: 'scroll',
flexDirection: 'column', flexDirection: 'column',
pr: '$3h',
}} }}
> >
{filteredRepositories.length > 0 ? ( {filteredRepositories.length > 0 ? (

View File

@ -1,7 +1,7 @@
import { Button, Separator } from '@/components'; import { Button, Separator } from '@/components';
import { githubActions, GithubState, useAppDispatch } from '@/store'; import { githubActions, GithubState, useAppDispatch } from '@/store';
import { Mint } from '@/views/mint/mint.context'; import { Mint } from '@/views/mint/mint.context';
import { RepoRow } from './github-repository-selection'; import { RepoRow } from '../repository-row';
type RepositoryProps = { type RepositoryProps = {
repository: GithubState.Repository; repository: GithubState.Repository;
@ -21,13 +21,13 @@ export const Repository = ({ repository, index, length }: RepositoryProps) => {
return ( return (
<> <>
<RepoRow <RepoRow
onClick={handleSelectRepo}
repo={repository.name} repo={repository.name}
button={ button={
<Button <Button
colorScheme="blue" colorScheme="blue"
variant="outline" variant="outline"
css={{ py: '$1', height: '$5', borderRadius: '$md' }} css={{ py: '$1', height: '$5', borderRadius: '$md' }}
onClick={handleSelectRepo}
> >
Use for NFA Use for NFA
</Button> </Button>

View File

@ -0,0 +1,28 @@
import { Flex, Icon } from '@/components';
import { forwardRef } from 'react';
type RepoRowProps = {
repo: string;
button: React.ReactNode;
} & React.ComponentProps<typeof Flex>;
export const RepoRow = forwardRef<HTMLDivElement, RepoRowProps>(
({ repo, button, ...props }, ref) => (
<Flex
{...props}
ref={ref}
css={{
justifyContent: 'space-between',
my: '$4',
...props.css,
cursor: 'pointer',
}}
>
<Flex css={{ alignItems: 'center' }}>
<Icon name="github" css={{ fontSize: '$2xl', mr: '$2' }} />
<span>{repo}</span>
</Flex>
{button}
</Flex>
)
);

View File

@ -1,24 +1,30 @@
import { Form } from '@/components'; import { Form } from '@/components';
import { Mint } from '../../../mint.context'; import { Mint } from '../../../mint.context';
const maxCharacters = 250;
export const AppDescriptionField = () => { export const AppDescriptionField = () => {
const { appDescription, setAppDescription } = Mint.useContext(); const { appDescription, setAppDescription } = Mint.useContext();
const handleAppDescriptionChange = ( const handleAppDescriptionChange = (
e: React.ChangeEvent<HTMLTextAreaElement> e: React.ChangeEvent<HTMLTextAreaElement>
) => { ) => {
if (e.target.value.length > maxCharacters) return;
setAppDescription(e.target.value); setAppDescription(e.target.value);
}; };
return ( return (
<Form.Field> <Form.Field>
<Form.Label>Description</Form.Label> <Form.Label isRequired>Description</Form.Label>
<Form.Textarea <Form.Textarea
placeholder="Add information about your project here." placeholder="Add information about your project here."
css={{ height: 'auto' }} css={{ height: 'auto' }}
value={appDescription} value={appDescription}
onChange={handleAppDescriptionChange} onChange={handleAppDescriptionChange}
/> />
<Form.MaxLength>
{appDescription.length}/{maxCharacters}
</Form.MaxLength>
</Form.Field> </Form.Field>
); );
}; };

View File

@ -1,6 +1,8 @@
import { Form } from '@/components'; import { Form } from '@/components';
import { Mint } from '../../../mint.context'; import { Mint } from '../../../mint.context';
const maxCharacters = 100;
export const AppNameField = () => { export const AppNameField = () => {
const { appName, setAppName } = Mint.useContext(); const { appName, setAppName } = Mint.useContext();
@ -9,12 +11,15 @@ export const AppNameField = () => {
}; };
return ( return (
<Form.Field> <Form.Field>
<Form.Label>Name</Form.Label> <Form.Label isRequired>Name</Form.Label>
<Form.Input <Form.Input
placeholder="Your app name" placeholder="Your app name"
value={appName} value={appName}
onChange={handleAppNameChange} onChange={handleAppNameChange}
/> />
<Form.MaxLength>
{appName.length}/{maxCharacters}
</Form.MaxLength>
</Form.Field> </Form.Field>
); );
}; };

View File

@ -9,7 +9,7 @@ export const DomainField = () => {
}; };
return ( return (
<Form.Field css={{ flex: 1 }}> <Form.Field css={{ flex: 1 }}>
<Form.Label>Domain</Form.Label> <Form.Label isRequired>Domain</Form.Label>
<Form.Input <Form.Input
placeholder="mydomain.com" placeholder="mydomain.com"
value={domain} value={domain}

View File

@ -27,7 +27,7 @@ export const LogoField = () => {
return ( return (
<Flex css={{ width: '$full', gap: '$4h', alignItems: 'flex-start' }}> <Flex css={{ width: '$full', gap: '$4h', alignItems: 'flex-start' }}>
<Form.Field> <Form.Field>
<Form.Label>Logo</Form.Label> <Form.Label isRequired>Logo</Form.Label>
<Form.LogoFileInput value={appLogo} onChange={handleFileChange} /> <Form.LogoFileInput value={appLogo} onChange={handleFileChange} />
{errorMessage && <Form.Error>{errorMessage}</Form.Error>} {errorMessage && <Form.Error>{errorMessage}</Form.Error>}
</Form.Field> </Form.Field>