[T-4907: style] Re-styling home page & sidebar component (#132)

* ️ feat: create heading component

* ♻️ refactor: move sidebar inside shared components

* ️ feat: add polymorphic prop type for heading component

* 🎨 style: re-styling project list page

* ♻️ refactor: remove `.env`

* 🎨 style: set default font weight to normal for heading component
This commit is contained in:
Wahyu Kurniawan 2024-02-28 12:50:30 +07:00 committed by GitHub
parent 282001c317
commit 62734308fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 102 additions and 54 deletions

View File

@ -1,7 +0,0 @@
REACT_APP_SERVER_URL = 'http://localhost:8000'
REACT_APP_GITHUB_CLIENT_ID =
REACT_APP_GITHUB_PWA_TEMPLATE_REPO =
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO =
REACT_APP_WALLET_CONNECT_ID =

View File

@ -0,0 +1,7 @@
import { tv, type VariantProps } from 'tailwind-variants';
export const headingTheme = tv({
base: ['text-elements-high-em', 'font-display', 'font-normal'],
});
export type HeadingVariants = VariantProps<typeof headingTheme>;

View File

@ -0,0 +1,30 @@
import React, { type PropsWithChildren } from 'react';
import { headingTheme, type HeadingVariants } from './Heading.theme';
import { PolymorphicProps } from 'types/common';
export type HeadingElementType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
export type HeadingProps<Element extends HeadingElementType> =
PropsWithChildren<PolymorphicProps<Element, HeadingVariants>>;
export const Heading = <Element extends HeadingElementType>({
children,
as,
className,
...props
}: HeadingProps<Element>) => {
// Component is the element that will be rendered
const Component = as ?? 'h1';
return (
<Component
{...props}
className={headingTheme({
className,
})}
>
{children}
</Component>
);
};

View File

@ -0,0 +1 @@
export * from './Heading';

View File

@ -5,8 +5,8 @@ import { Organization } from 'gql-client';
import { Option } from '@material-tailwind/react'; import { Option } from '@material-tailwind/react';
import { useDisconnect } from 'wagmi'; import { useDisconnect } from 'wagmi';
import { useGQLClient } from '../context/GQLClientContext'; import { useGQLClient } from 'context/GQLClientContext';
import AsyncSelect from './shared/AsyncSelect'; import AsyncSelect from 'components/shared/AsyncSelect';
import { import {
ChevronGrabberHorizontal, ChevronGrabberHorizontal,
FolderIcon, FolderIcon,
@ -14,10 +14,11 @@ import {
LifeBuoyIcon, LifeBuoyIcon,
QuestionMarkRoundIcon, QuestionMarkRoundIcon,
SettingsSlidersIcon, SettingsSlidersIcon,
} from './shared/CustomIcon'; } from 'components/shared/CustomIcon';
import { Tabs } from 'components/shared/Tabs'; import { Tabs } from 'components/shared/Tabs';
import { Heading } from 'components/shared/Heading';
const Sidebar = () => { export const Sidebar = () => {
const { orgSlug } = useParams(); const { orgSlug } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const client = useGQLClient(); const client = useGQLClient();
@ -42,20 +43,20 @@ const Sidebar = () => {
}, [disconnect, navigate]); }, [disconnect, navigate]);
return ( return (
<div className="flex flex-col h-full p-4 pt-10"> <div className="flex flex-col h-full px-6 py-8 gap-9">
<div className="grow"> {/* Logo */}
<Link to={`/${orgSlug}`}> <Link to={`/${orgSlug}`}>
<div className="flex items-center space-x-3 mb-10 ml-2"> <div className="flex items-center gap-3 px-2">
<img <img
src="/logo.svg" src="/logo.svg"
alt="Snowball Logo" alt="Snowball Logo"
className="h-8 w-8 rounded-lg" className="h-10 w-10 rounded-lg"
/> />
<span className="text-2xl font-bold text-snowball-900"> <Heading className="text-[24px] font-semibold">Snowball</Heading>
Snowball
</span>
</div> </div>
</Link> </Link>
{/* Switch organization */}
<div className="flex flex-1 flex-col gap-4">
<AsyncSelect <AsyncSelect
containerProps={{ className: 'h-14 border-none' }} containerProps={{ className: 'h-14 border-none' }}
labelProps={{ className: 'before:border-none after:border-none' }} labelProps={{ className: 'before:border-none after:border-none' }}
@ -82,7 +83,7 @@ const Sidebar = () => {
)} )}
arrow={<ChevronGrabberHorizontal className="h-4 w-4 text-gray-500" />} arrow={<ChevronGrabberHorizontal className="h-4 w-4 text-gray-500" />}
> >
{/* TODO: Show label organization and manage in option */} {/* // TODO: Show label organization and manage in option */}
{organizations.map((org) => ( {organizations.map((org) => (
<Option key={org.id} value={org.slug}> <Option key={org.id} value={org.slug}>
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
@ -99,7 +100,7 @@ const Sidebar = () => {
</Option> </Option>
))} ))}
</AsyncSelect> </AsyncSelect>
<Tabs defaultValue="Projects" orientation="vertical" className="mt-10"> <Tabs defaultValue="Projects" orientation="vertical">
<Tabs.List> <Tabs.List>
{[ {[
{ title: 'Projects', url: `/${orgSlug}/`, icon: <FolderIcon /> }, { title: 'Projects', url: `/${orgSlug}/`, icon: <FolderIcon /> },
@ -118,9 +119,10 @@ const Sidebar = () => {
</Tabs.List> </Tabs.List>
</Tabs> </Tabs>
</div> </div>
<div className="grow flex flex-col justify-end mb-8"> {/* Bottom navigation */}
<div className="flex flex-col justify-end">
<Tabs defaultValue="Projects" orientation="vertical"> <Tabs defaultValue="Projects" orientation="vertical">
{/* TODO: use proper link buttons */} {/* // TODO: use proper link buttons */}
<Tabs.List> <Tabs.List>
<Tabs.Trigger icon={<GlobeIcon />} value=""> <Tabs.Trigger icon={<GlobeIcon />} value="">
<a className="cursor-pointer" onClick={handleLogOut}> <a className="cursor-pointer" onClick={handleLogOut}>
@ -139,5 +141,3 @@ const Sidebar = () => {
</div> </div>
); );
}; };
export default Sidebar;

View File

@ -0,0 +1 @@
export * from './Sidebar';

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import Sidebar from '../components/Sidebar'; import { OctokitProvider } from 'context/OctokitContext';
import { OctokitProvider } from '../context/OctokitContext'; import { Sidebar } from 'components/shared/Sidebar';
const OrgSlug = () => { const OrgSlug = () => {
return ( return (

View File

@ -3,11 +3,11 @@ import { Link, useParams } from 'react-router-dom';
import { Project } from 'gql-client'; import { Project } from 'gql-client';
import { Button } from 'components/shared/Button'; import { Button } from 'components/shared/Button';
import { Typography, Chip } from '@material-tailwind/react';
import { useGQLClient } from '../../context/GQLClientContext';
import { PlusIcon } from 'components/shared/CustomIcon'; import { PlusIcon } from 'components/shared/CustomIcon';
import { ProjectCard } from 'components/projects/ProjectCard'; import { ProjectCard } from 'components/projects/ProjectCard';
import { Heading } from 'components/shared/Heading';
import { Badge } from 'components/shared/Badge';
import { useGQLClient } from 'context/GQLClientContext';
const Projects = () => { const Projects = () => {
const client = useGQLClient(); const client = useGQLClient();
@ -26,33 +26,31 @@ const Projects = () => {
}, [orgSlug]); }, [orgSlug]);
return ( return (
<div> <section className="px-6 py-6 flex flex-col gap-6">
<div className="flex p-5"> {/* Header */}
<div className="flex items-center">
<div className="grow"> <div className="grow">
<div className="flex gap-2 items-center"> <div className="flex gap-4 items-center">
<Typography variant="h4" placeholder={''}> <Heading as="h2" className="text-[24px]">
Projects Projects
</Typography> </Heading>
<Chip <Badge className="bg-base-bg-alternate text-elements-mid-em h-7 w-7">
className="bg-gray-300 rounded-full static" {projects.length}
value={projects.length} </Badge>
size="sm"
/>
</div> </div>
</div> </div>
<div>
<Link to="projects/create"> <Link to="projects/create">
<Button leftIcon={<PlusIcon />}>Create project</Button> <Button leftIcon={<PlusIcon />}>Create project</Button>
</Link> </Link>
</div> </div>
</div> {/* List of projects */}
<div className="grid grid-cols-3 gap-5 p-5"> <div className="grid grid-cols-3 gap-4">
{projects.length !== 0 && {projects.length > 0 &&
projects.map((project, key) => { projects.map((project, key) => {
return <ProjectCard project={project} key={key} />; return <ProjectCard project={project} key={key} />;
})} })}
</div> </div>
</div> </section>
); );
}; };

View File

@ -1,3 +1,9 @@
import {
type ElementType,
type ComponentPropsWithoutRef,
forwardRef,
} from 'react';
/** /**
* Construct a type by excluding common keys from one type to another. * Construct a type by excluding common keys from one type to another.
* @template T - The type from which to omit properties. * @template T - The type from which to omit properties.
@ -7,3 +13,15 @@
* @returns A new type that includes all properties from T except those that are common with U. * @returns A new type that includes all properties from T except those that are common with U.
*/ */
export type OmitCommon<T, U> = Pick<T, Exclude<keyof T, keyof U>>; export type OmitCommon<T, U> = Pick<T, Exclude<keyof T, keyof U>>;
export type PolymorphicProps<Element extends ElementType, Props> = Props &
Omit<ComponentPropsWithoutRef<Element>, 'as'> & {
as?: Element;
};
// taken from : https://github.com/total-typescript/react-typescript-tutorial/blob/main/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx
type FixedForwardRef = <T, P = object>(
render: (props: P, ref: React.Ref<T>) => React.ReactNode,
) => (props: P & React.RefAttributes<T>) => JSX.Element;
export const fixedForwardRef = forwardRef as FixedForwardRef;