chore: add initial dropdown for new vs old

This commit is contained in:
Corbin Crutchley
2023-07-21 22:32:52 -05:00
parent 5b0615178f
commit 79678854dc
7 changed files with 396 additions and 134 deletions

29
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"license": "MPL-2.0",
"dependencies": {
"@astrojs/vercel": "^3.7.0",
"@floating-ui/react": "^0.24.3",
"@floating-ui/react": "^0.24.8",
"@tanstack/react-query": "^4.29.19",
"medium-zoom": "^1.0.8",
"preact": "^10.16.0"
@@ -66,7 +66,7 @@
"postcss-csso": "^6.0.1",
"preact-render-to-string": "^6.1.0",
"prettier": "^2.8.8",
"prettier-plugin-astro": "^0.10.0",
"prettier-plugin-astro": "^0.11.0",
"probe-image-size": "^7.2.3",
"rehype-raw": "^6.1.1",
"rehype-retext": "^3.0.2",
@@ -25141,13 +25141,13 @@
}
},
"node_modules/prettier-plugin-astro": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.10.0.tgz",
"integrity": "sha512-dPzop0gKZyVGpTDQmfy+e7FKXC9JT3mlpfYA2diOVz+Ui+QR1U4G/s+OesKl2Hib2JJOtAYJs/l+ovgT0ljlFA==",
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.11.0.tgz",
"integrity": "sha512-rl2hJ4Kty/aEfGjk3i4JS+bpng9MjgvwqLRNzeb9NqYhqKoWNwOR39cIJXFjU1vR3zYOPnwWNRMelKb0orunYA==",
"dev": true,
"dependencies": {
"@astrojs/compiler": "^1.5.0",
"prettier": "^2.8.8",
"@astrojs/compiler": "^1.5.5",
"prettier": "^3.0.0",
"sass-formatter": "^0.7.6"
},
"engines": {
@@ -25155,6 +25155,21 @@
"pnpm": ">=7.14.0"
}
},
"node_modules/prettier-plugin-astro/node_modules/prettier": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
"integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",

View File

@@ -90,7 +90,7 @@
"postcss-csso": "^6.0.1",
"preact-render-to-string": "^6.1.0",
"prettier": "^2.8.8",
"prettier-plugin-astro": "^0.10.0",
"prettier-plugin-astro": "^0.11.0",
"probe-image-size": "^7.2.3",
"rehype-raw": "^6.1.1",
"rehype-retext": "^3.0.2",
@@ -120,7 +120,7 @@
},
"dependencies": {
"@astrojs/vercel": "^3.7.0",
"@floating-ui/react": "^0.24.3",
"@floating-ui/react": "^0.24.8",
"@tanstack/react-query": "^4.29.19",
"medium-zoom": "^1.0.8",
"preact": "^10.16.0"

View File

@@ -1,7 +1,8 @@
import { JSXNode, PropsWithChildren } from "../types";
import { createElement, Ref } from "preact";
import { createElement, Ref, VNode } from "preact";
import { JSX } from "preact";
import { forwardRef } from "preact/compat";
import { useMemo } from "preact/hooks";
type AllowedTags = "a" | "button";
@@ -34,7 +35,7 @@ const ButtonWrapper = forwardRef(
}: ButtonProps<T>,
ref: Ref<T extends "a" ? HTMLAnchorElement : HTMLButtonElement>
) => {
const Wrapper = (props: any) => createElement(tag, props, props.children);
const Wrapper: any = tag;
return (
<Wrapper
@@ -81,7 +82,10 @@ export const LargeButton = forwardRef(
}
);
type IconOnlyButtonProps<T extends AllowedTags = "a"> = Omit<ButtonProps<T>, "leftIcon" | "rightIcon">;
type IconOnlyButtonProps<T extends AllowedTags = "a"> = Omit<
ButtonProps<T>,
"leftIcon" | "rightIcon"
>;
export const IconOnlyButton = forwardRef(
<T extends AllowedTags = "a">(

View File

@@ -0,0 +1,12 @@
@import "src/tokens/index";
.downSpan,
.downSpan svg {
height: 100%;
width: 100%;
}
.selectDropdown {
display: flex;
flex-direction: column;
}

View File

@@ -0,0 +1,187 @@
/**
* TODO: Migrate this to be controlled at some point. Right now, it's uncontrolled
*/
import {
autoUpdate,
flip,
useFloating,
useInteractions,
useListNavigation,
useTypeahead,
useClick,
useListItem,
useDismiss,
useRole,
FloatingFocusManager,
FloatingList,
} from "@floating-ui/react";
import { createContext } from "preact";
import { PropsWithChildren, useContext } from "preact/compat";
import { useCallback, useMemo, useRef, useState } from "preact/hooks";
import down from "src/icons/chevron_down.svg?raw";
import { Button } from "components/button/button";
import styles from "./select.module.scss";
interface SelectContextValue {
activeIndex: number | null;
selectedIndex: number | null;
getItemProps: ReturnType<typeof useInteractions>["getItemProps"];
handleSelect: (index: number | null) => void;
}
const SelectContext = createContext<SelectContextValue>(
{} as SelectContextValue
);
const rightIcon = (
<span
className={styles.downSpan}
dangerouslySetInnerHTML={{ __html: down }}
></span>
);
interface SelectProps {
initial?: {
selectedIndex: number;
selectedLabel: string;
};
class?: string;
className?: string;
onChangeVal: (val: string) => void;
}
export function Select({
children,
initial,
class: className = "",
className: classNameName = "",
onChangeVal,
}: PropsWithChildren<SelectProps>) {
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const [selectedIndex, setSelectedIndex] = useState<number | null>(
initial?.selectedIndex ?? null
);
const [selectedLabel, setSelectedLabel] = useState<string | null>(
initial?.selectedLabel ?? null
);
const { refs, floatingStyles, context } = useFloating({
placement: "bottom",
open: isOpen,
onOpenChange: setIsOpen,
whileElementsMounted: autoUpdate,
middleware: [flip()],
});
const elementsRef = useRef<Array<HTMLElement | null>>([]);
const labelsRef = useRef<Array<string | null>>([]);
const handleSelect = useCallback((index: number | null) => {
setSelectedIndex(index);
setIsOpen(false);
if (index !== null) {
const newLabel = labelsRef.current[index];
setSelectedLabel(newLabel);
onChangeVal(newLabel);
}
}, []);
function handleTypeaheadMatch(index: number | null) {
if (isOpen) {
setActiveIndex(index);
} else {
handleSelect(index);
}
}
const listNav = useListNavigation(context, {
listRef: elementsRef,
activeIndex,
selectedIndex,
onNavigate: setActiveIndex,
});
const typeahead = useTypeahead(context, {
listRef: labelsRef,
activeIndex,
selectedIndex,
onMatch: handleTypeaheadMatch,
});
const click = useClick(context);
const dismiss = useDismiss(context);
const role = useRole(context, { role: "listbox" });
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions(
[listNav, typeahead, click, dismiss, role]
);
const selectContext = useMemo(
() => ({
activeIndex,
selectedIndex,
getItemProps,
handleSelect,
}),
[activeIndex, selectedIndex, getItemProps, handleSelect]
);
return (
<>
<Button
class={`${className} ${classNameName}`}
tag="button"
type="button"
ref={refs.setReference}
{...getReferenceProps()}
rightIcon={rightIcon}
>
{selectedLabel ?? "Select..."}
</Button>
<SelectContext.Provider value={selectContext}>
{isOpen && (
<FloatingFocusManager context={context} modal={false}>
<div
ref={refs.setFloating}
style={floatingStyles}
class={styles.selectDropdown}
{...getFloatingProps()}
>
<FloatingList elementsRef={elementsRef} labelsRef={labelsRef}>
{children}
</FloatingList>
</div>
</FloatingFocusManager>
)}
</SelectContext.Provider>
</>
);
}
export function Option({ label }: { label: string }) {
const { activeIndex, selectedIndex, getItemProps, handleSelect } =
useContext(SelectContext);
const { ref, index } = useListItem({ label });
const isActive = activeIndex === index;
const isSelected = selectedIndex === index;
return (
<button
ref={ref}
role="option"
aria-selected={isActive && isSelected}
tabIndex={isActive ? 0 : -1}
style={{
background: isActive ? "cyan" : "",
fontWeight: isSelected ? "bold" : "",
}}
{...getItemProps({
onClick: () => handleSelect(index),
})}
>
{label}
</button>
);
}

View File

@@ -4,8 +4,11 @@
display: grid;
gap: var(--spc-4x);
grid-template-rows: repeat(5, auto);
grid-template-columns: repeat(2, auto);
@include from($tabletSmall) {
grid-template-rows: repeat(3, auto);
grid-template-rows: repeat(4, auto);
grid-template-columns: repeat(2, auto);
}
@@ -21,8 +24,12 @@
width: 100%;
background: var(--background_disabled);
grid-column: 1 / 3;
grid-row: 2;
@include from($tabletSmall) {
grid-column: 1 / 3;
grid-row: unset;
}
@include from($tabletLarge) {
@@ -32,12 +39,32 @@
}
}
.orderSelectContainer {
grid-column: 2;
grid-row: 3;
@include from($tabletSmall) {
display: none;
}
}
.tabletSmallTopBarDivider {
height: 1px;
width: 100%;
background: var(--background_disabled);
grid-column: 1 / 3;
@include from($tabletLarge) {
display: none;
}
}
.topBarButtonsContentToDisplay {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: var(--spc-2x);
justify-self: start;
align-items: center;
@include from($tabletSmall) {
grid-column: 1;
@@ -47,6 +74,7 @@
@include from($tabletLarge) {
grid-row: unset;
grid-column: unset;
justify-content: center;
}
}
@@ -96,6 +124,8 @@
justify-content: center;
align-items: center;
grid-column: 1 / 3;
@include from($tabletSmall) {
grid-column: 1 / 3;
}

View File

@@ -3,6 +3,7 @@ import { SearchInput } from "components/input/input";
import { Button, IconOnlyButton } from "components/button/button";
import filter from "src/icons/filter.svg?raw";
import forward from "src/icons/arrow_right.svg?raw";
import { Option, Select } from "components/select/select";
interface SearchTopbarProps {
onSearch: (search: string) => void;
@@ -88,6 +89,18 @@ export const SearchTopbar = ({
Collections
</Button>
</div>
<div class={style.orderSelectContainer}>
<Select
initial={{
selectedIndex: 1,
selectedLabel: "Newest",
}}
onChangeVal={(val) => alert(val)}
>
<Option label={"Newest"} />
<Option label={"Oldest"} />
</Select>
</div>
<div className={style.topBarSmallTabletButtons}>
<div role="group" className={style.topBarSmallTabletButtonsToggle}>
<Button
@@ -114,6 +127,7 @@ export const SearchTopbar = ({
></span>
</IconOnlyButton>
</div>
<div class={style.tabletSmallTopBarDivider} />
</div>
);
};