From bef9c2b2865b796161063f16d3d4c174a229484b Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 24 Jan 2023 02:07:54 -0800 Subject: [PATCH] Add JobAccessoryPopover This opens, closes, and saves the value but it doesn't persist to the server yet. Not a responsive component yet. --- components/JobAccessoryItem/index.scss | 48 +++++++++ components/JobAccessoryItem/index.tsx | 34 ++++++ components/JobAccessoryPopover/index.scss | 10 ++ components/JobAccessoryPopover/index.tsx | 124 ++++++++++++++++++++++ components/JobImage/index.tsx | 59 ++++++++-- components/JobSection/index.tsx | 32 +++++- 6 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 components/JobAccessoryItem/index.scss create mode 100644 components/JobAccessoryItem/index.tsx create mode 100644 components/JobAccessoryPopover/index.scss create mode 100644 components/JobAccessoryPopover/index.tsx diff --git a/components/JobAccessoryItem/index.scss b/components/JobAccessoryItem/index.scss new file mode 100644 index 00000000..7b2b069c --- /dev/null +++ b/components/JobAccessoryItem/index.scss @@ -0,0 +1,48 @@ +.JobAccessoryItem { + background: none; + border-radius: $input-corner; + border: none; + display: flex; + flex-direction: column; + gap: $unit; + padding: $unit; + margin: 0; + width: 100%; + + &[data-state='checked'] { + background: var(--input-bg-hover); + + h4 { + color: var(--button-text-hover); + } + } + + &:hover { + cursor: pointer; + background: var(--input-bg-hover); + + img { + transform: scale(1.025); + } + + h4 { + color: var(--button-text-hover); + } + } + + h4 { + color: var(--button-text); + font-size: $font-small; + text-align: center; + width: 100%; + } + + img { + border-radius: $item-corner; + width: 100%; + height: auto; + position: relative; + transition: $duration-zoom all ease-in-out; + z-index: 2; + } +} diff --git a/components/JobAccessoryItem/index.tsx b/components/JobAccessoryItem/index.tsx new file mode 100644 index 00000000..c547a900 --- /dev/null +++ b/components/JobAccessoryItem/index.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { useRouter } from 'next/router' + +import * as RadioGroup from '@radix-ui/react-radio-group' + +import './index.scss' + +interface Props { + accessory: JobAccessory + selected: boolean +} + +const JobAccessoryItem = ({ accessory, selected }: Props) => { + // Localization + const router = useRouter() + const locale = + router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en' + + return ( + + {accessory.name[locale]} +

{accessory.name[locale]}

+
+ ) +} + +export default JobAccessoryItem diff --git a/components/JobAccessoryPopover/index.scss b/components/JobAccessoryPopover/index.scss new file mode 100644 index 00000000..4d8afa01 --- /dev/null +++ b/components/JobAccessoryPopover/index.scss @@ -0,0 +1,10 @@ +.JobAccessory.Popover { + padding: $unit-2x; + width: 504px; + + .Accessories { + display: grid; + gap: $unit; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + } +} diff --git a/components/JobAccessoryPopover/index.tsx b/components/JobAccessoryPopover/index.tsx new file mode 100644 index 00000000..1ddf30e0 --- /dev/null +++ b/components/JobAccessoryPopover/index.tsx @@ -0,0 +1,124 @@ +import React, { PropsWithChildren, useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import { useTranslation } from 'next-i18next' + +import * as RadioGroup from '@radix-ui/react-radio-group' + +import Button from '~components/Button' +import { + Popover, + PopoverTrigger, + PopoverContent, +} from '~components/PopoverContent' +import JobAccessoryItem from '~components/JobAccessoryItem' + +import './index.scss' + +interface Props { + buttonref: React.RefObject + currentAccessory?: JobAccessory + accessories: JobAccessory[] + editable: boolean + open: boolean + job: Job + onAccessorySelected: (value: string) => void + onOpenChange: (open: boolean) => void +} + +const JobAccessoryPopover = ({ + buttonref, + currentAccessory, + accessories, + editable, + open: modalOpen, + children, + job, + onAccessorySelected, + onOpenChange, +}: PropsWithChildren) => { + // Localization + const { t } = useTranslation('common') + + const router = useRouter() + const locale = + router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en' + + // Component state + const [open, setOpen] = useState(false) + + // Hooks + useEffect(() => { + setOpen(modalOpen) + }, [modalOpen]) + + // Event handlers + function handleAccessorySelected() { + onAccessorySelected + closePopover() + } + + function handlePointerDownOutside( + event: CustomEvent<{ originalEvent: PointerEvent }> + ) { + const target = event.detail.originalEvent.target as Element + if ( + target && + buttonref.current && + target.closest('.JobAccessory.Button') !== buttonref.current + ) { + onOpenChange(false) + } + } + + function closePopover() { + onOpenChange(false) + } + const radioGroup = ( + + {accessories.map((accessory) => ( + + ))} + + ) + + const readOnly = currentAccessory ? ( +
+ {currentAccessory.name[locale]} +

{currentAccessory.name[locale]}

+
+ ) : ( +

+ No shield selected + {/* {t('no_accessory', { job: job.name.en.replace(' ', '-').toLowerCase() })} */} +

+ ) + + return ( + + {children} + + {editable ? radioGroup : readOnly} + + + ) +} + +export default JobAccessoryPopover diff --git a/components/JobImage/index.tsx b/components/JobImage/index.tsx index d7000292..513693a8 100644 --- a/components/JobImage/index.tsx +++ b/components/JobImage/index.tsx @@ -2,26 +2,42 @@ import React, { useEffect, useState } from 'react' import { useRouter } from 'next/router' import { useTranslation } from 'next-i18next' +import { ACCESSORY_JOB_IDS } from '~utils/jobsWithAccessories' + import Button from '~components/Button' +import JobAccessoryPopover from '~components/JobAccessoryPopover' + import ShieldIcon from '~public/icons/Shield.svg' import './index.scss' interface Props { job?: Job + currentAccessory?: JobAccessory + accessories?: JobAccessory[] + editable: boolean user?: User - onAccessoryButtonClicked: () => void + onAccessorySelected: (value: string) => void } -const ACCESSORY_JOB_IDS = ['683ffee8-4ea2-432d-bc30-4865020ac9f4'] - -const JobImage = ({ job, user, onAccessoryButtonClicked }: Props) => { +const JobImage = ({ + job, + currentAccessory, + editable, + accessories, + user, + onAccessorySelected, +}: Props) => { // Localization - const { t } = useTranslation('common') - const router = useRouter() const locale = router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en' + // Component state + const [open, setOpen] = useState(false) + + // Refs + const buttonRef = React.createRef() + // Static variables const imageUrl = () => { let source = '' @@ -38,20 +54,47 @@ const JobImage = ({ job, user, onAccessoryButtonClicked }: Props) => { const hasAccessory = job && ACCESSORY_JOB_IDS.includes(job.id) const image = {job?.name[locale]} + function handleAccessoryButtonClicked() { + setOpen(!open) + } + + function handlePopoverOpenChanged(open: boolean) { + setOpen(open) + } + // Elements const accessoryButton = () => { return (