From c37f0754fb17abd84acf62e3d9291ab042c9bf17 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 5 Dec 2022 18:04:12 -0800 Subject: [PATCH] Implement radix select --- components/Fieldset/index.scss | 2 +- components/Party/index.tsx | 200 ++++++++++++------------- components/PartyDetails/index.scss | 13 +- components/PartyDetails/index.tsx | 225 +++++++++++++---------------- components/RaidDropdown/index.tsx | 124 +++++++++------- components/Select/index.scss | 51 +++++++ components/Select/index.tsx | 49 +++++++ components/SelectGroup/index.scss | 25 ++++ components/SelectGroup/index.tsx | 33 +++++ components/SelectItem/index.scss | 11 ++ components/SelectItem/index.tsx | 21 +++ styles/globals.scss | 2 +- styles/themes.scss | 12 ++ styles/variables.scss | 33 ++++- 14 files changed, 512 insertions(+), 289 deletions(-) create mode 100644 components/Select/index.scss create mode 100644 components/Select/index.tsx create mode 100644 components/SelectGroup/index.scss create mode 100644 components/SelectGroup/index.tsx create mode 100644 components/SelectItem/index.scss create mode 100644 components/SelectItem/index.tsx diff --git a/components/Fieldset/index.scss b/components/Fieldset/index.scss index 0f641ca8..fbffe370 100644 --- a/components/Fieldset/index.scss +++ b/components/Fieldset/index.scss @@ -3,7 +3,7 @@ display: inline-flex; flex-direction: column; padding: 0; - margin: 0 0 $unit 0; + margin: 0; .Input { -webkit-font-smoothing: antialiased; diff --git a/components/Party/index.tsx b/components/Party/index.tsx index a0107446..6fcc15b9 100644 --- a/components/Party/index.tsx +++ b/components/Party/index.tsx @@ -1,55 +1,55 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { useRouter } from "next/router"; -import { useSnapshot } from "valtio"; -import { getCookie } from "cookies-next"; -import clonedeep from "lodash.clonedeep"; +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useRouter } from 'next/router' +import { useSnapshot } from 'valtio' +import { getCookie } from 'cookies-next' +import clonedeep from 'lodash.clonedeep' -import PartySegmentedControl from "~components/PartySegmentedControl"; -import PartyDetails from "~components/PartyDetails"; -import WeaponGrid from "~components/WeaponGrid"; -import SummonGrid from "~components/SummonGrid"; -import CharacterGrid from "~components/CharacterGrid"; +import PartySegmentedControl from '~components/PartySegmentedControl' +import PartyDetails from '~components/PartyDetails' +import WeaponGrid from '~components/WeaponGrid' +import SummonGrid from '~components/SummonGrid' +import CharacterGrid from '~components/CharacterGrid' -import api from "~utils/api"; -import { appState, initialAppState } from "~utils/appState"; -import { GridType, TeamElement } from "~utils/enums"; +import api from '~utils/api' +import { appState, initialAppState } from '~utils/appState' +import { GridType, TeamElement } from '~utils/enums' -import "./index.scss"; +import './index.scss' // Props interface Props { - new?: boolean; - team?: Party; - raids: Raid[][]; - pushHistory?: (path: string) => void; + new?: boolean + team?: Party + raids: Raid[][] + pushHistory?: (path: string) => void } const Party = (props: Props) => { // Cookies - const cookie = getCookie("account"); + const cookie = getCookie('account') const accountData: AccountCookie = cookie ? JSON.parse(cookie as string) - : null; + : null const headers = useMemo(() => { return accountData ? { headers: { Authorization: `Bearer ${accountData.token}` } } - : {}; - }, [accountData]); + : {} + }, [accountData]) // Set up router - const router = useRouter(); + const router = useRouter() // Set up states - const { party } = useSnapshot(appState); - const [currentTab, setCurrentTab] = useState(GridType.Weapon); + const { party } = useSnapshot(appState) + const [currentTab, setCurrentTab] = useState(GridType.Weapon) // Reset state on first load useEffect(() => { - const resetState = clonedeep(initialAppState); - appState.grid = resetState.grid; - if (props.team) storeParty(props.team); - }, []); + const resetState = clonedeep(initialAppState) + appState.grid = resetState.grid + if (props.team) storeParty(props.team) + }, []) // Methods: Creating a new party async function createParty(extra: boolean = false) { @@ -58,14 +58,14 @@ const Party = (props: Props) => { ...(accountData && { user_id: accountData.userId }), extra: extra, }, - }; + } - return await api.endpoints.parties.create(body, headers); + return await api.endpoints.parties.create(body, headers) } // Methods: Updating the party's details function checkboxChanged(event: React.ChangeEvent) { - appState.party.extra = event.target.checked; + appState.party.extra = event.target.checked if (party.id) { api.endpoints.parties.update( @@ -74,7 +74,7 @@ const Party = (props: Props) => { party: { extra: event.target.checked }, }, headers - ); + ) } } @@ -98,11 +98,11 @@ const Party = (props: Props) => { headers ) .then(() => { - appState.party.name = name; - appState.party.description = description; - appState.party.raid = raid; - appState.party.updated_at = party.updated_at; - }); + appState.party.name = name + appState.party.description = description + appState.party.raid = raid + appState.party.updated_at = party.updated_at + }) } } @@ -113,95 +113,95 @@ const Party = (props: Props) => { .destroy({ id: appState.party.id, params: headers }) .then(() => { // Push to route - router.push("/"); + router.push('/') // Clean state - const resetState = clonedeep(initialAppState); + const resetState = clonedeep(initialAppState) Object.keys(resetState).forEach((key) => { - appState[key] = resetState[key]; - }); + appState[key] = resetState[key] + }) // Set party to be editable - appState.party.editable = true; + appState.party.editable = true }) .catch((error) => { - console.error(error); - }); + console.error(error) + }) } } // Methods: Storing party data const storeParty = function (party: Party) { // Store the important party and state-keeping values - appState.party.name = party.name; - appState.party.description = party.description; - appState.party.raid = party.raid; - appState.party.updated_at = party.updated_at; - appState.party.job = party.job; - appState.party.jobSkills = party.job_skills; + appState.party.name = party.name + appState.party.description = party.description + appState.party.raid = party.raid + appState.party.updated_at = party.updated_at + appState.party.job = party.job + appState.party.jobSkills = party.job_skills - appState.party.id = party.id; - appState.party.extra = party.extra; - appState.party.user = party.user; - appState.party.favorited = party.favorited; - appState.party.created_at = party.created_at; - appState.party.updated_at = party.updated_at; + appState.party.id = party.id + appState.party.extra = party.extra + appState.party.user = party.user + appState.party.favorited = party.favorited + appState.party.created_at = party.created_at + appState.party.updated_at = party.updated_at // Populate state - storeCharacters(party.characters); - storeWeapons(party.weapons); - storeSummons(party.summons); - }; + storeCharacters(party.characters) + storeWeapons(party.weapons) + storeSummons(party.summons) + } const storeCharacters = (list: Array) => { list.forEach((object: GridCharacter) => { if (object.position != null) - appState.grid.characters[object.position] = object; - }); - }; + appState.grid.characters[object.position] = object + }) + } const storeWeapons = (list: Array) => { list.forEach((gridObject: GridWeapon) => { if (gridObject.mainhand) { - appState.grid.weapons.mainWeapon = gridObject; - appState.party.element = gridObject.object.element; + appState.grid.weapons.mainWeapon = gridObject + appState.party.element = gridObject.object.element } else if (!gridObject.mainhand && gridObject.position != null) { - appState.grid.weapons.allWeapons[gridObject.position] = gridObject; + appState.grid.weapons.allWeapons[gridObject.position] = gridObject } - }); - }; + }) + } const storeSummons = (list: Array) => { list.forEach((gridObject: GridSummon) => { - if (gridObject.main) appState.grid.summons.mainSummon = gridObject; + if (gridObject.main) appState.grid.summons.mainSummon = gridObject else if (gridObject.friend) - appState.grid.summons.friendSummon = gridObject; + appState.grid.summons.friendSummon = gridObject else if ( !gridObject.main && !gridObject.friend && gridObject.position != null ) - appState.grid.summons.allSummons[gridObject.position] = gridObject; - }); - }; + appState.grid.summons.allSummons[gridObject.position] = gridObject + }) + } // Methods: Navigating with segmented control function segmentClicked(event: React.ChangeEvent) { switch (event.target.value) { - case "class": - setCurrentTab(GridType.Class); - break; - case "characters": - setCurrentTab(GridType.Character); - break; - case "weapons": - setCurrentTab(GridType.Weapon); - break; - case "summons": - setCurrentTab(GridType.Summon); - break; + case 'class': + setCurrentTab(GridType.Class) + break + case 'characters': + setCurrentTab(GridType.Character) + break + case 'weapons': + setCurrentTab(GridType.Weapon) + break + case 'summons': + setCurrentTab(GridType.Summon) + break default: - break; + break } } @@ -212,7 +212,7 @@ const Party = (props: Props) => { onClick={segmentClicked} onCheckboxChange={checkboxChanged} /> - ); + ) const weaponGrid = ( { createParty={createParty} pushHistory={props.pushHistory} /> - ); + ) const summonGrid = ( { createParty={createParty} pushHistory={props.pushHistory} /> - ); + ) const characterGrid = ( { createParty={createParty} pushHistory={props.pushHistory} /> - ); + ) const currentGrid = () => { switch (currentTab) { case GridType.Character: - return characterGrid; + return characterGrid case GridType.Weapon: - return weaponGrid; + return weaponGrid case GridType.Summon: - return summonGrid; + return summonGrid } - }; + } return ( -
+ {navigation}
{currentGrid()}
{ @@ -263,8 +263,8 @@ const Party = (props: Props) => { deleteCallback={deleteTeam} /> } -
- ); -}; + + ) +} -export default Party; +export default Party diff --git a/components/PartyDetails/index.scss b/components/PartyDetails/index.scss index 5bf25045..36705e87 100644 --- a/components/PartyDetails/index.scss +++ b/components/PartyDetails/index.scss @@ -1,8 +1,7 @@ .PartyDetails { display: none; // This breaks transition, find a workaround opacity: 0; - margin: 0 auto; - margin-bottom: 100px; + margin: $unit-4x auto 0; max-width: $unit * 95; position: relative; @@ -13,9 +12,10 @@ transition: opacity 0.2s ease-in-out, top 0.2s ease-in-out; &.Visible { - display: block; + display: flex; + flex-direction: column; + gap: $unit; height: auto; - margin-bottom: 40vh; opacity: 1; top: 0; } @@ -34,6 +34,7 @@ display: flex; flex-direction: row; gap: $unit; + margin-bottom: $unit-12x; .left { flex-grow: 1; @@ -108,7 +109,7 @@ } & > *:not(:last-child):after { - content: " · "; + content: ' · '; margin: 0 calc($unit / 2); } } @@ -146,7 +147,7 @@ .EmptyDetails { display: none; justify-content: center; - margin-bottom: $unit * 10; + margin: $unit-4x 0 $unit-10x; &.Visible { display: flex; diff --git a/components/PartyDetails/index.tsx b/components/PartyDetails/index.tsx index 3f94a598..651268ce 100644 --- a/components/PartyDetails/index.tsx +++ b/components/PartyDetails/index.tsx @@ -1,81 +1,81 @@ -import React, { useState } from "react"; -import Head from "next/head"; -import { useRouter } from "next/router"; -import { useSnapshot } from "valtio"; -import { useTranslation } from "next-i18next"; +import React, { useState } from 'react' +import Head from 'next/head' +import { useRouter } from 'next/router' +import { useSnapshot } from 'valtio' +import { useTranslation } from 'next-i18next' -import Linkify from "react-linkify"; -import classNames from "classnames"; +import Linkify from 'react-linkify' +import classNames from 'classnames' -import * as AlertDialog from "@radix-ui/react-alert-dialog"; -import CrossIcon from "~public/icons/Cross.svg"; +import * as AlertDialog from '@radix-ui/react-alert-dialog' +import CrossIcon from '~public/icons/Cross.svg' -import Button from "~components/Button"; -import CharLimitedFieldset from "~components/CharLimitedFieldset"; -import RaidDropdown from "~components/RaidDropdown"; -import TextFieldset from "~components/TextFieldset"; +import Button from '~components/Button' +import CharLimitedFieldset from '~components/CharLimitedFieldset' +import RaidDropdown from '~components/RaidDropdown' +import TextFieldset from '~components/TextFieldset' -import { accountState } from "~utils/accountState"; -import { appState } from "~utils/appState"; +import { accountState } from '~utils/accountState' +import { appState } from '~utils/appState' -import "./index.scss"; -import Link from "next/link"; -import { formatTimeAgo } from "~utils/timeAgo"; +import './index.scss' +import Link from 'next/link' +import { formatTimeAgo } from '~utils/timeAgo' const emptyRaid: Raid = { - id: "", + id: '', name: { - en: "", - ja: "", + en: '', + ja: '', }, - slug: "", + slug: '', level: 0, group: 0, element: 0, -}; +} // Props interface Props { - editable: boolean; - updateCallback: (name?: string, description?: string, raid?: Raid) => void; + editable: boolean + updateCallback: (name?: string, description?: string, raid?: Raid) => void deleteCallback: ( event: React.MouseEvent - ) => void; + ) => void } const PartyDetails = (props: Props) => { - const { party, raids } = useSnapshot(appState); - const { account } = useSnapshot(accountState); + const { party, raids } = useSnapshot(appState) + const { account } = useSnapshot(accountState) - const { t } = useTranslation("common"); - const router = useRouter(); - const locale = router.locale || "en"; + const { t } = useTranslation('common') + const router = useRouter() + const locale = router.locale || 'en' - const nameInput = React.createRef(); - const descriptionInput = React.createRef(); - const raidSelect = React.createRef(); + const nameInput = React.createRef() + const descriptionInput = React.createRef() + const raidSelect = React.createRef() const readOnlyClasses = classNames({ PartyDetails: true, ReadOnly: true, Visible: !party.detailsVisible, - }); + }) const editableClasses = classNames({ PartyDetails: true, Editable: true, Visible: party.detailsVisible, - }); + }) const emptyClasses = classNames({ EmptyDetails: true, Visible: !party.detailsVisible, - }); + }) const userClass = classNames({ user: true, empty: !party.user, - }); + }) const linkClass = classNames({ wind: party && party.element == 1, @@ -84,42 +84,42 @@ const PartyDetails = (props: Props) => { earth: party && party.element == 4, dark: party && party.element == 5, light: party && party.element == 6, - }); + }) const [errors, setErrors] = useState<{ [key: string]: string }>({ - name: "", - description: "", - }); + name: '', + description: '', + }) function handleInputChange(event: React.ChangeEvent) { - event.preventDefault(); + event.preventDefault() - const { name, value } = event.target; - let newErrors = errors; + const { name, value } = event.target + let newErrors = errors - setErrors(newErrors); + setErrors(newErrors) } function handleTextAreaChange(event: React.ChangeEvent) { - event.preventDefault(); + event.preventDefault() - const { name, value } = event.target; - let newErrors = errors; + const { name, value } = event.target + let newErrors = errors - setErrors(newErrors); + setErrors(newErrors) } function toggleDetails() { - appState.party.detailsVisible = !appState.party.detailsVisible; + appState.party.detailsVisible = !appState.party.detailsVisible } function updateDetails(event: React.MouseEvent) { - const nameValue = nameInput.current?.value; - const descriptionValue = descriptionInput.current?.value; - const raid = raids.find((raid) => raid.slug === raidSelect.current?.value); + const nameValue = nameInput.current?.value + const descriptionValue = descriptionInput.current?.value + const raid = raids.find((raid) => raid.slug === raidSelect.current?.value) - props.updateCallback(nameValue, descriptionValue, raid); - toggleDetails(); + props.updateCallback(nameValue, descriptionValue, raid) + toggleDetails() } const userImage = () => { @@ -132,18 +132,18 @@ const PartyDetails = (props: Props) => { /profile/${party.user.picture.picture}@2x.png 2x`} src={`/profile/${party.user.picture.picture}.png`} /> - ); - else return
; - }; + ) + else return
+ } const userBlock = () => { return (
{userImage()} - {party.user ? party.user.username : t("no_user")} + {party.user ? party.user.username : t('no_user')}
- ); - }; + ) + } const linkedUserBlock = (user: User) => { return ( @@ -152,8 +152,8 @@ const PartyDetails = (props: Props) => { {userBlock()}
- ); - }; + ) + } const linkedRaidBlock = (raid: Raid) => { return ( @@ -162,8 +162,8 @@ const PartyDetails = (props: Props) => { {raid.name[locale]}
- ); - }; + ) + } const deleteButton = () => { if (party.editable) { @@ -173,36 +173,36 @@ const PartyDetails = (props: Props) => { - {t("buttons.delete")} + {t('buttons.delete')} - {t("modals.delete_team.title")} + {t('modals.delete_team.title')} - {t("modals.delete_team.description")} + {t('modals.delete_team.description')}
- {t("modals.delete_team.buttons.cancel")} + {t('modals.delete_team.buttons.cancel')} props.deleteCallback(e)} > - {t("modals.delete_team.buttons.confirm")} + {t('modals.delete_team.buttons.confirm')}
- ); + ) } else { - return ""; + return '' } - }; + } const editable = (
@@ -217,13 +217,13 @@ const PartyDetails = (props: Props) => { /> {
- {router.pathname !== "/new" ? deleteButton() : ""} + {router.pathname !== '/new' ? deleteButton() : ''}
- ); + ) const readOnly = (
- {party.name ?

{party.name}

: ""} + {party.name ?

{party.name}

: ''}
{party.user ? linkedUserBlock(party.user) : userBlock()} - {party.raid ? linkedRaidBlock(party.raid) : ""} + {party.raid ? linkedRaidBlock(party.raid) : ''} {party.created_at != undefined ? ( ) : ( - "" + '' )}
{party.editable ? ( ) : (
@@ -283,71 +283,52 @@ const PartyDetails = (props: Props) => { {party.description}

) : ( - "" + '' )}
- ); + ) const emptyDetails = (
{party.editable ? ( ) : (
)}
- ); + ) const generateTitle = () => { - let title = party.raid ? `[${party.raid?.name[locale]}] ` : ""; + let title = party.raid ? `[${party.raid?.name[locale]}] ` : '' const username = - party.user != null ? `@${party.user?.username}` : t("header.anonymous"); + party.user != null ? `@${party.user?.username}` : t('header.anonymous') if (party.name != null) - title += t("header.byline", { + title += t('header.byline', { partyName: party.name, username: username, - }); - else if (party.name == null && party.editable && router.route === "/new") - title = t("header.new_team"); + }) + else if (party.name == null && party.editable && router.route === '/new') + title = t('header.new_team') else - title += t("header.untitled_team", { + title += t('header.untitled_team', { username: username, - }); + }) - return title; - }; + return title + } return ( -
- - {generateTitle()} - - - - - - - - - - - + {editable && (party.name || party.description || party.raid) ? readOnly : emptyDetails} {editable} -
- ); -}; + + ) +} -export default PartyDetails; +export default PartyDetails diff --git a/components/RaidDropdown/index.tsx b/components/RaidDropdown/index.tsx index 1e430d7c..50ca41ee 100644 --- a/components/RaidDropdown/index.tsx +++ b/components/RaidDropdown/index.tsx @@ -1,68 +1,78 @@ -import React, { useCallback, useEffect, useState } from "react"; -import { useRouter } from "next/router"; +import React, { useCallback, useEffect, useState } from 'react' +import { useRouter } from 'next/router' -import api from "~utils/api"; -import { appState } from "~utils/appState"; -import { raidGroups } from "~utils/raidGroups"; +import Select from '~components/Select' +import SelectItem from '~components/SelectItem' +import SelectGroup from '~components/SelectGroup' +import { SelectSeparator } from '@radix-ui/react-select' -import "./index.scss"; +import api from '~utils/api' +import { appState } from '~utils/appState' +import { raidGroups } from '~utils/raidGroups' + +import './index.scss' // Props interface Props { - showAllRaidsOption: boolean; - currentRaid?: string; - onChange?: (slug?: string) => void; - onBlur?: (event: React.ChangeEvent) => void; + showAllRaidsOption: boolean + currentRaid?: string + onChange?: (slug?: string) => void + onBlur?: (event: React.ChangeEvent) => void } const RaidDropdown = React.forwardRef( function useFieldSet(props, ref) { // Set up router for locale - const router = useRouter(); - const locale = router.locale || "en"; + const router = useRouter() + const locale = router.locale || 'en' // Set up local states for storing raids - const [currentRaid, setCurrentRaid] = useState(); - const [raids, setRaids] = useState(); - const [sortedRaids, setSortedRaids] = useState(); + const [open, setOpen] = useState(false) + const [currentRaid, setCurrentRaid] = useState() + const [raids, setRaids] = useState() + const [sortedRaids, setSortedRaids] = useState() + + function openRaidSelect() { + setOpen(!open) + } // Organize raids into groups on mount const organizeRaids = useCallback( (raids: Raid[]) => { // Set up empty raid for "All raids" const all = { - id: "0", + id: '0', name: { - en: "All raids", - ja: "全て", + en: 'All raids', + ja: '全て', }, - slug: "all", + slug: 'all', level: 0, group: 0, element: 0, - }; + } const numGroups = Math.max.apply( Math, raids.map((raid) => raid.group) - ); - let groupedRaids = []; + ) + let groupedRaids = [] for (let i = 0; i <= numGroups; i++) { - groupedRaids[i] = raids.filter((raid) => raid.group == i); + groupedRaids[i] = raids.filter((raid) => raid.group == i) } if (props.showAllRaidsOption) { - raids.unshift(all); - groupedRaids[0].unshift(all); + raids.unshift(all) + groupedRaids[0].unshift(all) } - setRaids(raids); - setSortedRaids(groupedRaids); - appState.raids = raids; + setRaids(raids) + setSortedRaids(groupedRaids) + appState.raids = raids }, [props.showAllRaidsOption] - ); + ) // Fetch all raids on mount useEffect(() => { @@ -70,24 +80,25 @@ const RaidDropdown = React.forwardRef( .getAll() .then((response) => organizeRaids(response.data.map((r: any) => r.raid)) - ); - }, [organizeRaids]); + ) + }, [organizeRaids]) // Set current raid on mount useEffect(() => { if (raids && props.currentRaid) { - const raid = raids.find((raid) => raid.slug === props.currentRaid); - setCurrentRaid(raid); + const raid = raids.find((raid) => raid.slug === props.currentRaid) + setCurrentRaid(raid) } - }, [raids, props.currentRaid]); + }, [raids, props.currentRaid]) // Enable changing select value - function handleChange(event: React.ChangeEvent) { - if (props.onChange) props.onChange(event.target.value); + function handleChange(value: string) { + console.log(value) + if (props.onChange) props.onChange(value) if (raids) { - const raid = raids.find((raid) => raid.slug === event.target.value); - setCurrentRaid(raid); + const raid = raids.find((raid) => raid.slug === value) + setCurrentRaid(raid) } } @@ -101,33 +112,36 @@ const RaidDropdown = React.forwardRef( .sort((a, b) => a.element - b.element) .map((item, i) => { return ( - - ); - }); - + + ) + }) return ( - + {options} - - ); + + ) } return ( - - ); + + ) } -); +) -export default RaidDropdown; +export default RaidDropdown diff --git a/components/Select/index.scss b/components/Select/index.scss new file mode 100644 index 00000000..639c1e3a --- /dev/null +++ b/components/Select/index.scss @@ -0,0 +1,51 @@ +.SelectTrigger { + align-items: center; + background-color: var(--input-bg); + border-radius: $input-corner; + border: none; + display: flex; + padding: $unit-2x $unit-2x; + + &:hover { + background-color: var(--input-bg-hover); + cursor: pointer; + } + + &[data-placeholder] > span:not(.SelectIcon) { + color: var(--text-tertiary); + } + + & > span:not(.SelectIcon) { + color: var(--text-primary); + flex-grow: 1; + font-size: $font-regular; + text-align: left; + } + + .SelectIcon { + display: flex; + align-items: center; + } +} + +.Select { + background: var(--card-bg); + border-radius: $input-corner; + border: $hover-stroke; + box-shadow: $hover-shadow; + padding: 0 $unit; + // position: relative; + // max-height: 350px !important; + // top: -800px; + z-index: 99999; + + .Scroll.Up, + .Scroll.Down { + padding: $unit 0; + text-align: center; + } + + .Scroll.Up { + transform: scale(1, -1); + } +} diff --git a/components/Select/index.tsx b/components/Select/index.tsx new file mode 100644 index 00000000..3043d942 --- /dev/null +++ b/components/Select/index.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import * as RadixSelect from '@radix-ui/react-select' + +import ArrowIcon from '~public/icons/Arrow.svg' + +import './index.scss' + +// Props +interface Props { + open: boolean + placeholder?: string + trigger?: React.ReactNode + children?: React.ReactNode + onClick?: () => void + onChange?: (value: string) => void +} + +const Select = React.forwardRef(function useFieldSet( + props, + ref +) { + return ( + + + + + + + + + + + + + + {props.children} + + + + + + + ) +}) + +export default Select diff --git a/components/SelectGroup/index.scss b/components/SelectGroup/index.scss new file mode 100644 index 00000000..5d9267bb --- /dev/null +++ b/components/SelectGroup/index.scss @@ -0,0 +1,25 @@ +.SelectGroup { + .Label { + align-items: center; + color: var(--text-tertiary); + display: flex; + flex-direction: row; + flex-shrink: 0; + font-size: $font-small; + font-weight: $medium; + gap: $unit; + padding: $unit $unit-2x $unit-half; + + &:first-child { + padding-top: $unit-2x; + } + + .Separator { + background: var(--item-bg-hover); + border-radius: 1px; + display: block; + flex-grow: 1; + height: 2px; + } + } +} diff --git a/components/SelectGroup/index.tsx b/components/SelectGroup/index.tsx new file mode 100644 index 00000000..6387b720 --- /dev/null +++ b/components/SelectGroup/index.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import * as RadixSelect from '@radix-ui/react-select' + +import './index.scss' + +// Props +interface Props { + label?: string + separator?: boolean + children?: React.ReactNode +} + +const defaultProps = { + separator: true, +} + +const SelectGroup = (props: Props) => { + return ( + + + + {props.label} + + + {props.children} + + + ) +} + +SelectGroup.defaultProps = defaultProps + +export default SelectGroup diff --git a/components/SelectItem/index.scss b/components/SelectItem/index.scss new file mode 100644 index 00000000..9fa6e240 --- /dev/null +++ b/components/SelectItem/index.scss @@ -0,0 +1,11 @@ +.SelectItem { + border-radius: $item-corner; + color: var(--text-primary); + font-size: $font-regular; + padding: ($unit * 1.5) $unit-2x; + + &:hover { + background-color: var(--item-bg-hover); + cursor: pointer; + } +} diff --git a/components/SelectItem/index.tsx b/components/SelectItem/index.tsx new file mode 100644 index 00000000..9bae53fe --- /dev/null +++ b/components/SelectItem/index.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import * as Select from '@radix-ui/react-select' + +import './index.scss' +import classNames from 'classnames' + +const SelectItem = React.forwardRef( + ({ children, className, ...props }, forwardedRef) => { + return ( + + {children} + + ) + } +) + +export default SelectItem diff --git a/styles/globals.scss b/styles/globals.scss index 78466219..b3994c0c 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -78,7 +78,7 @@ h1 { select { appearance: none; - background-color: var(--input-bg); + background-color: var(--input-bound-bg); background-image: url("/icons/Arrow.svg"); background-repeat: no-repeat; background-position-y: center; diff --git a/styles/themes.scss b/styles/themes.scss index fe319f2c..7b6a8497 100644 --- a/styles/themes.scss +++ b/styles/themes.scss @@ -9,12 +9,18 @@ --input-bg: #{$input--bg--light}; --input-bg-hover: #{$input--bg--light--hover}; + --input-bound-bg: #{$input--bound--bg--light}; + --input-bound-bg-hover: #{$input--bound--bg--light--hover}; + + --item-bg-hover: #{$item--bg--light--hover}; --text-primary: #{$text--primary--color--light}; --text-secondary: #{$text--secondary--color--light}; --text-secondary-hover: #{$text--secondary--hover--light}; + --text-tertiary: #{$text--tertiary--color--light}; + --icon-secondary: #{$icon--secondary--color--light}; --icon-secondary-hover: #{$icon--secondary--hover--light}; @@ -47,12 +53,18 @@ --input-bg: #{$input--bg--dark}; --input-bg-hover: #{$input--bg--dark--hover}; + --input-bound-bg: #{$input--bound--bg--dark}; + --input-bound-bg-hover: #{$input--bound--bg--dark--hover}; + + --item-bg-hover: #{$item--bg--dark--hover}; --text-primary: #{$text--primary--color--dark}; --text-secondary: #{$text--secondary--color--dark}; --text-secondary-hover: #{$text--secondary--hover--dark}; + --text-tertiary: #{$text--tertiary--color--dark}; + --icon-secondary: #{$icon--secondary--color--dark}; --icon-secondary-hover: #{$icon--secondary--hover--dark}; diff --git a/styles/variables.scss b/styles/variables.scss index b9660834..b790a26d 100644 --- a/styles/variables.scss +++ b/styles/variables.scss @@ -11,6 +11,16 @@ $medium-screen: 800px; // Sizing $unit: 8px; +$unit-fourth: calc($unit / 4); +$unit-half: calc($unit / 2); +$unit-2x: $unit * 2; +$unit-4x: $unit * 4; +$unit-6x: $unit * 6; +$unit-8x: $unit * 8; +$unit-10x: $unit * 10; +$unit-12x: $unit * 12; +$unit-20x: $unit * 20; + // Colors $grey-00: #111; $grey-10: #191919; @@ -35,12 +45,20 @@ $page--hover--dark: $grey-20; $page--element--bg--light: $grey-70; $page--element--bg--dark: $grey-30; -$input--bg--light: $grey-90; -$input--bg--light--hover: $grey-80; -$input--bg--dark: $grey-10; +$input--bg--light: $grey-100; +$input--bg--light--hover: $grey-95; +$input--bg--dark: $grey-20; $input--bg--dark--hover: $grey-15; -$grid--rep--hover--light: white; +$input--bound--bg--light: $grey-90; +$input--bound--bg--light--hover: $grey-80; +$input--bound--bg--dark: $grey-10; +$input--bound--bg--dark--hover: $grey-15; + +$item--bg--light--hover: $grey-90; +$item--bg--dark--hover: $grey-10; + +$grid--rep--hover--light: $grey-100; $grid--rep--hover--dark: $grey-00; $grid--border--color--light: $grey-80; @@ -62,6 +80,9 @@ $icon--secondary--color--dark: $grey-40; $icon--secondary--hover--light: $grey-40; $icon--secondary--hover--dark: $grey-70; +$text--tertiary--color--light: $grey-60; +$text--tertiary--color--dark: $grey-60; + $blue: #275dc5; $red: #ff6161; $error: #d13a3a; @@ -128,6 +149,10 @@ $font-xxlarge: 28px; $scale-wide: scale(1.05, 1.05); $scale-tall: scale(1.012, 1.012); +// Border radius +$input-corner: $unit; +$item-corner: $unit-half; + // Shadows $hover-stroke: 1px solid rgba(0, 0, 0, 0.1); $hover-shadow: rgba(0, 0, 0, 0.08) 0px 0px 14px;