Skip to content

Commit 1552580

Browse files
committed
Finish the blog overview page
1 parent 25c3cbc commit 1552580

File tree

23 files changed

+565
-264
lines changed

23 files changed

+565
-264
lines changed

package-lock.json

+80-158
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"script:gen-articles-index": "node ./scripts/generateArticlesIndex.js"
1111
},
1212
"dependencies": {
13+
"@headlessui/react": "^1.7.13",
14+
"@headlessui/tailwindcss": "^0.1.2",
1315
"@heroicons/react": "^2.0.16",
1416
"@hookform/error-message": "^2.0.1",
1517
"@hookform/resolvers": "^2.9.11",

src/animations/fade.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Variants } from "framer-motion";
2+
3+
const fadeAnim = {
4+
in: {
5+
opacity: 0
6+
},
7+
anim: {
8+
opacity: 1,
9+
transition: {
10+
duration: 0.35
11+
}
12+
},
13+
exit: {
14+
opacity: 0,
15+
transition: {
16+
duration: 0.35
17+
}
18+
}
19+
} as Variants;
20+
21+
export default fadeAnim;

src/assets/state/articles/1.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import thumbnail from "@/assets/images/content/blog/1/thumbnail.webp";
66

77
export default {
88
title: "1",
9-
9+
1010
thumbnail,
1111
thumbnailAlt: "A placeholder thumbnail",
1212

@@ -18,7 +18,7 @@ export default {
1818

1919
author: people.rik,
2020

21-
teaser: "1",
21+
teaser: "Lorem ipsum dolor sit amet consectetur Voluptates facere quasi repellat doloremque quae saepe?",
2222
content: <>
2323
1
2424
</>,

src/assets/state/articles/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import IArticle from "./article";
2+
23
import article1 from "./1";
34
import article2 from "./2";
45

src/assets/state/team.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export interface IMember {
1414
export const people = {
1515
"rik": {
1616
image: RikPicture,
17-
name: "Rik",
17+
name: "Rik den Breejen",
1818
title: "Lead Developer & Founder",
1919
links: [
2020
{

src/components/controls/Button.tsx

+5-22
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { twMerge } from "tailwind-merge";
55

66
import { RequiredKeys } from "@/types/utility";
77
import ArrowPathIcon from "@heroicons/react/24/solid/ArrowPathIcon";
8+
import fadeAnim from "@/animations/fade";
89

9-
export const style = cva("relative flex items-center justify-center transition-colors shadow-sm px-3 py-2 gap-2 rounded-full", {
10+
export const style = cva("relative flex items-center justify-center transition-colors shadow-sm px-3 py-2 gap-2 rounded-full border-2", {
1011
variants: {
1112
color: {
12-
white: "bg-neutral-200 hover:bg-neutral-50 border-2 border-neutral-400 text-neutral-900",
13-
secondary: "bg-secondary hover:bg-secondary-light border-2 border-secondary-dark text-secondary-contrast"
13+
white: "bg-neutral-200 hover:bg-neutral-50 border-neutral-400 text-neutral-900",
14+
primary: "bg-primary hover:bg-primary-light border-primary-dark text-primary-contrast",
15+
secondary: "bg-secondary hover:bg-secondary-light border-secondary-dark text-secondary-contrast"
1416
}
1517
}
1618
});
@@ -27,25 +29,6 @@ type ButtonProps = {
2729
& RequiredKeys<VariantProps, "color">
2830
& DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
2931

30-
31-
const fadeAnim = {
32-
in: {
33-
opacity: 0
34-
},
35-
anim: {
36-
opacity: 1,
37-
transition: {
38-
duration: 0.35
39-
}
40-
},
41-
exit: {
42-
opacity: 0,
43-
transition: {
44-
duration: 0.35
45-
}
46-
}
47-
} as const;
48-
4932
const Button = forwardRef(({ className, color, loading, children, disabled, ...props }: ButtonProps, ref: ForwardedRef<HTMLButtonElement>) => {
5033

5134
const computedClassName = useMemo(

src/components/controls/Form.tsx

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,6 @@
11
import { ReactNode } from "react";
22
import { AnimatePresence, HTMLMotionProps, motion } from "framer-motion";
3-
4-
const fadeAnim = {
5-
in: {
6-
opacity: 0
7-
},
8-
anim: {
9-
opacity: 1,
10-
transition: {
11-
duration: 0.35
12-
}
13-
},
14-
exit: {
15-
opacity: 0,
16-
transition: {
17-
duration: 0.35
18-
}
19-
}
20-
} as const;
3+
import fadeAnim from "@/animations/fade";
214

225
export interface FormProps extends HTMLMotionProps<"form"> {
236
children: ReactNode;

src/components/controls/Select.tsx

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { ForwardedRef, forwardRef, ReactNode, useMemo } from "react";
2+
import { Listbox } from "@headlessui/react";
3+
import { twMerge } from "tailwind-merge";
4+
import { cva } from "class-variance-authority";
5+
import { Control, Controller, FieldPath, FieldValues, PathValue, UseControllerProps } from "react-hook-form";
6+
import { AnimatePresence, motion, Variants } from "framer-motion";
7+
8+
import { style as inputStyle, VariantProps as InputVariantProps } from "./Input";
9+
import { mergeRefs } from "@/utils/react";
10+
import ChevronDownIcon from "@heroicons/react/24/solid/ChevronDownIcon";
11+
12+
export const style = inputStyle;
13+
export type VariantProps = InputVariantProps;
14+
15+
export const containerStyle = cva("absolute border-2 bg-white left-1/2 -translate-x-1/2 top-full rounded-lg min-w-fit w-full overflow-hidden translate-y-1 z-50", {
16+
variants: {
17+
color: {
18+
primary: "border-primary",
19+
secondary: "border-secondary"
20+
}
21+
}
22+
});
23+
export const itemStyle = cva("px-4 py-2", {
24+
variants: {
25+
color: {
26+
primary: "ui-active:bg-primary/20",
27+
secondary: "ui-active:bg-secondary/20"
28+
}
29+
}
30+
});
31+
32+
export type BaseProps<TOption extends any, TOptions extends readonly TOption[] = readonly TOption[]> = {
33+
name?: string;
34+
className?: string;
35+
containerClassName?: string;
36+
itemClassName?: string;
37+
value: TOption;
38+
getDisplayName: (option: TOption) => ReactNode;
39+
options: TOptions;
40+
onChange: (option: TOption) => void;
41+
onBlur?: () => void;
42+
};
43+
44+
export type SelectProps<TOption extends any> = BaseProps<TOption> & VariantProps;
45+
46+
const optionsAnim = {
47+
in: {
48+
height: 0,
49+
paddingBlock: 0,
50+
},
51+
anim: {
52+
height: "auto",
53+
paddingBlock: "0.5rem",
54+
transition: {
55+
duration: 0.15
56+
}
57+
},
58+
exit: {
59+
height: 0,
60+
paddingBlock: 0,
61+
transition: {
62+
duration: 0.15
63+
}
64+
}
65+
} as Variants;
66+
67+
const Select = forwardRef(<TOption extends any>(
68+
{
69+
name,
70+
className,
71+
containerClassName,
72+
itemClassName,
73+
value,
74+
color,
75+
options,
76+
getDisplayName,
77+
onChange,
78+
onBlur
79+
}: SelectProps<TOption>,
80+
ref: ForwardedRef<HTMLButtonElement>
81+
) => {
82+
83+
const computedClassName = useMemo(() => twMerge(
84+
"relative flex items-center",
85+
style({ color }),
86+
className
87+
), [color, className]);
88+
89+
const computedContainerClassName = useMemo(() => twMerge(
90+
containerStyle({ color }),
91+
containerClassName
92+
), [color, containerClassName]);
93+
94+
const computedItemClassName = useMemo(() => twMerge(
95+
itemStyle({ color }),
96+
itemClassName
97+
), [color, itemClassName]);
98+
99+
return (
100+
101+
<Listbox name={name} value={value} onChange={onChange}>
102+
<Listbox.Button
103+
ref={ref}
104+
onBlur={onBlur}
105+
className={computedClassName}
106+
>
107+
{({ open }) => (
108+
<>
109+
{getDisplayName(value)}
110+
<ChevronDownIcon className="inline w-4 h-4 ml-2 motion-safe:transition-transform ui-open:rotate-180" />
111+
<AnimatePresence>
112+
{open && <motion.div
113+
key="options"
114+
className={computedContainerClassName}
115+
variants={optionsAnim}
116+
initial="in"
117+
animate="anim"
118+
exit="exit"
119+
>
120+
<Listbox.Options static>
121+
{options.map((option, i) => (
122+
<Listbox.Option key={i} value={option} className={computedItemClassName}>
123+
{getDisplayName(option)}
124+
</Listbox.Option>
125+
))}
126+
</Listbox.Options>
127+
</motion.div>}
128+
</AnimatePresence>
129+
</>
130+
)}
131+
</Listbox.Button>
132+
</Listbox>
133+
);
134+
});
135+
136+
export type ControlledSelectProps<Values extends FieldValues, TOption extends any, Name extends FieldPath<Values> = FieldPath<Values>> = {
137+
name: Name,
138+
defaultValue?: PathValue<Values, Name>;
139+
control: Control<Values>;
140+
}
141+
& Omit<SelectProps<TOption>, "value" | "onChange">
142+
& Pick<UseControllerProps<Values>, "rules" | "shouldUnregister">;;
143+
144+
export const ControlledSelect = forwardRef(<Values extends FieldValues, TOption extends any>(
145+
{
146+
name,
147+
control,
148+
defaultValue,
149+
rules,
150+
shouldUnregister,
151+
...props
152+
}: ControlledSelectProps<Values, TOption>,
153+
ref: ForwardedRef<HTMLButtonElement>
154+
) => (
155+
<Controller
156+
name={name}
157+
control={control}
158+
rules={rules}
159+
defaultValue={defaultValue}
160+
shouldUnregister={shouldUnregister}
161+
render={({ field: { ref: controllerRef, value, ...controlledProps } }) => (
162+
<Select
163+
ref={mergeRefs(ref, controllerRef)}
164+
value={value}
165+
{...props}
166+
{...controlledProps}
167+
/>
168+
)}
169+
/>
170+
));
171+
172+
173+
export default Select;

src/components/navigation/Link.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export const style = cva("transition-colors gap-2", {
1515
color: {
1616
white: "text-white hover:text-neutral-300",
1717
primary: "text-primary hover:text-primary-light",
18-
secondary: "text-secondary hover:text-secondary-light"
18+
secondary: "text-secondary hover:text-secondary-light",
19+
"fill-contrast": "text-fill-contrast hover:text-fill-contrast-light"
1920
}
2021
},
2122
defaultVariants: {

src/components/navigation/NavDropdown.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const NavDropdown = ({ summary, children }: NavDropdownProps) => {
5454
data-open={open}
5555
/>
5656
</summary>
57-
<div ref={containerRef} className="absolute inset-x-0 flex flex-col gap-2 px-4 py-2 border-2 rounded-md group/dropdown border-primary bg-fill md:w-fit" data-in-group>
57+
<div ref={containerRef} className="absolute inset-x-0 flex flex-col gap-2 px-4 py-2 bg-white border-2 rounded-lg group/dropdown border-primary md:w-fit" data-in-group>
5858
{children}
5959
</div>
6060
</details>

0 commit comments

Comments
 (0)