-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from KSET/develop
Mostly finish current design
- Loading branch information
Showing
26 changed files
with
1,894 additions
and
158 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
Oops, something went wrong.