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

View File

@ -1,11 +1,11 @@
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 { SSRProvider } from "@react-aria/ssr";
export const wrapRootElement = ({ element }) => (
<SSRProvider>
<Provider
{/* <Provider
UNSAFE_style={{
background: "unset",
color: "unset",
@ -14,8 +14,8 @@ export const wrapRootElement = ({ element }) => (
colorScheme="light"
// scale="medium"
theme={lightTheme}
>
> */}
{element}
</Provider>
{/* </Provider> */}
</SSRProvider>
);

View File

@ -22,8 +22,8 @@
"pretty": "prettier --write ."
},
"dependencies": {
"@adobe/react-spectrum": "^3.19.0",
"@spectrum-icons/workflow": "^4.0.0",
"@react-spectrum/provider": "^3.6.0",
"@spectrum-icons/workflow": "^4.0.4",
"autoprefixer": "^10.2.6",
"chalk": "^4.1.1",
"chroma-js": "^2.1.2",
@ -47,10 +47,12 @@
"postcss-nested": "^6.0.0",
"ramda": "^0.27.1",
"react": "^18.2.0",
"react-aria": "^3.21.0",
"react-cool-dimensions": "^2.0.7",
"react-div-100vh": "^0.7.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-stately": "^3.19.0",
"react-tiny-popover": "^7.2.0",
"sass": "^1.34.0",
"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
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">
<MetadataItem
data={dateTaken.toLocaleDateString()}
icon={<Calendar />}
icon={<Calendar UNSAFE_style={IconStyle} />}
title="date taken"
/>
<div className="sm:flex justify-end gap-2 border border-vibrant-light pl-2 rounded">
<MetadataItem
data={shutterSpeed}
icon={<Stopwatch />}
icon={<Stopwatch UNSAFE_style={IconStyle} />}
title="shutter speed"
/>
{meta.FNumber && (
<MetadataItem
data={`f/${meta.FNumber}`}
icon={<Exposure />}
icon={<Exposure UNSAFE_style={IconStyle} />}
title="aperture"
/>
)}
<MetadataItem
data={meta.ISO}
icon={<Filmroll />}
icon={<Filmroll UNSAFE_style={IconStyle} />}
title="ISO"
/>
</div>
<MetadataItem
data={locationString}
icon={<Location />}
icon={<Location UNSAFE_style={IconStyle} />}
title="location"
/>
{(meta.Make || meta.Model) && (
<MetadataItem
data={[meta.Make, meta.Model].join(" ")}
icon={<Camera />}
icon={<Camera UNSAFE_style={IconStyle} />}
title="camera"
/>
)}
@ -302,7 +307,7 @@ const GalleryImage = ({ data, location: { state } }) => {
]
.filter(Boolean)
.join(" @")}
icon={<Circle />}
icon={<Circle UNSAFE_style={IconStyle} />}
title="lens"
/>
)}

View File

@ -1,5 +1,6 @@
import * as React from "react";
import classNames from "classnames";
import Checkmark from "@spectrum-icons/workflow/Checkmark";
interface KeywordsPickerProps {
keywords: string[];
@ -9,7 +10,7 @@ interface KeywordsPickerProps {
const KeywordsPicker = ({ keywords, value, onChange }: KeywordsPickerProps) => {
return (
<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
</span>
<ul className="flex gap-1 flex-wrap mt-1 mb-2">
@ -19,20 +20,24 @@ const KeywordsPicker = ({ keywords, value, onChange }: KeywordsPickerProps) => {
<li key={keyword}>
<button
className={classNames(
"transition",
`py-[5px] px-3 rounded-full`,
`text-[var(--spectrum-fieldbutton-text-color,var(--spectrum-alias-text-color))]
border border-[var(--spectrum-fieldbutton-border-color,var(--spectrum-alias-border-color))]`,
`text-black border border-black`,
selected
? "bg-green-500 hover:bg-green-300"
: `bg-[var(--spectrum-fieldbutton-background-color,var(--spectrum-global-color-gray-75))]
hover:bg-[var(--spectrum-fieldbutton-background-color-down,var(--spectrum-global-color-gray-200))]`
? "bg-transparentblack font-bold"
: `bg-white
hover:bg-transparentblack`
)}
onClick={() => (selected ? onChange(null) : onChange(keyword))}
type="button"
>
{keyword}
{keyword}{" "}
{selected && (
<Checkmark
UNSAFE_className="mx-1"
UNSAFE_style={{ width: "15px" }}
aria-hidden="true"
/>
)}
</button>
</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 (
<Link
className={classNames(
"border-4 overflow-hidden",
"border-4 border-white overflow-hidden",
debugHue && "border-8"
)}
id={image.base}
@ -159,7 +159,7 @@ const MasonryGallery = ({
${image.fields?.imageMeta?.dominantHue?.[1] ?? 0 * 100}%,
${image.fields?.imageMeta?.dominantHue?.[2] ?? 0 * 100}%
)`
: "white",
: "",
}}
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],
Vibrant: [0, 0, 0],
LightVibrant: [0, 0, 0],
DarkMuted: [255, 255, 255],
DarkVibrant: [255, 255, 255],
DarkMuted: [238, 238, 238],
DarkVibrant: [238, 238, 238],
})}
/>
</Helmet>

View File

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

View File

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

1347
yarn.lock

File diff suppressed because it is too large Load Diff