[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 { useDisconnect } from 'wagmi';
import { useGQLClient } from '../context/GQLClientContext';
import AsyncSelect from './shared/AsyncSelect';
import { useGQLClient } from 'context/GQLClientContext';
import AsyncSelect from 'components/shared/AsyncSelect';
import {
ChevronGrabberHorizontal,
FolderIcon,
@ -14,10 +14,11 @@ import {
LifeBuoyIcon,
QuestionMarkRoundIcon,
SettingsSlidersIcon,
} from './shared/CustomIcon';
} from 'components/shared/CustomIcon';
import { Tabs } from 'components/shared/Tabs';
import { Heading } from 'components/shared/Heading';
const Sidebar = () => {
export const Sidebar = () => {
const { orgSlug } = useParams();
const navigate = useNavigate();
const client = useGQLClient();
@ -42,20 +43,20 @@ const Sidebar = () => {
}, [disconnect, navigate]);
return (
<div className="flex flex-col h-full p-4 pt-10">
<div className="grow">
<div className="flex flex-col h-full px-6 py-8 gap-9">
{/* Logo */}
<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
src="/logo.svg"
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">
Snowball
</span>
<Heading className="text-[24px] font-semibold">Snowball</Heading>
</div>
</Link>
{/* Switch organization */}
<div className="flex flex-1 flex-col gap-4">
<AsyncSelect
containerProps={{ className: 'h-14 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" />}
>
{/* TODO: Show label organization and manage in option */}
{/* // TODO: Show label organization and manage in option */}
{organizations.map((org) => (
<Option key={org.id} value={org.slug}>
<div className="flex items-center space-x-3">
@ -99,7 +100,7 @@ const Sidebar = () => {
</Option>
))}
</AsyncSelect>
<Tabs defaultValue="Projects" orientation="vertical" className="mt-10">
<Tabs defaultValue="Projects" orientation="vertical">
<Tabs.List>
{[
{ title: 'Projects', url: `/${orgSlug}/`, icon: <FolderIcon /> },
@ -118,9 +119,10 @@ const Sidebar = () => {
</Tabs.List>
</Tabs>
</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">
{/* TODO: use proper link buttons */}
{/* // TODO: use proper link buttons */}
<Tabs.List>
<Tabs.Trigger icon={<GlobeIcon />} value="">
<a className="cursor-pointer" onClick={handleLogOut}>
@ -139,5 +141,3 @@ const Sidebar = () => {
</div>
);
};
export default Sidebar;

View File

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

View File

@ -1,8 +1,8 @@
import React from 'react';
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 = () => {
return (

View File

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