switch from react-spectrum to react-aria custom components

This commit is contained in:
Chuck Dries 2022-11-17 20:50:54 -08:00
parent d28ee00866
commit a9dd2e25d5
No known key found for this signature in database
GPG Key ID: A00B7AEAE1DC5BE6
79 changed files with 386 additions and 1313 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,12 @@
import * as React from "react"; // import * as React from "react";
import { lightTheme, Provider, SSRProvider } from "@adobe/react-spectrum";
import "./src/styles/global.css"; import "./src/styles/global.css";
declare global {
interface Window {
plausible: any;
}
}
const env = const env =
process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || "development"; process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || "development";
export const onRouteUpdate = function () { export const onRouteUpdate = function () {
@ -22,21 +27,21 @@ export const onRouteUpdate = function () {
// p: MyParagraph, // p: MyParagraph,
// }; // };
export const wrapRootElement = ({ element }) => ( // export const wrapRootElement = ({ element }) => (
<SSRProvider> // <SSRProvider>
<Provider // <Provider
UNSAFE_className="overflow-x-hidden" // UNSAFE_className="overflow-x-hidden"
UNSAFE_style={{ // UNSAFE_style={{
background: "unset", // background: "unset",
color: "unset", // color: "unset",
}} // }}
colorScheme="light" // colorScheme="light"
// scale="medium" // // scale="medium"
theme={lightTheme} // theme={{light: {}, ...lightTheme}}
> // >
{element} // {element}
</Provider> // </Provider>
</SSRProvider> // </SSRProvider>
); // );
// {/* // <MDXProvider components={components}>{element}</MDXProvider> */} // {/* // <MDXProvider components={components}>{element}</MDXProvider> */}

View File

@ -1,11 +1,11 @@
import * as React from "react"; import * as React from "react";
import { lightTheme, Provider } from "@adobe/react-spectrum"; // import { lightTheme, Provider } from "@adobe/react-spectrum";
import "./src/styles/global.css"; import "./src/styles/global.css";
import { SSRProvider } from "@react-aria/ssr"; import { SSRProvider } from "@react-aria/ssr";
export const wrapRootElement = ({ element }) => ( export const wrapRootElement = ({ element }) => (
<SSRProvider> <SSRProvider>
<Provider {/* <Provider
UNSAFE_style={{ UNSAFE_style={{
background: "unset", background: "unset",
color: "unset", color: "unset",
@ -14,8 +14,8 @@ export const wrapRootElement = ({ element }) => (
colorScheme="light" colorScheme="light"
// scale="medium" // scale="medium"
theme={lightTheme} theme={lightTheme}
> > */}
{element} {element}
</Provider> {/* </Provider> */}
</SSRProvider> </SSRProvider>
); );

View File

@ -22,8 +22,8 @@
"pretty": "prettier --write ." "pretty": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@adobe/react-spectrum": "^3.19.0", "@react-spectrum/provider": "^3.6.0",
"@spectrum-icons/workflow": "^4.0.0", "@spectrum-icons/workflow": "^4.0.4",
"autoprefixer": "^10.2.6", "autoprefixer": "^10.2.6",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"chroma-js": "^2.1.2", "chroma-js": "^2.1.2",
@ -47,10 +47,12 @@
"postcss-nested": "^6.0.0", "postcss-nested": "^6.0.0",
"ramda": "^0.27.1", "ramda": "^0.27.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-aria": "^3.21.0",
"react-cool-dimensions": "^2.0.7", "react-cool-dimensions": "^2.0.7",
"react-div-100vh": "^0.7.0", "react-div-100vh": "^0.7.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-stately": "^3.19.0",
"react-tiny-popover": "^7.2.0", "react-tiny-popover": "^7.2.0",
"sass": "^1.34.0", "sass": "^1.34.0",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",

View File

@ -39,6 +39,11 @@ const logKeyShortcut = (keyCode) => {
} }
}; };
const IconStyle = {
width: '24px',
margin: '0 4px'
}
const ArrowLinkClasses = `hover:underline text-vibrant-light hover:text-muted-light const ArrowLinkClasses = `hover:underline text-vibrant-light hover:text-muted-light
lg:px-4 self-stretch flex items-center hover:bg-black/50 max-h-screen sticky top-0 lg:px-4 self-stretch flex items-center hover:bg-black/50 max-h-screen sticky top-0
`; `;
@ -260,37 +265,37 @@ const GalleryImage = ({ data, location: { state } }) => {
<div className="flex flex-col items-end"> <div className="flex flex-col items-end">
<MetadataItem <MetadataItem
data={dateTaken.toLocaleDateString()} data={dateTaken.toLocaleDateString()}
icon={<Calendar />} icon={<Calendar UNSAFE_style={IconStyle} />}
title="date taken" title="date taken"
/> />
<div className="sm:flex justify-end gap-2 border border-vibrant-light pl-2 rounded"> <div className="sm:flex justify-end gap-2 border border-vibrant-light pl-2 rounded">
<MetadataItem <MetadataItem
data={shutterSpeed} data={shutterSpeed}
icon={<Stopwatch />} icon={<Stopwatch UNSAFE_style={IconStyle} />}
title="shutter speed" title="shutter speed"
/> />
{meta.FNumber && ( {meta.FNumber && (
<MetadataItem <MetadataItem
data={`f/${meta.FNumber}`} data={`f/${meta.FNumber}`}
icon={<Exposure />} icon={<Exposure UNSAFE_style={IconStyle} />}
title="aperture" title="aperture"
/> />
)} )}
<MetadataItem <MetadataItem
data={meta.ISO} data={meta.ISO}
icon={<Filmroll />} icon={<Filmroll UNSAFE_style={IconStyle} />}
title="ISO" title="ISO"
/> />
</div> </div>
<MetadataItem <MetadataItem
data={locationString} data={locationString}
icon={<Location />} icon={<Location UNSAFE_style={IconStyle} />}
title="location" title="location"
/> />
{(meta.Make || meta.Model) && ( {(meta.Make || meta.Model) && (
<MetadataItem <MetadataItem
data={[meta.Make, meta.Model].join(" ")} data={[meta.Make, meta.Model].join(" ")}
icon={<Camera />} icon={<Camera UNSAFE_style={IconStyle} />}
title="camera" title="camera"
/> />
)} )}
@ -302,7 +307,7 @@ const GalleryImage = ({ data, location: { state } }) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(" @")} .join(" @")}
icon={<Circle />} icon={<Circle UNSAFE_style={IconStyle} />}
title="lens" title="lens"
/> />
)} )}

View File

@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import classNames from "classnames"; import classNames from "classnames";
import Checkmark from "@spectrum-icons/workflow/Checkmark";
interface KeywordsPickerProps { interface KeywordsPickerProps {
keywords: string[]; keywords: string[];
@ -9,7 +10,7 @@ interface KeywordsPickerProps {
const KeywordsPicker = ({ keywords, value, onChange }: KeywordsPickerProps) => { const KeywordsPicker = ({ keywords, value, onChange }: KeywordsPickerProps) => {
return ( return (
<div className="mx-2 mt-2"> <div className="mx-2 mt-2">
<span className="text-xs text-[var(--spectrum-fieldlabel-text-color,var(--spectrum-alias-label-text-color))]"> <span className="text-xs text-black">
Collections Collections
</span> </span>
<ul className="flex gap-1 flex-wrap mt-1 mb-2"> <ul className="flex gap-1 flex-wrap mt-1 mb-2">
@ -19,20 +20,24 @@ const KeywordsPicker = ({ keywords, value, onChange }: KeywordsPickerProps) => {
<li key={keyword}> <li key={keyword}>
<button <button
className={classNames( className={classNames(
"transition",
`py-[5px] px-3 rounded-full`, `py-[5px] px-3 rounded-full`,
`text-[var(--spectrum-fieldbutton-text-color,var(--spectrum-alias-text-color))] `text-black border border-black`,
border border-[var(--spectrum-fieldbutton-border-color,var(--spectrum-alias-border-color))]`,
selected selected
? "bg-green-500 hover:bg-green-300" ? "bg-transparentblack font-bold"
: `bg-[var(--spectrum-fieldbutton-background-color,var(--spectrum-global-color-gray-75))] : `bg-white
hover:bg-[var(--spectrum-fieldbutton-background-color-down,var(--spectrum-global-color-gray-200))]` hover:bg-transparentblack`
)} )}
onClick={() => (selected ? onChange(null) : onChange(keyword))} onClick={() => (selected ? onChange(null) : onChange(keyword))}
type="button" type="button"
> >
{keyword} {keyword}{" "}
{selected && (
<Checkmark
UNSAFE_className="mx-1"
UNSAFE_style={{ width: "15px" }}
aria-hidden="true"
/>
)}
</button> </button>
</li> </li>
); );

105
src/components/ListBox.tsx Normal file
View File

@ -0,0 +1,105 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import * as React from "react";
import type { AriaListBoxOptions } from "@react-aria/listbox";
import type { ListState } from "react-stately";
import type { Node } from "@react-types/shared";
import { useListBox, useListBoxSection, useOption } from "react-aria";
// import { CheckIcon } from "@heroicons/react/solid";
import Checkmark from '@spectrum-icons/workflow/Checkmark'
interface ListBoxProps extends AriaListBoxOptions<unknown> {
listBoxRef?: React.RefObject<HTMLUListElement>;
state: ListState<unknown>;
}
interface SectionProps {
section: Node<unknown>;
state: ListState<unknown>;
}
interface OptionProps {
item: Node<unknown>;
state: ListState<unknown>;
}
export function ListBox(props: ListBoxProps) {
let ref = React.useRef<HTMLUListElement>(null);
let { listBoxRef = ref, state } = props;
let { listBoxProps } = useListBox(props, state, listBoxRef);
return (
<ul
{...listBoxProps}
className="w-full max-h-72 overflow-auto outline-none"
ref={listBoxRef}
>
{[...state.collection].map((item) =>
item.type === "section" ? (
<ListBoxSection key={item.key} section={item} state={state} />
) : (
<Option item={item} key={item.key} state={state} />
)
)}
</ul>
);
}
function ListBoxSection({ section, state }: SectionProps) {
let { itemProps, headingProps, groupProps } = useListBoxSection({
heading: section.rendered,
"aria-label": section["aria-label"]
});
return (
<>
<li {...itemProps} className="pt-2">
{section.rendered && (
<span
{...headingProps}
className="text-xs font-bold uppercase text-gray-500 mx-3"
>
{section.rendered}
</span>
)}
<ul {...groupProps}>
{[...section.childNodes].map((node) => (
<Option item={node} key={node.key} state={state} />
))}
</ul>
</li>
</>
);
}
function Option({ item, state }: OptionProps) {
let ref = React.useRef<HTMLLIElement>(null);
let { optionProps, isDisabled, isSelected, isFocused } = useOption(
{
key: item.key
},
state,
ref
);
let text = "text-black";
if (isFocused || isSelected) {
text = "text-black";
} else if (isDisabled) {
text = "text-gray-200";
}
return (
<li
{...optionProps}
className={`p-3 outline-none cursor-default flex items-center justify-between ${text} ${
isFocused ? "bg-transparentblack" : ""
} ${isSelected ? "font-bold" : ""}`}
ref={ref}
>
{item.rendered}
{isSelected && (
<Checkmark UNSAFE_className="mx-1" UNSAFE_style={{ width: '15px' }} aria-hidden="true" />
)}
</li>
);
}

View File

@ -138,7 +138,7 @@ const MasonryGallery = ({
return ( return (
<Link <Link
className={classNames( className={classNames(
"border-4 overflow-hidden", "border-4 border-white overflow-hidden",
debugHue && "border-8" debugHue && "border-8"
)} )}
id={image.base} id={image.base}
@ -159,7 +159,7 @@ const MasonryGallery = ({
${image.fields?.imageMeta?.dominantHue?.[1] ?? 0 * 100}%, ${image.fields?.imageMeta?.dominantHue?.[1] ?? 0 * 100}%,
${image.fields?.imageMeta?.dominantHue?.[2] ?? 0 * 100}% ${image.fields?.imageMeta?.dominantHue?.[2] ?? 0 * 100}%
)` )`
: "white", : "",
}} }}
to={`/photogallery/${image.base}`} to={`/photogallery/${image.base}`}
> >

View File

@ -0,0 +1,39 @@
import type { OverlayTriggerState } from "react-stately";
import type { AriaPopoverProps } from "@react-aria/overlays";
import * as React from "react";
import { usePopover, DismissButton, Overlay } from "@react-aria/overlays";
interface PopoverProps extends Omit<AriaPopoverProps, "popoverRef"> {
children: React.ReactNode;
state: OverlayTriggerState;
className?: string;
popoverRef?: React.RefObject<HTMLDivElement>;
}
export function Popover(props: PopoverProps) {
let ref = React.useRef<HTMLDivElement>(null);
let { popoverRef = ref, state, children, className, isNonModal } = props;
let { popoverProps, underlayProps } = usePopover(
{
...props,
popoverRef
},
state
);
return (
<Overlay>
{!isNonModal && <div {...underlayProps} className="fixed inset-0" />}
<div
{...popoverProps}
className={`z-10 shadow border border-black bg-white rounded mt-1 ${className}`}
ref={popoverRef}
>
{!isNonModal && <DismissButton onDismiss={state.close} />}
{children}
<DismissButton onDismiss={state.close} />
</div>
</Overlay>
);
}

89
src/components/Select.tsx Normal file
View File

@ -0,0 +1,89 @@
import * as React from "react";
import type { AriaSelectProps } from "@react-types/select";
import { useSelectState } from "react-stately";
import {
useSelect,
HiddenSelect,
useButton,
mergeProps,
useFocusRing
} from "react-aria";
// import { SelectorIcon } from "@heroicons/react/solid";
import ChevronDown from "@spectrum-icons/workflow/ChevronDown";
import { ListBox } from "./ListBox";
import { Popover } from "./Popover";
export { Item } from "react-stately";
export function Select<T extends object>(props: AriaSelectProps<T>) {
// Create state based on the incoming props
let state = useSelectState(props);
// Get props for child elements from useSelect
let ref = React.useRef(null);
let { labelProps, triggerProps, valueProps, menuProps } = useSelect(
props,
state,
ref
);
// Get props for the button based on the trigger props from useSelect
let { buttonProps } = useButton(triggerProps, ref);
let { focusProps, isFocusVisible } = useFocusRing();
return (
<div className="inline-flex flex-col relative">
<div
{...labelProps}
className="block text-sm text-left cursor-default mb-1"
>
{props.label}
</div>
<HiddenSelect
label={props.label}
name={props.name}
state={state}
triggerRef={ref}
/>
<button
{...mergeProps(buttonProps, focusProps)}
className={`py-[5px] px-3 w-[150px] flex flex-row items-center justify-between overflow-hidden cursor-default rounded border ${
isFocusVisible ? "border-green-500" : "border-black"
} ${state.isOpen ? "bg-gray-100" : "bg-white"}`}
ref={ref}
>
<span
{...valueProps}
// className={`text-md flex-auto ${
// state.selectedItem ? "text-gray-800" : "text-gray-500"
// }`}
>
{state.selectedItem
? state.selectedItem.rendered
: "Select an option"}
</span>
<ChevronDown
UNSAFE_className="mx-1"
UNSAFE_style={{
width: '15px'
}}
// UNSAFE_className={`w-[20px] h-5 ${
// isFocusVisible ? "text-pink-500" : "text-gray-500"
// }`}
/>
</button>
{state.isOpen && (
<Popover
className="w-[150px]"
placement="bottom start"
state={state}
triggerRef={ref}
>
<ListBox {...menuProps} state={state} />
</Popover>
)}
</div>
);
}

View File

@ -78,8 +78,8 @@ const IndexPage = ({
LightMuted: [0, 0, 0], LightMuted: [0, 0, 0],
Vibrant: [0, 0, 0], Vibrant: [0, 0, 0],
LightVibrant: [0, 0, 0], LightVibrant: [0, 0, 0],
DarkMuted: [255, 255, 255], DarkMuted: [238, 238, 238],
DarkVibrant: [255, 255, 255], DarkVibrant: [238, 238, 238],
})} })}
/> />
</Helmet> </Helmet>

View File

@ -2,12 +2,13 @@ import * as React from "react";
import * as R from "ramda"; import * as R from "ramda";
import { graphql, PageProps } from "gatsby"; import { graphql, PageProps } from "gatsby";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { Picker, Item } from "@adobe/react-spectrum"; // import { Picker, Item } from "@adobe/react-spectrum";
import MasonryGallery from "../components/MasonryGallery"; import MasonryGallery from "../components/MasonryGallery";
import KeywordsPicker from "../components/KeywordsPicker"; import KeywordsPicker from "../components/KeywordsPicker";
import { getGalleryPageUrl, getHelmetSafeBodyStyle } from "../utils"; import { getGalleryPageUrl, getHelmetSafeBodyStyle } from "../utils";
import Nav from "../components/Nav"; import Nav from "../components/Nav";
import { Item, Select } from "../components/Select";
const SORT_KEYS = { const SORT_KEYS = {
hue: ["fields", "imageMeta", "vibrantHue"], hue: ["fields", "imageMeta", "vibrantHue"],
@ -170,8 +171,8 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
LightMuted: [0, 0, 0], LightMuted: [0, 0, 0],
Vibrant: [0, 0, 0], Vibrant: [0, 0, 0],
LightVibrant: [0, 0, 0], LightVibrant: [0, 0, 0],
DarkMuted: [255, 255, 255], DarkMuted: [238, 238, 238],
DarkVibrant: [255, 255, 255], DarkVibrant: [238, 238, 238],
})} })}
/> />
</Helmet> </Helmet>
@ -205,7 +206,7 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
value={filterKeyword} value={filterKeyword}
/> />
<div className="m-2"> <div className="m-2">
<Picker <Select
label="Sort by..." label="Sort by..."
// @ts-ignore // @ts-ignore
onSelectionChange={setSortKey} onSelectionChange={setSortKey}
@ -214,7 +215,7 @@ const GalleryPage = ({ data }: PageProps<Queries.GalleryPageQueryQuery>) => {
<Item key="rating">Curated</Item> <Item key="rating">Curated</Item>
<Item key="date">Date</Item> <Item key="date">Date</Item>
<Item key="hue">Hue</Item> <Item key="hue">Hue</Item>
</Picker> </Select>
</div> </div>
</div> </div>
</div> </div>

View File

@ -45,8 +45,9 @@ module.exports = {
"huge-2": "max(7.8vw, 120px)", "huge-2": "max(7.8vw, 120px)",
}, },
colors: { colors: {
"white": "#EEEEEE",
buzzwordsPrimary: "#F6C54B", buzzwordsPrimary: "#F6C54B",
transparentblack: "rgba(0,0,0,0.3)", transparentblack: "rgba(0,0,0,0.24)",
vibrant: { vibrant: {
DEFAULT: ({ opacityVariable, opacityValue }) => { DEFAULT: ({ opacityVariable, opacityValue }) => {
if (opacityValue !== undefined) { if (opacityValue !== undefined) {

1347
yarn.lock

File diff suppressed because it is too large Load Diff