Skip to content

Commit

Permalink
Merge pull request #12 from KSET/develop
Browse files Browse the repository at this point in the history
Mostly finish current design
  • Loading branch information
Allypost committed Sep 19, 2023
2 parents 6d97a52 + 4aa0874 commit b901765
Show file tree
Hide file tree
Showing 26 changed files with 1,894 additions and 158 deletions.
17 changes: 17 additions & 0 deletions src/assets/images/logo/kset-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions src/components/base/carousel/ImageCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable jsx-a11y/alt-text */
import { type FC } from "react";

import { type Assign } from "~/types/object";

import AspectRatio, { type AspectRatioProps } from "../aspect-ratio";
import { Carousel, type CarouselProps } from ".";

export type ImageCarouselImage = {
alt: string;
src: string;
aspect?: AspectRatioProps;
};

export type ImageCarouselPropsStrict = {
images: ImageCarouselImage[];
};

export type ImageCarouselProps = Assign<
CarouselProps,
ImageCarouselPropsStrict
>;

export const ImageCarousel: FC<ImageCarouselProps> = ({ images, ...props }) => {
return (
<Carousel
className="max-br:[--slide-size-override:100%]"
displayed={3}
{...props}
>
{images.map(({ aspect, ...props }) => {
return (
<Carousel.Item key={props.src}>
<AspectRatio ratio={1.2} {...aspect}>
<img
className="h-full w-full object-cover"
decoding="async"
loading="lazy"
{...props}
/>
</AspectRatio>
</Carousel.Item>
);
})}
</Carousel>
);
};
21 changes: 12 additions & 9 deletions src/components/base/carousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
RiArrowRightSLine as IconChevronRight,
} from "react-icons/ri";

import { type Dict } from "~/types/object";
import { type Assign, type Dict } from "~/types/object";
import { cn } from "~/utils/class";

const CarouselItem: FC<PropsWithChildren> = ({ children }) => {
Expand Down Expand Up @@ -54,14 +54,17 @@ const CarouselArrow: FC<{
);
};

export const Carousel: FC<
PropsWithChildren<
HTMLProps<HTMLDivElement> & {
displayed?: number;
options?: EmblaOptionsType;
}
>
> & {
export type CarouselPropsStrict = {
displayed?: number;
options?: EmblaOptionsType;
};

export type CarouselProps = Assign<
PropsWithChildren<HTMLProps<HTMLDivElement>>,
CarouselPropsStrict
>;

export const Carousel: FC<CarouselProps> & {
Item: typeof CarouselItem;
} = ({ children, displayed, options: emblaOptions, ...divProps }) => {
const [emblaRef, emblaApi] = useEmblaCarousel({
Expand Down
12 changes: 9 additions & 3 deletions src/components/base/footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import Image from "next/image";
import Link from "next/link";
import { type FC } from "react";
import { type FC, type HTMLProps } from "react";
import { RiArrowRightSLine as IconChevronRight } from "react-icons/ri";

import KsetLogo from "~/assets/common/kset-logo.png";
import { cn } from "~/utils/class";

export const BaseFooter: FC = () => {
export const BaseFooter: FC<HTMLProps<HTMLElement>> = (props) => {
return (
<footer className="pb-safe mt-auto overflow-hidden bg-secondary">
<footer
{...props}
className={cn(
"pb-safe mt-auto overflow-hidden bg-secondary",
props.className,
)}
>
<div className="container flex flex-wrap-reverse gap-8 pb-20 pt-8 max-br:pb-8">
<div className="flex-auto">
<Link href="/">
Expand Down
13 changes: 10 additions & 3 deletions src/components/base/header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Image from "next/image";
import Link from "next/link";
import { type FC } from "react";
import { type FC, type HTMLProps } from "react";

import KsetLogo from "~/assets/common/kset-logo.png";
import { cn } from "~/utils/class";

import { NavDrawer, type NavItem } from "./components";

export const BaseHeader: FC = () => {
export const BaseHeader: FC<HTMLProps<HTMLElement>> = (props) => {
const headerItems = [
{
text: "Program",
Expand Down Expand Up @@ -35,7 +36,13 @@ export const BaseHeader: FC = () => {
] satisfies NavItem[];

return (
<header className="relative mb-6 mt-6 flex h-6 transition-[height,margin] br:mb-14 br:mt-12 br:h-8">
<header
{...props}
className={cn(
"relative mb-6 mt-6 flex h-6 transition-[height,margin] br:mb-14 br:mt-12 br:h-8",
props.className,
)}
>
<Link href="/">
<Image
priority
Expand Down
24 changes: 24 additions & 0 deletions src/components/base/input/app-input.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.select {
position: relative;

select {
appearance: none;
}

.selectArrow {
@apply p-2 pr-3;
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
height: 100%;
width: auto;
aspect-ratio: 1 / 1;

svg {
height: 100%;
width: auto;
opacity: 0.7;
}
}
}
195 changes: 195 additions & 0 deletions src/components/base/input/app-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import {
cloneElement,
type HTMLProps,
type PropsWithChildren,
type ReactElement,
type ReactNode,
useId,
useState,
} from "react";
import { RiArrowDownSLine as IconChevronDown } from "react-icons/ri";

import { type Assign } from "~/types/object";
import { type Maybe } from "~/types/util";
import { cn } from "~/utils/class";

import $style from "./app-input.module.scss";

// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
export const AppInput = <TValue extends unknown>(
props: HTMLProps<HTMLDivElement> & {
label?: string;
name: string;
initialValue?: TValue;
iconBefore?: ReactNode;
iconAfter?: ReactNode;
onChange?: (val: TValue) => void | never;
},
) => {
return (
<AppInputBase
{...props}
input={
<input className="flex-1 bg-transparent p-3 pr-11 leading-none text-inherit" />
}
/>
);
};

export const AppTextarea = (
props: HTMLProps<HTMLDivElement> & {
label?: string;
name: string;
initialValue?: string;
iconBefore?: ReactNode;
iconAfter?: ReactNode;
onChange?: (val: string) => void | never;
},
) => {
return (
<AppInputBase
{...props}
input={
<textarea
className="flex-1 bg-transparent p-3 pr-11 leading-none text-inherit"
placeholder={props.placeholder}
>
{props.value}
</textarea>
}
/>
);
};

export const AppSelect = ({
options,
children,
placeholder,
...props
}: HTMLProps<HTMLDivElement> & {
label?: string;
name: string;
initialValue?: string;
options: Maybe<{
label: string;
value: string;
}>[];
iconBefore?: ReactNode;
iconAfter?: ReactNode;
onChange?: (val: string) => void | never;
}) => {
return (
<AppInputBase
{...props}
inputContainerClassName={$style.select}
input={
<select
className="flex-1 appearance-none bg-transparent p-3 text-inherit"
defaultValue=""
placeholder={placeholder}
>
{placeholder ? (
<option
key="$$placeholder"
disabled
className="bg-off-black"
value=""
>
{placeholder}
</option>
) : null}
{options.filter(Boolean).map((option) => {
return (
<option
key={option.value}
className="bg-off-black"
value={option.value}
>
{option.label}
</option>
);
})}
</select>
}
>
<span className={$style.selectArrow}>
<IconChevronDown />
</span>
{children}
</AppInputBase>
);
};

export const AppInputBase = <TValue, TElement extends ReactElement>({
label,
name,
input,
initialValue,
children,
inputContainerClassName,
iconBefore,
iconAfter,
onChange,
...props
}: Assign<
PropsWithChildren<HTMLProps<HTMLDivElement>>,
{
label?: string;
name: string;
initialValue?: TValue;
inputContainerClassName?: string;
input: TElement;
iconBefore?: ReactNode;
iconAfter?: ReactNode;
onChange?: (val: TValue) => void | never;
}
>) => {
const inputId = useId();
const inputElId = `input-${name}-${inputId}`;
const [value, setValue] = useState(initialValue);

return (
<div
{...props}
className={cn(
"flex w-full flex-col gap-1.5 tracking-wide",
props.className,
)}
>
<label
className="text-[.9em] uppercase leading-3 opacity-80"
htmlFor={inputElId}
>
{label ?? name}
</label>
<div
className={cn(
"relative flex overflow-hidden rounded-sm bg-off-black text-white",
inputContainerClassName,
)}
>
{iconBefore ? (
<div className="pointer-events-none absolute right-0 aspect-square h-full p-3 [&>*]:h-full [&>*]:w-auto [&>*]:opacity-70">
{iconBefore}
</div>
) : null}
{cloneElement(input, {
id: inputElId,
value,
onChange: (e: { target: { value: TValue } }) => {
const val = e.target.value;

onChange?.(val);
setValue(val);
},
})}
{children}
{iconAfter ? (
<div className="pointer-events-none absolute right-0 aspect-square h-full p-3 [&>*]:h-full [&>*]:w-auto [&>*]:opacity-70">
{iconAfter}
</div>
) : null}
</div>
</div>
);
};
Loading

0 comments on commit b901765

Please sign in to comment.