feat: add more generic Select component
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Fragment, useState } from "react"
|
import { Fragment, useState } from "react"
|
||||||
|
import { type Key } from "react-aria-components"
|
||||||
import { Minus } from "react-feather"
|
import { Minus } from "react-feather"
|
||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
@@ -9,6 +10,7 @@ import { _ } from "@/lib/translation"
|
|||||||
import CheckCircle from "@/components/Icons/CheckCircle"
|
import CheckCircle from "@/components/Icons/CheckCircle"
|
||||||
import ChevronDown from "@/components/Icons/ChevronDown"
|
import ChevronDown from "@/components/Icons/ChevronDown"
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
|
import Select from "@/components/TempDesignSystem/Form/Select"
|
||||||
import Title from "@/components/Title"
|
import Title from "@/components/Title"
|
||||||
|
|
||||||
import levelsData from "./data/EN.json"
|
import levelsData from "./data/EN.json"
|
||||||
@@ -114,16 +116,18 @@ export default function OverviewTable() {
|
|||||||
const [selectedLevelB, setSelectedLevelB] = useState(getLevelByTier(2))
|
const [selectedLevelB, setSelectedLevelB] = useState(getLevelByTier(2))
|
||||||
|
|
||||||
// TODO Come up with a nice wat to make these two a single reusable function
|
// TODO Come up with a nice wat to make these two a single reusable function
|
||||||
function handleSelectChangeA(event: React.ChangeEvent<HTMLSelectElement>) {
|
function handleSelectChangeA(key: Key) {
|
||||||
const tier = parseInt(event.target.value)
|
if (typeof key === "number") {
|
||||||
const level = getLevelByTier(tier)
|
const level = getLevelByTier(key)
|
||||||
setSelectedLevelA(level)
|
setSelectedLevelA(level)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelectChangeB(event: React.ChangeEvent<HTMLSelectElement>) {
|
function handleSelectChangeB(key: Key) {
|
||||||
const tier = parseInt(event.target.value)
|
if (typeof key === "number") {
|
||||||
const level = getLevelByTier(tier)
|
const level = getLevelByTier(key)
|
||||||
setSelectedLevelB(level)
|
setSelectedLevelB(level)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const levelOptions = levelsData.levels.map((level) => ({
|
const levelOptions = levelsData.levels.map((level) => ({
|
||||||
@@ -149,9 +153,11 @@ export default function OverviewTable() {
|
|||||||
<div className={styles.columnHeaderContainer}>
|
<div className={styles.columnHeaderContainer}>
|
||||||
<div className={styles.columnHeader}>
|
<div className={styles.columnHeader}>
|
||||||
<Select
|
<Select
|
||||||
options={levelOptions}
|
name={"benefitA"}
|
||||||
defaultOption={selectedLevelA.tier}
|
label={"Level"}
|
||||||
onChange={handleSelectChangeA}
|
items={levelOptions}
|
||||||
|
defaultSelectedKey={selectedLevelA.tier}
|
||||||
|
onSelect={handleSelectChangeA}
|
||||||
/>
|
/>
|
||||||
<LevelSummary
|
<LevelSummary
|
||||||
level={
|
level={
|
||||||
@@ -163,9 +169,11 @@ export default function OverviewTable() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.columnHeader}>
|
<div className={styles.columnHeader}>
|
||||||
<Select
|
<Select
|
||||||
options={levelOptions}
|
name={"benefitA"}
|
||||||
defaultOption={selectedLevelB.tier}
|
label={"Level"}
|
||||||
onChange={handleSelectChangeB}
|
items={levelOptions}
|
||||||
|
defaultSelectedKey={selectedLevelB.tier}
|
||||||
|
onSelect={handleSelectChangeB}
|
||||||
/>
|
/>
|
||||||
<LevelSummary
|
<LevelSummary
|
||||||
level={
|
level={
|
||||||
@@ -182,37 +190,6 @@ export default function OverviewTable() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectProps = {
|
|
||||||
options: {
|
|
||||||
label: string
|
|
||||||
value: number
|
|
||||||
}[]
|
|
||||||
defaultOption: number
|
|
||||||
onChange: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
|
||||||
}
|
|
||||||
// TODO: replace with Select component from TempDesignSystem
|
|
||||||
function Select({ options, defaultOption, onChange }: SelectProps) {
|
|
||||||
return (
|
|
||||||
<div className={styles.selectContainer}>
|
|
||||||
<select
|
|
||||||
className={styles.select}
|
|
||||||
onChange={onChange}
|
|
||||||
defaultValue={defaultOption}
|
|
||||||
>
|
|
||||||
{options.map((option) => (
|
|
||||||
<option key={option.label} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<label className={styles.selectLabel}>{_("Level")}</label>
|
|
||||||
<span className={styles.selectChevron}>
|
|
||||||
<ChevronDown />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function LevelSummary({ level }: LevelSummaryProps) {
|
function LevelSummary({ level }: LevelSummaryProps) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.levelSummary}>
|
<div className={styles.levelSummary}>
|
||||||
|
|||||||
79
components/TempDesignSystem/Form/Select/index.tsx
Normal file
79
components/TempDesignSystem/Form/Select/index.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"use client"
|
||||||
|
import { useRef } from "react"
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
type Key,
|
||||||
|
Label,
|
||||||
|
ListBox,
|
||||||
|
ListBoxItem,
|
||||||
|
Popover,
|
||||||
|
Select as ReactAriaSelect,
|
||||||
|
SelectValue,
|
||||||
|
} from "react-aria-components"
|
||||||
|
|
||||||
|
import SelectChevron from "../SelectChevron"
|
||||||
|
|
||||||
|
import styles from "./select.module.css"
|
||||||
|
|
||||||
|
import type { SelectProps } from "./select"
|
||||||
|
|
||||||
|
export default function Select({
|
||||||
|
"aria-label": ariaLabel,
|
||||||
|
items,
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
onSelect,
|
||||||
|
placeholder,
|
||||||
|
value,
|
||||||
|
defaultSelectedKey,
|
||||||
|
}: SelectProps) {
|
||||||
|
const divRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
function handleOnSelect(key: Key) {
|
||||||
|
onSelect(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.date} ref={divRef}>
|
||||||
|
<ReactAriaSelect
|
||||||
|
defaultSelectedKey={defaultSelectedKey}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
className={styles.select}
|
||||||
|
onSelectionChange={handleOnSelect}
|
||||||
|
placeholder={placeholder}
|
||||||
|
selectedKey={value as Key}
|
||||||
|
>
|
||||||
|
<Label className={styles.label}>{label}</Label>
|
||||||
|
<Button className={styles.input}>
|
||||||
|
<SelectValue />
|
||||||
|
<SelectChevron />
|
||||||
|
</Button>
|
||||||
|
<Popover
|
||||||
|
className={styles.popover}
|
||||||
|
placement="bottom"
|
||||||
|
shouldFlip={false}
|
||||||
|
/**
|
||||||
|
* react-aria uses portals to render Popover in body
|
||||||
|
* unless otherwise specified. We need it to be contained
|
||||||
|
* by this component to both access css variables assigned
|
||||||
|
* on the container as well as to not overflow it at any time.
|
||||||
|
*/
|
||||||
|
UNSTABLE_portalContainer={divRef.current ?? undefined}
|
||||||
|
>
|
||||||
|
<ListBox className={styles.listBox}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<ListBoxItem
|
||||||
|
aria-label={String(item)}
|
||||||
|
className={styles.listBoxItem}
|
||||||
|
id={item.value}
|
||||||
|
key={item.label}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</ListBoxItem>
|
||||||
|
))}
|
||||||
|
</ListBox>
|
||||||
|
</Popover>
|
||||||
|
</ReactAriaSelect>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
57
components/TempDesignSystem/Form/Select/select.module.css
Normal file
57
components/TempDesignSystem/Form/Select/select.module.css
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
.date {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--Base-Text-UI-Placeholder);
|
||||||
|
font-family: var(--ff-fira-sans);
|
||||||
|
font-size: var(--typography-Footnote-Regular-fontSize);
|
||||||
|
font-weight: 400;
|
||||||
|
position: absolute;
|
||||||
|
left: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
align-items: end;
|
||||||
|
background-color: var(--some-white-color, #fff);
|
||||||
|
border: 1px solid var(--Base-Input-Controls-Border-Normal, #b8a79a);
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
color: var(--some-black-color, #757575);
|
||||||
|
display: grid;
|
||||||
|
font-family: var(--ff-fira-sans);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 400;
|
||||||
|
gap: 1rem;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
height: 5.6rem;
|
||||||
|
/* letter-spacing: -1.5%; */
|
||||||
|
line-height: 2.4rem;
|
||||||
|
padding: 0.8rem 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover {
|
||||||
|
background-color: var(--some-white-color, #fff);
|
||||||
|
border: var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listBox {
|
||||||
|
padding: 1.6rem 1.6rem 1.6rem 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listBoxItem {
|
||||||
|
padding: 0 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listBoxItem[data-selected="true"],
|
||||||
|
.listBoxItem[data-focused="true"] {
|
||||||
|
background-color: rgba(75, 75, 75, 0.2);
|
||||||
|
}
|
||||||
11
components/TempDesignSystem/Form/Select/select.ts
Normal file
11
components/TempDesignSystem/Form/Select/select.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { Key } from "react-aria-components"
|
||||||
|
|
||||||
|
export interface SelectProps
|
||||||
|
extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "onSelect"> {
|
||||||
|
items: { label: string; value: Key }[]
|
||||||
|
label: string
|
||||||
|
name: string
|
||||||
|
onSelect: (key: Key) => void
|
||||||
|
placeholder?: string
|
||||||
|
defaultSelectedKey: Key
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user