Skip to content

Commit

Permalink
feat: Use consistent design for card components
Browse files Browse the repository at this point in the history
This patch removes repeated definitions of card-like components and
introduces reusable `Card` and `LinkCard` components instead. There
are no longer any cards with a border, however, `LinkCard` shows a
border when hovering/focused (like `JobPanel` did previously).
  • Loading branch information
meyfa committed Jul 12, 2024
1 parent d33015d commit 26c6a7b
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 55 deletions.
16 changes: 16 additions & 0 deletions frontend/src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FunctionComponent, PropsWithChildren } from 'react'
import clsx from 'clsx'

export const Card: FunctionComponent<PropsWithChildren<{
className?: string
}>> = (props) => {
return (
<div className={clsx(
'my-2 p-4 rounded bg-[#2b2d30]',
props.className
)}
>
{props.children}
</div>
)
}
9 changes: 5 additions & 4 deletions frontend/src/components/InfoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { FunctionComponent, PropsWithChildren } from 'react'
import { ColoredSkeleton } from './ColoredSkeleton.js'
import { Card } from './Card.js'

export const InfoCard: FunctionComponent<PropsWithChildren<{
title?: string
}>> = (props) => {
return (
<div className='mb-2 px-4 py-2 rounded bg-[#2b2d30]'>
<dt className='text-sm text-gray-200'>{props.title ?? <ColoredSkeleton />}</dt>
<dd className='mt-1 text-white-900 font-semibold'>{props.children ?? <ColoredSkeleton />}</dd>
</div>
<Card>
<div className='text-sm text-gray-200'>{props.title ?? <ColoredSkeleton />}</div>
<div className='mt-2 text-white-900 font-semibold'>{props.children ?? <ColoredSkeleton />}</div>
</Card>
)
}
13 changes: 6 additions & 7 deletions frontend/src/components/JobPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Link } from 'react-router-dom'
import { DateTime } from 'luxon'
import { Annotation } from './Annotation.js'
import { faPaintRoller } from '@fortawesome/free-solid-svg-icons'
import { Card } from './Card.js'
import { LinkCard } from './LinkCard.js'

export const JobPanel: FunctionComponent<{
namespace: string | undefined
Expand All @@ -15,23 +17,20 @@ export const JobPanel: FunctionComponent<{
}> = (props) => {
if (props.namespace == null || props.name == null) {
return (
<div className='block mb-2 p-4 bg-[#2b2d30] border-2 border-[#43454a] rounded'>
<Card>
<div className='flex items-center'>
<StatusDot status={props.status} />
<div className='ml-2 flex-1'>
<ColoredSkeleton className='block' />
</div>
</div>
<ColoredSkeleton />
</div>
</Card>
)
}

return (
<Link
to={`/jobs/${props.namespace}/${props.name}`}
className='block mb-2 p-4 bg-[#2b2d30] border-2 border-[#43454a] rounded hocus:border-white/25'
>
<LinkCard to={`/jobs/${props.namespace}/${props.name}`}>
<div className='flex items-center'>
<StatusDot status={props.status} />
<span className='ml-2'>{props.namespace}/{props.name}</span>
Expand All @@ -42,6 +41,6 @@ export const JobPanel: FunctionComponent<{
<Annotation icon={faPaintRoller} text='Manual' />
)}
</div>
</Link>
</LinkCard>
)
}
2 changes: 1 addition & 1 deletion frontend/src/components/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const Label: FunctionComponent<PropsWithChildren<{
text: string
}>> = (props) => {
return (
<label className='block my-6'>
<label className='block my-6 first:mt-0'>
<div className='text-gray-200 mb-1'>{props.text}</div>
{props.children}
</label>
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/components/LinkCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ComponentPropsWithoutRef, FunctionComponent, PropsWithChildren } from 'react'
import { Link } from 'react-router-dom'
import clsx from 'clsx'

export const LinkCard: FunctionComponent<PropsWithChildren<{
className?: string
}> & ComponentPropsWithoutRef<typeof Link>> = (props) => {
return (
<Link
{...props}
className={clsx(
'block my-2 p-4 rounded bg-[#2b2d30] border-2 border-transparent hocus:border-white/25 outline-none',
props.className
)}
>
{props.children}
</Link>
)
}
4 changes: 2 additions & 2 deletions frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const Home: FunctionComponent = () => {
const isMobileNavigation = useMobileNavigation()

return (
<div>
<>
<Heading>
Overview
</Heading>
Expand All @@ -45,7 +45,7 @@ export const Home: FunctionComponent = () => {
</InfoCard>
</dl>
{isMobileNavigation && <JobsSection />}
</div>
</>
)
}

Expand Down
21 changes: 11 additions & 10 deletions frontend/src/pages/Job.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ErrorMessage } from '../components/ErrorMessage.js'
import { DateTime } from 'luxon'
import { Button } from '../components/Button.js'
import clsx from 'clsx'
import { Card } from '../components/Card.js'

export const Job: FunctionComponent = () => {
const { namespace, name } = useParams()
Expand All @@ -30,7 +31,7 @@ export const Job: FunctionComponent = () => {
const dialogPod = pods?.find((pod) => pod.name === dialogPodName)

return (
<div>
<>
<Heading>
{job != null ? <span>Job: {job.namespace}/{job.name}</span> : <ColoredSkeleton />}
</Heading>
Expand Down Expand Up @@ -58,7 +59,7 @@ export const Job: FunctionComponent = () => {
onClose={() => setDialogPodName(undefined)}
/>
)}
</div>
</>
)
}

Expand Down Expand Up @@ -115,25 +116,25 @@ const JobStatus: FunctionComponent<{

if (!props.job.manual) {
return (
<div className='p-4 flex flex-col gap-1 rounded bg-[#2b2d30]'>
<Card className='flex flex-col gap-1'>
<p className='mb-2 font-semibold'>
<Icon icon={faClock} className='mr-2' />
This job was scheduled to run automatically.
</p>
{common}
</div>
</Card>
)
}

return (
<div className='p-4 flex flex-col gap-1 rounded bg-[#2b2d30]'>
<Card className='flex flex-col gap-1'>
<p className='text-orange-300 mb-2 font-semibold'>
<Icon icon={faPaintRoller} className='mr-2' />
{props.job.triggeredBy == null && 'This job was triggered manually by an unknown user.'}
{props.job.triggeredBy != null && `This job was triggered manually by ${props.job.triggeredBy.username} (${props.job.triggeredBy.strategy} login).`}
</p>
{common}
</div>
</Card>
)
}

Expand Down Expand Up @@ -171,7 +172,7 @@ const Pod: FunctionComponent<{
}, [progress])

return (
<div className='mb-2 p-4 bg-[#2b2d30] border-2 border-[#43454a] rounded'>
<Card>
{/* name and actions */}
<div className='flex flex-row items-center justify-between'>
<div>
Expand Down Expand Up @@ -217,13 +218,13 @@ const Pod: FunctionComponent<{
<ColoredSkeleton className='mr-4 px-2' />
</div>
}
</div>
</Card>
)
}

const DummyPod: FunctionComponent = () => {
return (
<div className='mb-2 p-4 bg-[#2b2d30] border-2 border-[#43454a] rounded'>
<Card>
{/* name and actions */}
<div className='flex flex-row items-center justify-between'>
<div className='flex items-center'>
Expand All @@ -247,7 +248,7 @@ const DummyPod: FunctionComponent = () => {
<div className='mt-4 pt-4 border-t-2 border-white/25'>
<ColoredSkeleton className='mr-4 px-2' />
</div>
</div>
</Card>
)
}

Expand Down
9 changes: 5 additions & 4 deletions frontend/src/pages/Jobs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { api } from '../api/api.js'
import { Heading } from '../components/Heading.js'
import { JobPanel } from '../components/JobPanel.js'
import { ErrorMessage } from '../components/ErrorMessage.js'
import { Card } from '../components/Card.js'

export const Jobs: FunctionComponent = () => {
const { loading, data: jobs, error } = useApiSubscription({ interval: 5_000 }, api.jobs)

return (
<div>
<>
<Heading>
Job History
</Heading>
Expand All @@ -19,9 +20,9 @@ export const Jobs: FunctionComponent = () => {
</ErrorMessage>
)}
{jobs != null && jobs.length === 0 && (
<p>
<Card>
There are no previous jobs. Once a job is run, it will appear here automatically.
</p>
</Card>
)}
{jobs?.map((job) => (
<JobPanel
Expand All @@ -43,6 +44,6 @@ export const Jobs: FunctionComponent = () => {
manual={false}
/>
))}
</div>
</>
)
}
13 changes: 7 additions & 6 deletions frontend/src/pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Heading } from '../components/Heading.js'
import { useSearchParams } from 'react-router-dom'
import { useApiSubscription } from '../api/subscription.js'
import { ErrorMessage } from '../components/ErrorMessage.js'
import { Card } from '../components/Card.js'

export const Login: FunctionComponent = () => {
const [searchParams] = useSearchParams()
Expand All @@ -16,35 +17,35 @@ export const Login: FunctionComponent = () => {
const authStrategies = useApiSubscription({ interval: 60_000 }, api.authStrategies)

return (
<div>
<>
<Heading>
Login
</Heading>
{authStrategies.data != null && (
<div className='max-w-[32rem]'>
{authStrategies.data.length === 0 && (
<div>
<Card>
No login methods are available.
</div>
</Card>
)}
{authStrategies.data.includes('oidc') && <OidcLogin hasError={error === 'oidc'} />}
{authStrategies.data.includes('local') && <LocalLogin hasError={error === 'local'} />}
</div>
)}
</div>
</>
)
}

const LoginOption: FunctionComponent<PropsWithChildren<{
title: string
}>> = (props) => {
return (
<div className='border-2 border-white/25 rounded p-4 mb-6'>
<Card>
<p className='text-lg mb-4'>
{props.title}
</p>
{props.children}
</div>
</Card>
)
}

Expand Down
45 changes: 24 additions & 21 deletions frontend/src/pages/Trigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TextInput } from '../components/TextInput.js'
import { InputInfo } from '../components/InputInfo.js'
import { Button } from '../components/Button.js'
import { ToggleSwitch } from '../components/ToggleSwitch.js'
import { Card } from '../components/Card.js'

export const Trigger: FunctionComponent = () => {
const navigate = useNavigate()
Expand Down Expand Up @@ -47,29 +48,31 @@ export const Trigger: FunctionComponent = () => {
}, [jobData, navigate])

return (
<div>
<>
<Heading>
Custom run
</Heading>
<form onSubmit={onTrigger} className='max-w-[32rem]'>
<Label text='Custom repository scope'>
<TextInput value={repositoryScope} onChange={(value) => setRepositoryScope(value)} />
<InputInfo>
If empty, the job will run on all configured repositories.
Optionally, you can specify a repository scope in the format <code>owner/name</code>.
</InputInfo>
</Label>
<Label text='Debug logging'>
<ToggleSwitch value={debugLogging} onChange={(value) => setDebugLogging(value)} />
<InputInfo>
Note: This option may cause very large logs that may be slow to load.
Choose a specific repository scope, if possible.
</InputInfo>
</Label>
<Button type='submit' disabled={!isValid || inProgress}>
Run job
</Button>
</form>
</div>
<Card className='max-w-[32rem]'>
<form onSubmit={onTrigger}>
<Label text='Custom repository scope'>
<TextInput value={repositoryScope} onChange={(value) => setRepositoryScope(value)} />
<InputInfo>
If empty, the job will run on all configured repositories.
Optionally, you can specify a repository scope in the format <code>owner/name</code>.
</InputInfo>
</Label>
<Label text='Debug logging'>
<ToggleSwitch value={debugLogging} onChange={(value) => setDebugLogging(value)} />
<InputInfo>
Note: This option may cause very large logs that may be slow to load.
Choose a specific repository scope, if possible.
</InputInfo>
</Label>
<Button type='submit' disabled={!isValid || inProgress}>
Run job
</Button>
</form>
</Card>
</>
)
}

0 comments on commit 26c6a7b

Please sign in to comment.