Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add <Header /> component #26

Merged
merged 10 commits into from
Dec 19, 2021
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