Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: migrate dropdowns from reach-ui #2315

Merged
merged 5 commits into from Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/desktop/app/javascripts/Renderer/Renderer.ts
Expand Up @@ -118,7 +118,7 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
/* Use custom title bar. Take the sn-titlebar-height off of
the app content height so its not overflowing */
sheet.insertRule(
'body, [data-reach-dialog-overlay], [data-mobile-popover] { padding-top: var(--sn-desktop-titlebar-height) !important; }',
'body, [data-dialog], [data-mobile-popover] { padding-top: var(--sn-desktop-titlebar-height) !important; }',
sheet.cssRules.length,
)
sheet.insertRule(
Expand Down
3 changes: 0 additions & 3 deletions packages/web/package.json
Expand Up @@ -31,7 +31,6 @@
"@lexical/react": "0.9.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@reach/disclosure": "^0.18.0",
"@reach/listbox": "^0.18.0",
"@simplewebauthn/browser": "^7.1.0",
"@standardnotes/authenticator": "^2.3.9",
"@standardnotes/autobiography-theme": "^1.2.7",
Expand Down Expand Up @@ -61,7 +60,6 @@
"@types/jest": "^29.2.4",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/styled-components": "^5.1.26",
"@types/wicg-file-system-access": "^2020.9.5",
"@zip.js/zip.js": "^2.6.60",
"autoprefixer": "^10.4.13",
Expand Down Expand Up @@ -95,7 +93,6 @@
"react-dom": "^18.2.0",
"react-refresh": "^0.14.0",
"sass-loader": "*",
"styled-components": "^5.3.8",
"svg-jest": "^1.0.1",
"tailwindcss": "^3.2.7",
"ts-jest": "^29.0.3",
Expand Down
Expand Up @@ -155,7 +155,6 @@ const PhotoCaptureModal = ({ filesController, close }: Props) => {
<label className="text-sm font-medium text-neutral">
Device:
<Dropdown
id={'photo-capture-device-dropdown'}
label={'Photo Capture Device'}
items={devicesAsDropdownItems}
value={recorder.selectedDevice.deviceId}
Expand All @@ -164,7 +163,6 @@ const PhotoCaptureModal = ({ filesController, close }: Props) => {
}}
classNameOverride={{
wrapper: 'mt-1',
popover: 'z-modal',
}}
/>
</label>
Expand Down
Expand Up @@ -149,12 +149,8 @@ const NewNotePreferences: FunctionComponent<Props> = ({
<div className="mt-3 text-mobile-menu-item md:text-menu-item">Note Type</div>
<div className="mt-2">
<Dropdown
classNameOverride={{
popover: 'z-modal',
}}
disabled={disabled}
fullWidth={true}
id="def-editor-dropdown"
label="Select the default note type"
items={editorItems}
value={defaultEditorIdentifier}
Expand All @@ -166,12 +162,8 @@ const NewNotePreferences: FunctionComponent<Props> = ({
<div className="mt-3 text-mobile-menu-item md:text-menu-item">Title Format</div>
<div className="mt-2">
<Dropdown
classNameOverride={{
popover: 'z-modal',
}}
disabled={disabled}
fullWidth={true}
id="def-new-note-title-format"
label="Select the default note type"
items={NoteTitleFormatOptions}
value={newNoteTitleFormat}
Expand Down
158 changes: 73 additions & 85 deletions packages/web/src/javascripts/Components/Dropdown/Dropdown.tsx
@@ -1,15 +1,18 @@
import { ListboxArrow, ListboxInput, ListboxList, ListboxPopover } from '@reach/listbox'
import '@reach/listbox/styles.css'
import { FunctionComponent } from 'react'
import { useEffect } from 'react'
import Icon from '@/Components/Icon/Icon'
import { DropdownItem } from './DropdownItem'
import StyledListboxButton from './StyledListboxButton'
import StyledListboxOption from './StyledListboxOption'
import { classNames } from '@standardnotes/snjs'
import { VisuallyHidden } from '@ariakit/react'
import {
Select,
SelectArrow,
SelectItem,
SelectLabel,
SelectPopover,
useSelectStore,
VisuallyHidden,
} from '@ariakit/react'

type DropdownProps = {
id: string
label: string
items: DropdownItem[]
value: string
Expand All @@ -21,92 +24,77 @@ type DropdownProps = {
popover?: string
}
fullWidth?: boolean
portal?: boolean
}

type ListboxButtonProps = DropdownItem & {
isExpanded: boolean
}

const CustomDropdownButton: FunctionComponent<ListboxButtonProps> = ({
label,
isExpanded,
icon,
iconClassName = '',
}) => (
<>
<div className="flex items-center">
{icon ? (
<div className="mr-2 flex">
<Icon type={icon} className={iconClassName} size="small" />
</div>
) : null}
<div className="text-base lg:text-sm">{label}</div>
</div>
<ListboxArrow className={`flex ${isExpanded ? 'rotate-180' : ''}`}>
<Icon type="menu-arrow-down" className="text-passive-1" size="small" />
</ListboxArrow>
</>
)
const Dropdown = ({ label, value, onChange, items, disabled, fullWidth, classNameOverride = {} }: DropdownProps) => {
const select = useSelectStore({
defaultValue: value,
})

const Dropdown: FunctionComponent<DropdownProps> = ({
id,
label,
items,
value,
onChange,
disabled,
classNameOverride = {},
fullWidth,
portal = true,
}) => {
const labelId = `${id}-label`
const selectedValue = select.useState('value')
const isExpanded = select.useState('open')

const handleChange = (value: string) => {
const selectedItem = items.find((item) => item.value === value) as DropdownItem
const currentItem = items.find((item) => item.value === selectedValue)

onChange(value, selectedItem)
}
useEffect(() => {
return select.subscribe(
(state) => {
if (state.value !== value) {
onChange(state.value, items.find((item) => item.value === state.value) as DropdownItem)
}
},
['value'],
)
}, [items, onChange, select, value])

return (
<div className={classNameOverride.wrapper}>
<VisuallyHidden id={labelId}>{label}</VisuallyHidden>
<ListboxInput value={value} onChange={handleChange} aria-labelledby={labelId} disabled={disabled}>
<StyledListboxButton
className={classNames('w-full', !fullWidth && 'md:w-fit', classNameOverride.button)}
children={({ value, label, isExpanded }) => {
const current = items.find((item) => item.value === value)
const icon = current ? current?.icon : null
const iconClassName = current ? current?.iconClassName : null
return CustomDropdownButton({
value: value ? value : label.toLowerCase(),
label,
isExpanded,
...(icon ? { icon } : null),
...(iconClassName ? { iconClassName } : null),
})
}}
/>
<ListboxPopover
portal={portal}
className={classNames('sn-dropdown sn-dropdown-popover', classNameOverride.popover)}
>
<div className="sn-component">
<ListboxList>
{items.map((item) => (
<StyledListboxOption key={item.value} value={item.value} label={item.label} disabled={item.disabled}>
{item.icon ? (
<div className="mr-3 flex">
<Icon type={item.icon} className={item.iconClassName ?? ''} size="small" />
</div>
) : null}
<div className="text-base lg:text-sm">{item.label}</div>
</StyledListboxOption>
))}
</ListboxList>
</div>
</ListboxPopover>
</ListboxInput>
<VisuallyHidden>
<SelectLabel store={select}>{label}</SelectLabel>
</VisuallyHidden>
<Select
className={classNames(
'flex w-full min-w-55 items-center justify-between rounded border border-border bg-default py-1.5 px-3.5 text-sm text-foreground',
disabled && 'opacity-50',
classNameOverride.button,
!fullWidth && 'md:w-fit',
)}
store={select}
disabled={disabled}
>
<div className="flex items-center">
{currentItem?.icon ? (
<div className="mr-2 flex">
<Icon type={currentItem.icon} className={currentItem.iconClassName ?? ''} size="small" />
</div>
) : null}
<div className="text-base lg:text-sm">{currentItem?.label}</div>
</div>
<SelectArrow className={classNames('text-passive-1', isExpanded && 'rotate-180')} />
</Select>
<SelectPopover
store={select}
className={classNames(
'max-h-[var(--popover-available-height)] w-[var(--popover-anchor-width)] overflow-y-auto rounded border border-border bg-default py-1',
classNameOverride.popover,
)}
>
{items.map((item) => (
<SelectItem
className="flex cursor-pointer items-center bg-transparent py-1.5 px-3 text-sm text-text hover:bg-contrast hover:text-foreground [&[data-active-item]]:bg-info [&[data-active-item]]:text-info-contrast"
key={item.value}
value={item.value}
disabled={item.disabled}
>
{item.icon ? (
<div className="mr-3 flex">
<Icon type={item.icon} className={item.iconClassName ?? ''} size="small" />
</div>
) : null}
<div className="text-base lg:text-sm">{item.label}</div>
</SelectItem>
))}
</SelectPopover>
</div>
)
}
Expand Down

This file was deleted.

This file was deleted.

13 changes: 1 addition & 12 deletions packages/web/src/javascripts/Components/Icon/IconPicker.tsx
Expand Up @@ -14,19 +14,10 @@ type Props = {
platform: Platform
useIconGrid?: boolean
iconGridClassName?: string
portalDropdown?: boolean
className?: string
}

const IconPicker = ({
selectedValue,
onIconChange,
platform,
className,
useIconGrid,
portalDropdown,
iconGridClassName,
}: Props) => {
const IconPicker = ({ selectedValue, onIconChange, platform, className, useIconGrid, iconGridClassName }: Props) => {
const iconKeys = useMemo(() => Object.keys(IconNameToSvgMapping), [])

const iconOptions = useMemo(
Expand Down Expand Up @@ -127,12 +118,10 @@ const IconPicker = ({
) : (
<Dropdown
fullWidth={true}
id="change-tag-icon-dropdown"
label="Change the icon for a tag"
items={iconOptions}
value={selectedValue as string}
onChange={handleIconChange}
portal={portalDropdown}
/>
))}
{currentType === 'emoji' && (
Expand Down
Expand Up @@ -47,7 +47,6 @@ const SuperExportModal = ({ exportNotes, close }: Props) => {
We detected your selection includes Super notes. How do you want to export them?
</div>
<Dropdown
id="export-format-dropdown"
label="Super notes export format"
items={[
{ label: 'Keep as Super', value: 'json' },
Expand All @@ -61,7 +60,6 @@ const SuperExportModal = ({ exportNotes, close }: Props) => {
value as PrefValue[PrefKey.SuperNoteExportFormat],
)
}}
portal={false}
/>
</div>
<div className="text-passive-0">
Expand Down
Expand Up @@ -23,8 +23,8 @@ export const usePopoverCloseOnClickOutside = ({
const isAnchorElement = anchorElement ? anchorElement === event.target || anchorElement.contains(target) : false
const closestPopoverId = target.closest('[data-popover]')?.getAttribute('data-popover')
const isDescendantOfChildPopover = closestPopoverId && childPopovers.has(closestPopoverId)
const isPopoverInModal = popoverElement?.closest('[aria-modal="true"]')
const isDescendantOfModal = isPopoverInModal ? false : !!target.closest('[aria-modal="true"]')
const isPopoverInModal = popoverElement?.closest('[data-dialog]')
const isDescendantOfModal = isPopoverInModal ? false : !!target.closest('[data-dialog]')

if (!isDescendantOfMenu && !isAnchorElement && !isDescendantOfChildPopover && !isDescendantOfModal) {
if (!disabled) {
Expand Down