Skip to content

Commit

Permalink
feat(app): add <Header /> and <Avatar /> components (#26)
Browse files Browse the repository at this point in the history
* chore(studio): initial code for header component

* refactor(studio): conditionally showing underline on link hover state

* chore(studio): add basic header

* added header dropdown menu && several fixes and improvements

* fix(app): resolved pckg deps

* feat(ui): add <Avatar /> component && various tweeks

* feat(studio): add menu header component

* refactor(ui): change <Avatar /> root styles && bumped pckg
  • Loading branch information
Kerosz authored Dec 19, 2021
1 parent 085ed85 commit b226a6d
Show file tree
Hide file tree
Showing 17 changed files with 1,658 additions and 1,722 deletions.
12 changes: 12 additions & 0 deletions apps/studio/src/components/common/Header/Header.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.blui-root {
@apply bg-mauve-50 border-b border-mauve-600 py-4;
}

.blui-separator {
@apply h-8 w-[1px] bg-gray-600 transform-gpu ml-7 mr-6;
}

.blui-page--text {
@apply font-medium;
/* @apply bg-clip-text !text-transparent bg-gradient-btn-active !decoration-crimson-700 !decoration-2; */
}
50 changes: 50 additions & 0 deletions apps/studio/src/components/common/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Image from "next/image";
import Logo from "~/assets/images/biolnk.png";
import Menu from "./Menu";
import Link from "../Link";
import { BaseIcon, Container, Text, Copy, Button } from "@biolnk/ui";
import { ctl } from "@biolnk/utils";

import Styles from "./Header.module.css";

const Header = () => {
const rootClass = ctl(`
${Styles["blui-root"]}
`);

return (
<header className={rootClass}>
<Container className="flex items-center justify-between h-full">
<div className="flex items-center">
<Image
src={Logo}
height={36}
width={22}
placeholder="blur"
alt="Biolnk.me branding"
title="Biolnk.me branding"
/>
<div className={`${Styles["blui-separator"]} rotate-[30deg]`} />

<Link url="/chirila" variant="basic">
<Text as="span" className={Styles["blui-page--text"]}>
biolnk.me/chirila
</Text>
</Link>
<Button variant="text" size="xs" title="Copy to clipboard">
<BaseIcon
aria-label="Copy to clipboard"
icon={Copy}
size="lg"
stroke="hsl(336 73.7% 53.5%)"
/>
</Button>
</div>

<Menu />
</Container>
</header>
);
};

export default Header;
39 changes: 39 additions & 0 deletions apps/studio/src/components/common/Header/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Link from "../Link";
import { Plus, Dropdown, Avatar } from "@biolnk/ui";
import { useSupabase } from "~/lib/supabase";
import { Routes } from "~/data/enums/routes";

const Menu = () => {
const { signOut } = useSupabase();

return (
<Dropdown
trigger={
<button type="button">
<Avatar alt="chirila" />
</button>
}
>
<Dropdown.Group>
{/* @ts-ignore */}
<Dropdown.ListItem as={Link} url={Routes.DASHBOARD}>
Dashboard
</Dropdown.ListItem>
</Dropdown.Group>

<Dropdown.Group>
<Dropdown.ListItem rightIcon={Plus}>Add Link</Dropdown.ListItem>
{/* @ts-ignore */}
<Dropdown.ListItem as={Link} url={Routes.ACCOUNT}>
Settings
</Dropdown.ListItem>
</Dropdown.Group>

<Dropdown.Group>
<Dropdown.ListItem onClick={signOut}>Log out</Dropdown.ListItem>
</Dropdown.Group>
</Dropdown>
);
};

export default Menu;
5 changes: 4 additions & 1 deletion apps/studio/src/components/common/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export interface LinkProps
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
url: string;
external?: boolean;
variant?: "hover" | "basic";
}

const Link = forwardRef<HTMLAnchorElement, LinkProps>(
(
{
url,
external = false,
variant = "basic",
children,
className,
replace,
Expand All @@ -22,11 +24,12 @@ const Link = forwardRef<HTMLAnchorElement, LinkProps>(
},
ref
) => {
const isHover = variant === "hover";
const matchInternal = url.startsWith("/") || url.startsWith("#");

const rootClass = ctl(`
${className}
hover:underline
${isHover && "hover:underline"}
`);

// Use Next Link for internal links, and <a> for others
Expand Down
8 changes: 5 additions & 3 deletions apps/studio/src/components/layout/SigningPageLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";
import Image from "next/image";
import Logo from "~/assets/images/biolnk.png";
import { BaseIcon, Button, Heading, Text } from "@biolnk/ui";
import { Button, Heading, Text } from "@biolnk/ui";
import { Facebook, Twitter } from "react-feather";

export interface SigningPageLayoutProps {
Expand Down Expand Up @@ -52,15 +52,17 @@ const SigningPageLayout: React.FC<SigningPageLayoutProps> = ({
<Button
size="md"
block
icon={<BaseIcon icon={Twitter} fill="#1DA1F2" />}
icon={Twitter}
iconProps={{ fill: "#1DA1F2" }}
>
{socialBtnText} With Twitter
</Button>

<Button
size="md"
block
icon={<BaseIcon icon={Facebook} fill="#4267B2" />}
icon={Facebook}
iconProps={{ fill: "#4267B2" }}
>
{socialBtnText} With Facebook
</Button>
Expand Down
1 change: 1 addition & 0 deletions apps/studio/src/data/enums/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum Routes {
SIGNIN = "/signin",
SIGNUP = "/signup",
DASHBOARD = "/",
ACCOUNT = "/account",
}
11 changes: 9 additions & 2 deletions apps/studio/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useRouter } from "next/router";
import { Text } from "@biolnk/ui";
import { Routes } from "~/data/enums/routes";
import { useSupabase } from "~/lib/supabase";
import Header from "~/components/common/Header/Header";

export default function HomePage() {
const { isAuthenticated } = useSupabase();
Expand All @@ -15,5 +17,10 @@ export default function HomePage() {
if (!isAuthenticated) router.replace(Routes.SIGNIN);
}, []);

return <h1>Studio</h1>;
return (
<>
<Header />
<Text size="lg">Studio</Text>
</>
);
}
3 changes: 2 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@biolnk/ui",
"version": "0.7.1",
"version": "0.8.0",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down Expand Up @@ -38,6 +38,7 @@
}
],
"dependencies": {
"@radix-ui/react-avatar": "^0.1.3",
"react-feather": "^2.0.9"
},
"devDependencies": {
Expand Down
11 changes: 11 additions & 0 deletions packages/ui/src/components/Avatar/Avatar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.blui-root {
@apply inline-flex items-center justify-center overflow-hidden select-none rounded-full bg-mauve-200 w-11 h-11 border border-mauve-400 shadow-sm;
}

.blui-image {
@apply w-full h-full object-cover rounded-full;
}

.blui-fallback {
@apply w-full h-full flex items-center justify-center text-plum-800 font-medium;
}
60 changes: 60 additions & 0 deletions packages/ui/src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { forwardRef, useCallback } from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { ctl } from "@biolnk/utils";

import Styles from "./Avatar.module.css";

const DEFAULT_TAG = "span";

export interface AvatarOwnProps
extends React.ComponentPropsWithRef<typeof DEFAULT_TAG> {
src?: string;
alt: string;
fallback?: string;
delay?: number;
}

const Avatar = forwardRef<HTMLSpanElement, AvatarOwnProps>(
({ src, alt, fallback, delay = 600, className, ...otherProps }, ref) => {
const getFallback = useCallback((value: string) => {
const fallbackValue = [];
const splitValue = value.split(" ");
const secondWord = splitValue[1];

fallbackValue.push(value.charAt(0));

if (secondWord && secondWord.length > 0) {
fallbackValue.push(secondWord.charAt(0));
} else {
fallbackValue.push(value.charAt(1));
}

return fallbackValue.join("").toUpperCase();
}, []);

const defaultFallback = fallback ?? getFallback(alt);

const rootClass = ctl(`
${Styles["blui-root"]}
${className}
`);

return (
<AvatarPrimitive.Root ref={ref} className={rootClass} {...otherProps}>
<AvatarPrimitive.Image
className={Styles["blui-image"]}
src={src}
alt={alt}
/>
<AvatarPrimitive.Fallback
delayMs={delay}
className={Styles["blui-fallback"]}
>
{defaultFallback}
</AvatarPrimitive.Fallback>
</AvatarPrimitive.Root>
);
}
);

export { Avatar };
2 changes: 1 addition & 1 deletion packages/ui/src/components/BaseIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const BaseIcon: React.FC<BaseIconProps> = ({
"2xl": 30,
"3xl": 42,
};
const DEFAULT_SIZE = SIZES_MAP["lg"];
const DEFAULT_SIZE = SIZES_MAP["sm"];

let iconSize: number = DEFAULT_SIZE;

Expand Down
10 changes: 5 additions & 5 deletions packages/ui/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { forwardRef } from "react";
import BaseIcon from "../BaseIcon";
import BaseIcon, { BaseIconProps } from "../BaseIcon";
import { Icon } from "react-feather";
import { ctl } from "@biolnk/utils";
import { Loader } from "react-feather";
Expand All @@ -25,6 +25,7 @@ export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
icon?: Icon;
/** Renders an icon on the `right` side of the button */
iconRight?: Icon;
iconProps?: Omit<BaseIconProps, "icon">;
/** `Boolean` used to activate the `loading` state */
loading?: boolean;
/** Removes the button text and centers the loading spinner */
Expand All @@ -46,7 +47,6 @@ export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
/** Size variant for the button icon
* @default `xs`
*/
iconSize?: "xs" | "sm" | "md" | "lg" | "xl";
style?: React.CSSProperties;
/** Style variants for the button
* @default `default`
Expand Down Expand Up @@ -96,7 +96,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
children,
icon,
iconRight,
iconSize = "xs",
iconProps,
loading = false,
loadingCentered = false,
customLoadingText = undefined,
Expand Down Expand Up @@ -184,12 +184,12 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
{loading ? (
<BaseIcon icon={Loader} size={size} className={loadingClass} />
) : icon ? (
<BaseIcon icon={icon} size={iconSize} />
<BaseIcon icon={icon} {...iconProps} />
) : null}
{children && (
<span className={textClass}>{loading ? loadingText : children}</span>
)}
{iconRight && !loading && <BaseIcon icon={iconRight} size={iconSize} />}
{iconRight && !loading && <BaseIcon icon={iconRight} {...iconProps} />}
</RenderedButton>
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/Dropdown/Dropdown.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
}

.blui-menu--items {
@apply absolute right-0 w-56 mt-2 py-1 origin-top-right bg-white divide-y divide-mauve-300 rounded shadow-lg focus:outline-none;
@apply absolute right-0 w-56 mt-2 py-1 origin-top-right bg-mauve-50 divide-y divide-mauve-300 rounded-md shadow-lg focus:outline-none;
}
.blui-listItem {
@apply flex items-center w-full px-5 py-2 text-sm;
Expand Down
9 changes: 7 additions & 2 deletions packages/ui/src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,19 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
<Button
size="xs"
icon={Copy}
iconSize="xs"
iconProps={{ size: "xs" }}
onClick={() => onCopy(value || defaultValue)}
>
{copyLabel}
</Button>
) : null}
{masked && reveal ? (
<Button size="xs" icon={Key} iconSize="xs" onClick={onReveal}>
<Button
size="xs"
icon={Key}
iconProps={{ size: "xs" }}
onClick={onReveal}
>
Reveal
</Button>
) : null}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export * from "./components/Container/Container";
export * from "./components/Text/Text";
export * from "./components/Heading/Heading";
export * from "./components/Dropdown/Dropdown";
export * from "./components/Avatar/Avatar";
// types
export * from "./types/index";
Loading

1 comment on commit b226a6d

@vercel
Copy link

@vercel vercel bot commented on b226a6d Dec 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.