profile
viewpoint
Ryan Florence ryanflorence React Training Seattle, WA http://reacttraining.com @ReactTraining, React Router, @Reach. Whole crew is on tour in November, join us!

jaredpalmer/after.js 3525

Next.js-like framework for server-rendered React apps built with React Router 4

mjackson/expect 2320

Write better assertions

reactions/component 1059

Declarative version of React.Component

ryanflorence/async-props 566

Co-located data loading for React Router

locks/ember-localstorage-adapter 478

Name says it all.

adamziel/react-router-named-routes 95

Painless support for named routes for ReactRouter 1.0, 2.0, 3.0, 4.0, and 5.0

knowbody/react-router-docs 57

Rewrite of the React Router docs - work in progress

mjackson/unpkg-demos 49

Experiments in how to use unpkg

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha b03a5a7676507d289246ebcd354f14a15e280b38

Production assets

view details

push time in a day

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 1cbfe4981404abd53b8a18ce80d8b84a626c2101

Rename since we don't have two anymore

view details

push time in 2 days

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 235f38e5b22ce160d566ef4ff939e8f897181f60

Omigosh been screwing around with this problem for a week probably and finally cracked it (you can't pass two webpack configs to the webpack dev middleware!). This is fantastic: Once you start the server you don't ever need to restart it. Webpack's HMR watches and triggers a page reload. The server clears out the require cache so the newly requested page uses the latest code on the server (and ofc the client).

view details

push time in 2 days

push eventryanflorence/webpack-help

Ryan Florence

commit sha 0980472c7dbe638f898498652849f876591240ce

Narrowed it down

view details

push time in 2 days

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 0e7f0f31b7e4605563b2e1be6f343be15888d85f

blah

view details

push time in 2 days

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 64e62ea87f9a043b71e5ac557a33200833020f76

webpack dev middleware

view details

Ryan Florence

commit sha da63e853ce4c18856aa7b1e2f293ac818d870f12

Attempting to get hot middleware working

view details

push time in 2 days

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 6860b359db1cc7859243bf2ce0d4a4fe9be55deb

added CSS with tailwind

view details

push time in 2 days

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 90dd66221d63cb90bb33203388de8ab1e08907f9

Added README

view details

push time in 2 days

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 4ee00468681689af0906e99f90c5b6f4b797d1fd

Added README

view details

push time in 2 days

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 6e5fabc82325c803d710ef2778f7a77f85532cb5

New name

view details

push time in 2 days

push eventryanflorence/ryan-learns-webpack-again-after-five-years

Ryan Florence

commit sha 8531a3e959dbd894a5894ee476fab8290c73f8ac

New name

view details

push time in 2 days

created repositoryryanflorence/ryan-learns-webpack-again-after-five-years

created time in 2 days

pull request commentryanflorence/webpack-help

Fix public path

thank you!

kevin940726

comment created time in 8 days

push eventryanflorence/webpack-help

Kai Hao

commit sha 335c70a388e2334f1871fa8ced1e0191f902aa59

Fix public path

view details

Ryan Florence

commit sha 14a6d157ed72c7632c4a6827ef026c115d20adab

Merge pull request #1 from kevin940726/fix-public-path Fix public path

view details

push time in 8 days

PR merged ryanflorence/webpack-help

Fix public path

This works for me :)

+2 -1

0 comment

1 changed file

kevin940726

pr closed time in 8 days

create barnchryanflorence/webpack-help

branch : master

created branch time in 8 days

created repositoryryanflorence/webpack-help

created time in 8 days

push eventryanflorence/fs-routes

Ryan Florence

commit sha 4ad5271ec1391f29c686f558baffc6822da73a7e

yoooooooooooo

view details

push time in 16 days

create barnchryanflorence/fs-routes

branch : master

created branch time in 16 days

created repositoryryanflorence/fs-routes

move along

created time in 16 days

push eventReactTraining/react-router

Ryan Florence

commit sha a61949e08b20afb259a5a0b40ba3f403a432c851

Update migrating-5-to-6.md

view details

push time in 19 days

push eventReactTraining/react-router

Ryan Florence

commit sha 638977f430c8ff844b9c688fe117206c4088be26

Update migrating-5-to-6.md

view details

push time in 19 days

push eventReactTraining/react-router

Ryan Florence

commit sha f0575e0588c5ab37f7a066cef749a4faad001081

Update migrating-5-to-6.md

view details

push time in 19 days

push eventReactTraining/react-router

Ryan Florence

commit sha afec87471c6b4a9419e5ff9cf9f2686dee918486

Update migrating-5-to-6.md

view details

push time in 19 days

push eventReactTraining/react-router

Ryan Florence

commit sha b84123aca911c74fcb3e79f182dfb1a447b841e9

Update migrating-5-to-6.md the userId => id swapping was kinda confusing (and the code was a bit inaccurate too, so I just switched it all to `id`

view details

push time in 19 days

push eventReactTraining/react-router

Ryan Florence

commit sha 474a4effa4905b60c51d361cc110938e9974ea8e

Update migrating-5-to-6.md

view details

push time in 19 days

push eventReactTraining/react-router

Ryan Florence

commit sha d8294d91dffab2b754631b809004ade799fdbed8

Update migrating-5-to-6.md

view details

push time in 19 days

pull request commentreach/router

[WIP] Add Hooks APIs

This says WIP but it looks good to me. Michael and I chatted about useMatch and we're good with it here, so I'm ready to merge this unless you've got some other stuff you needed to do?

blainekasten

comment created time in a month

Pull request review commentreach/router

[WIP] Add Hooks APIs

+# useLocation++Returns the location to any component.++This API requires a hook-compatible version of React.++```jsx+import { useLocation } from "@reach/router"++const AnalyticTracker = (props) => {+  const location = useLocation();++  useEffect(() => {+    ga.send(['pageview', location.pathname]);+  }, [])++  return null+)+```

I'd just use a hook, we don't need these weird components that don't render anything anymore :D

import { useLocation } from "@reach/router"

const usePageViews = () => {
  const location = useLocation();
  useEffect(() => {
    ga.send(['pageview', location.pathname]);
  }, [location]) // don't forget the location here:
)
blainekasten

comment created time in a month

push eventreach/reach-ui

Ryan Florence

commit sha d8a6b509ffa78fc2a8b700e086b288686107aa11

Rename stuff to avoid cognitive leaks

view details

push time in a month

push eventreach/reach-ui

Ryan Florence

commit sha dbb8374c20a61e255850701a4384170e6419e9f5

Couple tweaks - Simplified the object lookup (😆) - Changed the classname to make it clear it's an example classname, not part of the API, since we use the terms "pseudo" and "container" it kinda felt like part of the API but it wasn't.

view details

push time in a month

pull request commentreach/router

[WIP] Add Hooks APIs

Regarding (2), right, so:

// replaces getting stuff from component props
const bag = useRouteMatch()

// replaces (or is simply the implementation of) <Match/>
const match = useMatch(path)
blainekasten

comment created time in a month

push eventReactTraining/react-workshop

Ryan Florence

commit sha 3111b1e203964803170b2c6f1b018f86081f2817

Use location from react router

view details

Ryan Florence

commit sha 853a4aca8b61f51973c3e9d556fb08a6296414cd

Clear out search when empty When you click "show all [filter type]" it would remove the type from the query string, but if you unchecked the last checked item, it would remain in the query string as something like "brands=&categories=", this clears out that key when nothing is selected

view details

push time in a month

Pull request review commentreach/reach-ui

New component: @reach/accordion

+////////////////////////////////////////////////////////////////////////////////+// Welcome to @reach/accordion!++import React, {+  Children,+  cloneElement,+  createContext,+  forwardRef,+  useCallback,+  useContext,+  useMemo,+  useRef,+  useState+} from "react";+import { makeId, wrapEvent, useForkedRef } from "@reach/utils";+import { useId } from "@reach/auto-id";+import PropTypes from "prop-types";+import warning from "warning";++// A11y reference:+//   - https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html+//   - https://inclusive-components.design/collapsible-sections/++// TODO: Screen reader testing++const AccordionContext = createContext({});+const AccordionItemContext = createContext({});+const useAccordionContext = () => useContext(AccordionContext);+const useAccordionItemContext = () => useContext(AccordionItemContext);++////////////////////////////////////////////////////////////////////////////////+// Accordion++export const Accordion = forwardRef(function Accordion(+  {+    children,+    defaultIndex,+    index: controlledIndex,+    onChange,+    readOnly = false,+    toggle = false,+    ...props+  },+  forwardedRef+) {+  /*+   * You shouldn't switch between controlled/uncontrolled. We'll check for a+   * controlled component and track any changes in a ref to show a warning.+   */+  const wasControlled = typeof controlledIndex !== "undefined";+  const { current: isControlled } = useRef(wasControlled);++  const id = useId(props.id);++  const [activeIndex, setActiveIndex] = useState(+    isControlled+      ? controlledIndex+      : defaultIndex != null+      ? defaultIndex+      : toggle+      ? -1+      : 0+  );++  /*+   * We will store all AccordionTrigger refs inside this array to manage focus.+   */+  const focusabledTriggerNodes = useRef([]);++  /*+   * Loop through children and find all `disabled` items. This will allow us+   * to determine next/previous focusable items. We also need to count children+   * with the `active` prop to deal with instances where `allowMultiple` is set+   * on an accordion with multiple active children.+   */+  const childrenArray = useMemo(() => {+    const arr = React.Children.toArray(children);+    return Array.isArray(arr) ? arr : [];+  }, [children]);++  const enabledIndices = useMemo(() => {+    let enabledIndices = [];++    for (let i = 0; i < childrenArray.length; i++) {+      const child = childrenArray[i];+      if (!(typeof child.type === "string" || child.props.disabled === true)) {+        enabledIndices.push(i);+      }+    }+    return enabledIndices;+  }, [childrenArray]);++  if (__DEV__) {+    warning(+      !((isControlled && !wasControlled) || (!isControlled && wasControlled)),+      "Accordion is changing from controlled to uncontrolled. Accordion should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled Accordion for the lifetime of the component. Check the `index` prop being passed in."+    );+  }++  function onSelectPanel(index) {+    onChange && onChange(index);+    /*+     * Before updating the active item internally, check that:+     *   - Component is uncontrolled+     *   - If toggle is not allowed, check that the change isn't coming from an+     *     item that is already active.+     */+    if (!isControlled && !(activeIndex === index && !toggle)) {+      setActiveIndex(activeIndex === index && toggle ? -1 : index);+    }+  }++  const context = {+    accordionId: id,+    activeIndex,+    focusabledTriggerNodes,+    enabledIndices,+    onSelectPanel: readOnly ? noop : onSelectPanel,+    readOnly+  };++  if (isControlled && controlledIndex !== activeIndex) {+    /*+     * If the component is controlled, we'll sync internal state with the+     * controlled state+     */+    setActiveIndex(controlledIndex);+  }++  return (+    <AccordionContext.Provider value={context}>+      <div data-reach-accordion="" ref={forwardedRef} {...props}>+        {Children.map(children, (child, _index) =>+          cloneElement(child, { _index })+        )}+      </div>+    </AccordionContext.Provider>+  );+});++Accordion.displayName = "Accordion";+if (__DEV__) {+  Accordion.propTypes = {+    children: PropTypes.node.isRequired,+    index: (props, name, compName, ...rest) => {+      if (props.index == null && props.onChange == null) {+        return new Error(+          "You provided an `index` prop to `Accordion` without an `onChange` handler. This will render a read-only accordion element. If the accordion should be mutable, remove `index` and use the `active` prop on the nested items that should be active. Otherwise, set `onChange`."+        );+      }+      return PropTypes.number(name, props, compName, ...rest);+    },+    onChange: PropTypes.func,+    readOnly: PropTypes.bool,+    toggle: PropTypes.bool+  };+}++////////////////////////////////////////////////////////////////////////////////+// AccordionItem++export const AccordionItem = forwardRef(function AccordionItem(+  { _index: index, children, disabled = false, ...props },+  forwardedRef+) {+  const { accordionId, activeIndex, readOnly } = useAccordionContext();++  // We need unique IDs for the panel and trigger to point to one another+  const itemId = makeId(accordionId, index);+  const panelId = makeId("panel", itemId);+  const triggerId = makeId("trigger", itemId);++  const context = {+    active: activeIndex === index,+    disabled,+    triggerId,+    index,+    itemId,+    panelId+  };++  return (+    <AccordionItemContext.Provider value={context}>+      <div+        ref={forwardedRef}+        data-reach-accordion-item=""+        data-active={activeIndex === index ? "" : undefined}+        data-disabled={disabled ? "" : undefined}+        data-read-only={readOnly ? "" : undefined}+        {...props}+      >+        {children}+      </div>+    </AccordionItemContext.Provider>+  );+});++AccordionItem.displayName = "AccordionItem";+if (__DEV__) {+  AccordionItem.propTypes = {+    disabled: PropTypes.bool+  };+}++////////////////////////////////////////////////////////////////////////////////+// AccordionTrigger++export const AccordionTrigger = forwardRef(function AccordionTrigger(+  {+    as: Comp = "button",+    children,+    onClick,+    onKeyDown,+    onMouseDown,+    onPointerDown,+    ...props+  },+  forwardedRef+) {+  const {+    enabledIndices,+    focusabledTriggerNodes,+    onSelectPanel,+    readOnly+  } = useAccordionContext();++  const {+    active,+    disabled,+    triggerId,+    index,+    panelId+  } = useAccordionItemContext();++  /*+   * If the user decides to use a div instead of a native button, we check the+   * ref's node type after it mounts to the DOM in order to shim the necessary+   * attributes.+   */+  const [isButtonElement, setIsButtonElement] = useState(Comp === "button");+  const ownRef = useRef(null);+  const setButtonRef = useCallback(+    node => {+      ownRef.current = node;+      if (node && Comp !== "button") {+        setIsButtonElement(node.nodeName === "BUTTON");+      }+    },+    [Comp]+  );+  const buttonAttributeProps = isButtonElement

I'd say to only pass in props for a proper button. If they want to use a different kind of element they're going to need a lot more than just a couple attributes and a keyboard handler (and I don't want to get into the business of recreating all the behaviors of buttons like we do later in this code. Either they use a button, or give us a component with the same props as a button and it's your fault for screwing up the event handlers if it doesn't work like a button)

chancestrickland

comment created time in 2 months

Pull request review commentreach/reach-ui

New component: @reach/accordion

+////////////////////////////////////////////////////////////////////////////////+// Welcome to @reach/accordion!++import React, {+  Children,+  cloneElement,+  createContext,+  forwardRef,+  useCallback,+  useContext,+  useMemo,+  useRef,+  useState+} from "react";+import { makeId, wrapEvent, useForkedRef } from "@reach/utils";+import { useId } from "@reach/auto-id";+import PropTypes from "prop-types";+import warning from "warning";++// A11y reference:+//   - https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html+//   - https://inclusive-components.design/collapsible-sections/++// TODO: Screen reader testing++const AccordionContext = createContext({});+const AccordionItemContext = createContext({});+const useAccordionContext = () => useContext(AccordionContext);+const useAccordionItemContext = () => useContext(AccordionItemContext);++////////////////////////////////////////////////////////////////////////////////+// Accordion++export const Accordion = forwardRef(function Accordion(+  {+    children,+    defaultIndex,+    index: controlledIndex,+    onChange,+    readOnly = false,+    toggle = false,+    ...props+  },+  forwardedRef+) {+  /*+   * You shouldn't switch between controlled/uncontrolled. We'll check for a+   * controlled component and track any changes in a ref to show a warning.+   */+  const wasControlled = typeof controlledIndex !== "undefined";+  const { current: isControlled } = useRef(wasControlled);++  const id = useId(props.id);++  const [activeIndex, setActiveIndex] = useState(+    isControlled+      ? controlledIndex+      : defaultIndex != null+      ? defaultIndex+      : toggle+      ? -1+      : 0+  );++  /*+   * We will store all AccordionTrigger refs inside this array to manage focus.+   */+  const focusabledTriggerNodes = useRef([]);++  /*+   * Loop through children and find all `disabled` items. This will allow us+   * to determine next/previous focusable items. We also need to count children+   * with the `active` prop to deal with instances where `allowMultiple` is set+   * on an accordion with multiple active children.+   */+  const childrenArray = useMemo(() => {+    const arr = React.Children.toArray(children);+    return Array.isArray(arr) ? arr : [];+  }, [children]);++  const enabledIndices = useMemo(() => {+    let enabledIndices = [];++    for (let i = 0; i < childrenArray.length; i++) {+      const child = childrenArray[i];+      if (!(typeof child.type === "string" || child.props.disabled === true)) {+        enabledIndices.push(i);+      }+    }+    return enabledIndices;+  }, [childrenArray]);++  if (__DEV__) {+    warning(+      !((isControlled && !wasControlled) || (!isControlled && wasControlled)),+      "Accordion is changing from controlled to uncontrolled. Accordion should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled Accordion for the lifetime of the component. Check the `index` prop being passed in."+    );+  }++  function onSelectPanel(index) {+    onChange && onChange(index);+    /*+     * Before updating the active item internally, check that:+     *   - Component is uncontrolled+     *   - If toggle is not allowed, check that the change isn't coming from an+     *     item that is already active.+     */+    if (!isControlled && !(activeIndex === index && !toggle)) {+      setActiveIndex(activeIndex === index && toggle ? -1 : index);+    }+  }++  const context = {+    accordionId: id,+    activeIndex,+    focusabledTriggerNodes,+    enabledIndices,+    onSelectPanel: readOnly ? noop : onSelectPanel,+    readOnly+  };++  if (isControlled && controlledIndex !== activeIndex) {+    /*+     * If the component is controlled, we'll sync internal state with the+     * controlled state+     */+    setActiveIndex(controlledIndex);+  }++  return (+    <AccordionContext.Provider value={context}>+      <div data-reach-accordion="" ref={forwardedRef} {...props}>+        {Children.map(children, (child, _index) =>+          cloneElement(child, { _index })

If we are cloning any reason to use context?

chancestrickland

comment created time in 2 months

Pull request review commentreach/reach-ui

New component: @reach/accordion

+////////////////////////////////////////////////////////////////////////////////+// Welcome to @reach/accordion!++import React, {+  Children,+  cloneElement,+  createContext,+  forwardRef,+  useCallback,+  useContext,+  useMemo,+  useRef,+  useState+} from "react";+import { makeId, wrapEvent, useForkedRef } from "@reach/utils";+import { useId } from "@reach/auto-id";+import PropTypes from "prop-types";+import warning from "warning";++// A11y reference:+//   - https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html+//   - https://inclusive-components.design/collapsible-sections/++// TODO: Screen reader testing++const AccordionContext = createContext({});+const AccordionItemContext = createContext({});+const useAccordionContext = () => useContext(AccordionContext);+const useAccordionItemContext = () => useContext(AccordionItemContext);++////////////////////////////////////////////////////////////////////////////////+// Accordion++export const Accordion = forwardRef(function Accordion(+  {+    children,+    defaultIndex,+    index: controlledIndex,+    onChange,+    readOnly = false,+    toggle = false,

I don't understand this prop. What does it do? Is it like, "can completely collapse"?

chancestrickland

comment created time in 2 months

Pull request review commentreach/reach-ui

New component: @reach/accordion

+:root {+  --reach-disclosure: 1;+}++[data-reach-disclosure] {+  border: 1px solid #c5c5c5;+}++[data-reach-disclosure-panel] {+  padding: 0.5em;+  margin: 0;+  border: 1px solid #aaa;+  border-top: 0;+  border-bottom-left-radius: 0.25rem;+  border-bottom-right-radius: 0.25rem;+}++[data-reach-disclosure-trigger] {

Actually, I think we don't need any styles at all for this one.

chancestrickland

comment created time in 2 months

Pull request review commentreach/reach-ui

New component: @reach/accordion

+:root {+  --reach-disclosure: 1;+}++[data-reach-disclosure] {+  border: 1px solid #c5c5c5;+}++[data-reach-disclosure-panel] {+  padding: 0.5em;+  margin: 0;+  border: 1px solid #aaa;+  border-top: 0;+  border-bottom-left-radius: 0.25rem;+  border-bottom-right-radius: 0.25rem;+}++[data-reach-disclosure-trigger] {

I think there are too many rules in these styles, just leave it as a button and give them an example in the docs of the styles to make buttons look like divs.

chancestrickland

comment created time in 2 months

pull request commentreach/router

Remove group role from focus wrapper

Looks good, I'm happy to just put this straight into a minor release w/o a beta.

chancestrickland

comment created time in 2 months

push eventreach/reach-ui

Ryan Florence

commit sha 89f0dcdc01024d87878ec6d53eb261825ac497b9

[auto-d] add additional info about implementation

view details

push time in 2 months

issue openedreach/reach-ui

[dialog] need to disable tooltips when dialogs close

🐛 Bug report

Current Behavior

Tooltip shows up after a dialog is closed and focus returns to the button that opened it.

Expected behavior

It shouldn't show up

Reproducible example

Maybe later ...

Suggested solution(s)

Need to do tricks with window.__REACH_DISABLE_TOOLTIPS or whatever it is in dialog the way we do in menu button.

We'll need to be careful not to disable them while the dialog is open because tooltips should be available for dialog content.

created time in 3 months

Pull request review commentreach/reach-ui

tooltip: add document event listener to hide tooltip on escape

 export function useTooltip({    useEffect(() => checkStyles("tooltip")); 

Could simplify this a bit:

useEffect(() => {
    const listener = event => {
      if ((event.key === "Escape" || event.key === "Esc") && state === VISIBLE) {
        transition("selectWithKeyboard")
      }
    };
    document.addEventListener("keydown", listener);
    return () => document.removeEventListener("keydown", listener);
  }, []);
chancestrickland

comment created time in 3 months

push eventreach/reach-ui

Chance Strickland

commit sha 3b5319027d763a3082880be887d7a29aee7d3afc

visually-hidden: add guard for word wrapping (#372)

view details

push time in 3 months

PR merged reach/reach-ui

visually-hidden: Add guard for word wrapping bug

Added guards in visually-hidden to deal with potential issues related to word wrapping. Reference article here: https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe

Went ahead and removed display: block since we're using position: absolute as well, per #372.

+5 -2

0 comment

1 changed file

chancestrickland

pr closed time in 3 months

pull request commentreach/reach-ui

New component: @reach/checkbox

If you click the readOnly checkbox twice it switches over to checked

chancestrickland

comment created time in 3 months

push eventreach/reach-ui

Ryan Florence

commit sha c9da4e29e3a9042a7c5c94837da5a741e4670534

Moar!

view details

Ryan Florence

commit sha c07938a6d519d08a5845213777c87a6370689c68

MOAR!

view details

Ryan Florence

commit sha bb8f91a8716cf0c2ae26138949fc765c56fb0678

MMOOOAR

view details

Ryan Florence

commit sha 6cfb7825b163d7037ec59449246c4e43b33b65a3

hol* sm*kes!

view details

Ryan Florence

commit sha 3d89bd446b342a956ce35f48b87adae1956d203b

Before deleting stuff

view details

Ryan Florence

commit sha 92be52e425e7fdf0b196009fccb45ef487649549

I dunno

view details

Ryan Florence

commit sha f88d2796af87e07d0c61ac1d58d357bc3214e9a9

wip

view details

push time in 3 months

push eventreach/reach-ui

Ryan Florence

commit sha 3f76e28028dc5af9acc01affadf506b736c7c9c6

[auto-id]: better implementation - don't do the update dance every time, only the very first render when it matters - useLayoutEffect to avoid flashing Both of this fixes would have prevented the tooltip bonanza bugs Also documented the code to answer "why are you doing this" and the "how the heck does it work?"

view details

Ryan Florence

commit sha ed9a975964605cd082fa69b9984b0c6bed150439

More menu button fun! It's done, now going to build the React Adapter

view details

push time in 4 months

push eventreach/reach-ui

Ryan Florence

commit sha 7f34f18771675f98ed2518da36691cd860069e95

Mo' bettah descendants

view details

Ryan Florence

commit sha 648471245d642bc15745d13a7b08020979e94e75

Better confirmation animation

view details

push time in 4 months

create barnchreach/reach-ui

branch : menu-button-rewrite

created branch time in 4 months

Pull request review commentreach/reach-ui

New component: @reach/checkbox

+////////////////////////////////////////////////////////////////////////////////+// Welcome to @reach/checkbox!++import React, {+  forwardRef,+  createContext,+  useContext,+  useEffect,+  useRef,+  useState+} from "react";+import { wrapEvent, assignRef } from "@reach/utils";+import warning from "warning";+import { any, string, bool, func, oneOfType, node, shape } from "prop-types";++const CustomCheckboxContext = createContext({});+const useCheckboxContext = () => useContext(CustomCheckboxContext);++////////////////////////////////////////////////////////////////////////////////+export const CustomCheckboxContainer = forwardRef(+  function CustomCheckboxContainer({ children, ...props }, forwardedRef) {+    const [+      { checked, mixed, disabled, readOnly, focused },+      setContainerState+    ] = useState({+      checked: null,+      mixed: null,+      disabled: null,+      readOnly: null,+      focused: null+    });++    return (+      <CustomCheckboxContext.Provider+        value={{ setContainerState, visuallyHidden: true }}+      >+        <div+          ref={forwardedRef}+          {...props}+          data-reach-custom-checkbox-container=""+          data-checked={checked ? "" : undefined}+          data-focus={focused ? "" : undefined}+          data-mixed={mixed ? "" : undefined}+          data-disabled={disabled ? "" : undefined}+          data-read-only={readOnly ? "" : undefined}+        >+          {children}+        </div>+      </CustomCheckboxContext.Provider>+    );+  }+);++////////////////////////////////////////////////////////////////////////////////+export const CustomCheckbox = forwardRef(function CustomCheckbox(+  {+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    checkmarks,+    children,+    id,

any reason we're messing around with id, name here? They just get forwarded to the underlying input.

chancestrickland

comment created time in 4 months

pull request commentreach/reach-ui

New component: @reach/checkbox

I made an example of an uncontrolled custom checkbox and I'm getting this error:

MixedCheckbox contains an input of type checkbox with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled
     <CustomCheckbox
        defaultChecked={state}
        onChange={event => {
          const { checked } = event.target;
          setState(checked);
        }}
        checkmarks={{
          true: <span style={smileyStyle}>😃</span>,
          false: <span style={smileyStyle}>🙁</span>
        }}
      />

While we can fix this directly, I think the issue stems from a deeper composition problem that might reveal more problems down the line.

MixedCheckbox is entangled with CustomCheckbox. I think the code could be organized in a way that CustomCheckbox looks at its props and then decides to render a normal checkbox or a mixed checkbox, rather than mixed checkbox being aware of custom checkboxes.

MixedCheckbox can have a much simpler implementation because it:

  1. won't have any knowledge of custom checkboxes
  2. won't need to support being uncontrolled, all mixed checkboxes are necessarily controlled.

The current code has a cartesian product of requirements that must be dealt with, rather than things being encapsulated and then composed.

chancestrickland

comment created time in 4 months

Pull request review commentreach/reach-ui

New component: @reach/checkbox

+////////////////////////////////////////////////////////////////////////////////+// Welcome to @reach/checkbox!++import React, {+  forwardRef,+  createContext,+  useContext,+  useEffect,+  useRef,+  useState+} from "react";+import { wrapEvent, assignRef } from "@reach/utils";+import warning from "warning";+import { any, string, bool, func, oneOfType, node, shape } from "prop-types";++const CustomCheckboxContext = createContext({});+const useCheckboxContext = () => useContext(CustomCheckboxContext);++////////////////////////////////////////////////////////////////////////////////+export const CustomCheckboxContainer = forwardRef(+  function CustomCheckboxContainer({ children, ...props }, forwardedRef) {+    const [+      { checked, mixed, disabled, readOnly, focused },+      setContainerState+    ] = useState({+      checked: null,+      mixed: null,+      disabled: null,+      readOnly: null,+      focused: null+    });++    return (+      <CustomCheckboxContext.Provider+        value={{ setContainerState, visuallyHidden: true }}+      >+        <div+          ref={forwardedRef}+          {...props}+          data-reach-custom-checkbox-container=""+          data-checked={checked ? "" : undefined}+          data-focused={focused ? "" : undefined}+          data-mixed={mixed ? "" : undefined}+          data-disabled={disabled ? "" : undefined}+          data-read-only={readOnly ? "" : undefined}+        >+          {children}+        </div>+      </CustomCheckboxContext.Provider>+    );+  }+);++////////////////////////////////////////////////////////////////////////////////+export const CustomCheckbox = forwardRef(function CustomCheckbox(+  {+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    checkmarks,+    children,+    id,+    name,+    onChange,+    readOnly,+    value,+    ...props+  },+  forwardedRef+) {+  const [inputProps, { checked }] = useMixedCheckbox({+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    onChange,+    readOnly+  });+  return (+    <CustomCheckboxContainer+      data-reach-custom-checkbox=""+      {...props}+      ref={forwardedRef}+    >+      {checked === true && checkmarks.true}+      {checked === false && checkmarks.false}+      {checked === "mixed" && checkmarks.mixed}+      {children}+      <MixedCheckbox+        id={id}+        value={value}+        name={name}+        readOnly={readOnly}+        {...inputProps}+      />+    </CustomCheckboxContainer>+  );+});++if (__DEV__) {+  CustomCheckbox.propTypes = {+    checkmarks: shape({+      true: node,+      false: node,+      mixed: node+    })+  };+  CustomCheckbox.displayName = "CustomCheckbox";+}++////////////////////////////////////////////////////////////////////////////////+export const MixedCheckbox = forwardRef(function MixedCheckbox(+  {+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    name,+    onBlur,+    onChange,+    onFocus,+    readOnly,+    ...props+  },+  forwardedRef+) {+  const { setContainerState, visuallyHidden } = useCheckboxContext();+  const [+    inputProps,+    { focused, checked, disabled, mixed, isControlled }+  ] = useMixedCheckbox({+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    onBlur,+    onChange,+    onFocus,+    readOnly+  });++  const ownRef = useRef(null);+  const ref = useForkedRef(forwardedRef, ownRef);++  useEffect(() => {+    ownRef.current.indeterminate = mixed;+    ownRef.current.checked = checked === true ? true : false;+  }, [mixed, checked]);++  useEffect(() => {+    if (setContainerState) {+      setContainerState({ checked, mixed, disabled, readOnly, focused });+    }+  }, [setContainerState, checked, disabled, mixed, readOnly, focused]);++  if (__DEV__) {+    checkboxErrorChecks({+      isControlled,+      controlledChecked,+      defaultChecked+    });+  }++  return (+    <input+      {...props}+      {...inputProps}+      ref={ref}+      data-reach-mixed-checkbox=""+      data-reach-mixed-checkbox-hidden={visuallyHidden && ""}+      name={name}+      readOnly={readOnly}+      type="checkbox"+    />+  );+});++if (__DEV__) {+  MixedCheckbox.propTypes = {+    checked: oneOfType([bool, string]),+    defaultChecked: bool,+    disabled: bool,

Do we need the rest of these prop-types? They're all just normal checkbox proptypes (and React, afaik) doesn't give us a way to extend "checkbox proptypes".

Additionally I don't think name or value need to be required. They aren't required in normal checkboxes, what makes this different?

chancestrickland

comment created time in 4 months

Pull request review commentreach/reach-ui

New component: @reach/checkbox

+////////////////////////////////////////////////////////////////////////////////+// Welcome to @reach/checkbox!++import React, {+  forwardRef,+  createContext,+  useContext,+  useEffect,+  useRef,+  useState+} from "react";+import { wrapEvent, assignRef } from "@reach/utils";+import warning from "warning";+import { any, string, bool, func, oneOfType, node, shape } from "prop-types";++const CustomCheckboxContext = createContext({});+const useCheckboxContext = () => useContext(CustomCheckboxContext);++////////////////////////////////////////////////////////////////////////////////+export const CustomCheckboxContainer = forwardRef(+  function CustomCheckboxContainer({ children, ...props }, forwardedRef) {+    const [+      { checked, mixed, disabled, readOnly, focused },+      setContainerState+    ] = useState({+      checked: null,+      mixed: null,+      disabled: null,+      readOnly: null,+      focused: null+    });++    return (+      <CustomCheckboxContext.Provider+        value={{ setContainerState, visuallyHidden: true }}+      >+        <div+          ref={forwardedRef}+          {...props}+          data-reach-custom-checkbox-container=""+          data-checked={checked ? "" : undefined}+          data-focused={focused ? "" : undefined}+          data-mixed={mixed ? "" : undefined}+          data-disabled={disabled ? "" : undefined}+          data-read-only={readOnly ? "" : undefined}+        >+          {children}+        </div>+      </CustomCheckboxContext.Provider>+    );+  }+);++////////////////////////////////////////////////////////////////////////////////+export const CustomCheckbox = forwardRef(function CustomCheckbox(+  {+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    checkmarks,+    children,+    id,+    name,+    onChange,+    readOnly,+    value,+    ...props+  },+  forwardedRef+) {+  const [inputProps, { checked }] = useMixedCheckbox({+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    onChange,+    readOnly+  });+  return (+    <CustomCheckboxContainer+      data-reach-custom-checkbox=""+      {...props}+      ref={forwardedRef}+    >+      {checked === true && checkmarks.true}+      {checked === false && checkmarks.false}+      {checked === "mixed" && checkmarks.mixed}+      {children}+      <MixedCheckbox+        id={id}+        value={value}+        name={name}+        readOnly={readOnly}+        {...inputProps}+      />+    </CustomCheckboxContainer>+  );+});++if (__DEV__) {+  CustomCheckbox.propTypes = {+    checkmarks: shape({+      true: node,+      false: node,+      mixed: node+    })+  };+  CustomCheckbox.displayName = "CustomCheckbox";+}++////////////////////////////////////////////////////////////////////////////////+export const MixedCheckbox = forwardRef(function MixedCheckbox(+  {+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    name,+    onBlur,+    onChange,+    onFocus,+    readOnly,+    ...props+  },+  forwardedRef+) {+  const { setContainerState, visuallyHidden } = useCheckboxContext();+  const [+    inputProps,+    { focused, checked, disabled, mixed, isControlled }+  ] = useMixedCheckbox({+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    onBlur,+    onChange,+    onFocus,+    readOnly+  });++  const ownRef = useRef(null);+  const ref = useForkedRef(forwardedRef, ownRef);++  useEffect(() => {+    ownRef.current.indeterminate = mixed;+    ownRef.current.checked = checked === true ? true : false;+  }, [mixed, checked]);++  useEffect(() => {+    if (setContainerState) {+      setContainerState({ checked, mixed, disabled, readOnly, focused });+    }+  }, [setContainerState, checked, disabled, mixed, readOnly, focused]);++  if (__DEV__) {+    checkboxErrorChecks({+      isControlled,+      controlledChecked,+      defaultChecked+    });+  }++  return (+    <input+      {...props}+      {...inputProps}+      ref={ref}+      data-reach-mixed-checkbox=""+      data-reach-mixed-checkbox-hidden={visuallyHidden && ""}+      name={name}+      readOnly={readOnly}+      type="checkbox"+    />+  );+});++if (__DEV__) {+  MixedCheckbox.propTypes = {+    checked: oneOfType([bool, string]),+    defaultChecked: bool,

should this be oneOfType([bool, string])?

chancestrickland

comment created time in 4 months

Pull request review commentreach/reach-ui

New component: @reach/checkbox

+////////////////////////////////////////////////////////////////////////////////+// Welcome to @reach/checkbox!++import React, {+  forwardRef,+  createContext,+  useContext,+  useEffect,+  useRef,+  useState+} from "react";+import { wrapEvent, assignRef } from "@reach/utils";+import warning from "warning";+import { any, string, bool, func, oneOfType, node, shape } from "prop-types";++const CustomCheckboxContext = createContext({});+const useCheckboxContext = () => useContext(CustomCheckboxContext);++////////////////////////////////////////////////////////////////////////////////+export const CustomCheckboxContainer = forwardRef(+  function CustomCheckboxContainer({ children, ...props }, forwardedRef) {+    const [+      { checked, mixed, disabled, readOnly, focused },+      setContainerState+    ] = useState({+      checked: null,+      mixed: null,+      disabled: null,+      readOnly: null,+      focused: null+    });++    return (+      <CustomCheckboxContext.Provider+        value={{ setContainerState, visuallyHidden: true }}+      >+        <div+          ref={forwardedRef}+          {...props}+          data-reach-custom-checkbox-container=""+          data-checked={checked ? "" : undefined}+          data-focused={focused ? "" : undefined}+          data-mixed={mixed ? "" : undefined}+          data-disabled={disabled ? "" : undefined}+          data-read-only={readOnly ? "" : undefined}+        >+          {children}+        </div>+      </CustomCheckboxContext.Provider>+    );+  }+);++////////////////////////////////////////////////////////////////////////////////+export const CustomCheckbox = forwardRef(function CustomCheckbox(+  {+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    checkmarks,+    children,+    id,+    name,+    onChange,+    readOnly,+    value,+    ...props+  },+  forwardedRef+) {+  const [inputProps, { checked }] = useMixedCheckbox({+    checked: controlledChecked,+    defaultChecked,+    disabled: disabledProp,+    onChange,+    readOnly+  });+  return (+    <CustomCheckboxContainer+      data-reach-custom-checkbox=""+      {...props}+      ref={forwardedRef}+    >+      {checked === true && checkmarks.true}+      {checked === false && checkmarks.false}+      {checked === "mixed" && checkmarks.mixed}+      {children}+      <MixedCheckbox+        id={id}+        value={value}+        name={name}+        readOnly={readOnly}+        {...inputProps}+      />+    </CustomCheckboxContainer>+  );+});++if (__DEV__) {+  CustomCheckbox.propTypes = {+    checkmarks: shape({+      true: node,+      false: node,+      mixed: node+    })

shouldn't this be required?

chancestrickland

comment created time in 4 months

Pull request review commentreach/reach-ui

New component: @reach/checkbox

+import React from "react";

What does this illustrate other than that you can have multiple checkboxes?

chancestrickland

comment created time in 4 months

push eventreach/reach-ui

Ryan Florence

commit sha e1a61425f6556c647ed8aba6c3d30f11898cb375

fix focus typo

view details

push time in 4 months

PR closed reach/reach-ui

docs: Add popover to list of components docs

Add popover to list of components on readme file.

This pull request:

  • [ ] Creates a new package
  • [ ] Fixes a bug in an existing package
  • [ ] Adds additional features/functionality to an existing package
  • [ ] Updates documentation or example code
  • [X] Other
+1 -0

2 comments

1 changed file

brunoxd13

pr closed time in 4 months

pull request commentreach/reach-ui

docs: Add popover to list of components

Correct. We'll have a public popover soon though. If we made this public right now there would be accessibility issues around pretty much every usage of it.

There's a spectrum of "popovers"

no interaction: tooltip < --- menu | restricted interaction | popup --- > dialog: all interaction

We want to get it right.

brunoxd13

comment created time in 4 months

Pull request review commentreach/reach-ui

New component API notes 📝

+## ❓: Breadcrumbs++```jsx+// High level API+<Breadcrumbs separator={<FancyIcon />}>+  <Breadcrumb as="a" href="/">Home</Breadcrumb>+  <Breadcrumb as={StyledLink} href="/components">Parent</Breadcrumb>+  <Breadcrumb current>Current Page</Breadcrumb>+</Breadcrumbs>++// Lower level API+<Breadcrumbs>+  <BreadcrumbList>+    <BreadcrumbItem>+      <a href="/">Home</a>+      <FancyIcon aria-hidden />+    </BreadcrumbItem>+    <BreadcrumbItem>+      <StyledLink href="/components">Parent</StyledLink>+      <FancyIcon aria-hidden />+    </BreadcrumbItem>+    <BreadcrumbItem>+      <span>Current Page</span>+    </BreadcrumbItem>+  </BreadcrumbList>+</Breadcrumbs>+```++## ❓: Button++TBD++## 🛠: Carousel++```jsx+<Carousel>+  <CarouselSlides>+    <CarouselSlide>Slide 1</CarouselSlide>+    <CarouselSlide>Slide 2</CarouselSlide>+    <CarouselSlide>Slide 3</CarouselSlide>+  </CarouselSlides>+  <CarouselArrows />+  <CarouselIndicators />+</Carousel>+```++```ts+type CarouselProps = {+  activeIndex: // starting index for controlled components+  // if multiple slides are visible, this positions the currently active slide.+  // default: center+  activeSlidePosition: 'center' | 'start' | 'end';+  autoPlay: boolean | number; // number = speed in MS+  controls: React.RefObject; // turns the slider into a control for another slider.+  defaultActiveIndex: number; // default starting index for uncontrolled components+  disabled: boolean;+  draggable: boolean; // default: true+  // whether or not the slider should be an infinite loop+  // imagining we'd need our component to control this if the user wants to+  // control animation between the last and first slides+  // (might also affect how indicators are rendered + arrow behavior)+  loopSlides: boolean;+  onChange(event, trigger): void;+  orientation: 'horizontal' | 'vertical'; // default: 'horizontal'+  rtl: boolean; // sets read/swipe mode to RTL+  slidesShown?: number; // default: 1+};+```++## 🛠: Disclosure++> NOTE: This should follow the Accordion, as it's essentially the same basic behavior with less flexibility and no custom focus management.++```jsx+<Disclosure>+  <DisclosureItem>+    <DisclosureTrigger>Open the box</DisclosureTrigger>+    <DisclosureContent>Some content</DisclosureContent>+  </DisclosureItem>+  <DisclosureItem>+    <DisclosureTrigger>Open the box</DisclosureTrigger>+    <DisclosureContent>Some content</DisclosureContent>+  </DisclosureItem>+</Disclosure>+```++## ❓: Feed++```jsx+// suspense component? Maybe? Does this even work?+function Example() {+  const posts = resource.posts.read();+  return (+    <Feed fallback={<Loading />} loadingNext={<LoadingNext />}>+      {posts.map(post => (+        <FeedItem key={post.id}>+          <h1>{post.title}</h1>+          <a href={post.src}>+            Read More<VisuallyHidden> about {post.title}</VisuallyHidden>+          </a>+        </FeedItem>+      ))}+    </Feed>+  );+}+```++## ❓: Grid++TBD++## ❓: Link++TBD++## 🛠: Listbox++```jsx+<Listbox>+  <ListboxArea>

Same here, not sure what this would do

chancestrickland

comment created time in 4 months

Pull request review commentreach/reach-ui

New component API notes 📝

+## ❓: Breadcrumbs++```jsx+// High level API+<Breadcrumbs separator={<FancyIcon />}>+  <Breadcrumb as="a" href="/">Home</Breadcrumb>+  <Breadcrumb as={StyledLink} href="/components">Parent</Breadcrumb>+  <Breadcrumb current>Current Page</Breadcrumb>+</Breadcrumbs>++// Lower level API+<Breadcrumbs>+  <BreadcrumbList>+    <BreadcrumbItem>+      <a href="/">Home</a>+      <FancyIcon aria-hidden />+    </BreadcrumbItem>+    <BreadcrumbItem>+      <StyledLink href="/components">Parent</StyledLink>+      <FancyIcon aria-hidden />+    </BreadcrumbItem>+    <BreadcrumbItem>+      <span>Current Page</span>+    </BreadcrumbItem>+  </BreadcrumbList>+</Breadcrumbs>+```++## ❓: Button++TBD++## 🛠: Carousel++```jsx+<Carousel>+  <CarouselSlides>+    <CarouselSlide>Slide 1</CarouselSlide>+    <CarouselSlide>Slide 2</CarouselSlide>+    <CarouselSlide>Slide 3</CarouselSlide>+  </CarouselSlides>+  <CarouselArrows />+  <CarouselIndicators />+</Carousel>+```++```ts+type CarouselProps = {+  activeIndex: // starting index for controlled components+  // if multiple slides are visible, this positions the currently active slide.+  // default: center+  activeSlidePosition: 'center' | 'start' | 'end';+  autoPlay: boolean | number; // number = speed in MS+  controls: React.RefObject; // turns the slider into a control for another slider.+  defaultActiveIndex: number; // default starting index for uncontrolled components+  disabled: boolean;+  draggable: boolean; // default: true+  // whether or not the slider should be an infinite loop+  // imagining we'd need our component to control this if the user wants to+  // control animation between the last and first slides+  // (might also affect how indicators are rendered + arrow behavior)+  loopSlides: boolean;+  onChange(event, trigger): void;+  orientation: 'horizontal' | 'vertical'; // default: 'horizontal'+  rtl: boolean; // sets read/swipe mode to RTL+  slidesShown?: number; // default: 1+};+```++## 🛠: Disclosure++> NOTE: This should follow the Accordion, as it's essentially the same basic behavior with less flexibility and no custom focus management.++```jsx+<Disclosure>+  <DisclosureItem>+    <DisclosureTrigger>Open the box</DisclosureTrigger>+    <DisclosureContent>Some content</DisclosureContent>+  </DisclosureItem>+  <DisclosureItem>+    <DisclosureTrigger>Open the box</DisclosureTrigger>+    <DisclosureContent>Some content</DisclosureContent>+  </DisclosureItem>+</Disclosure>+```++## ❓: Feed++```jsx+// suspense component? Maybe? Does this even work?+function Example() {+  const posts = resource.posts.read();+  return (+    <Feed fallback={<Loading />} loadingNext={<LoadingNext />}>+      {posts.map(post => (+        <FeedItem key={post.id}>+          <h1>{post.title}</h1>+          <a href={post.src}>+            Read More<VisuallyHidden> about {post.title}</VisuallyHidden>+          </a>+        </FeedItem>+      ))}+    </Feed>+  );+}+```++## ❓: Grid++TBD++## ❓: Link++TBD++## 🛠: Listbox++```jsx+<Listbox>

Not sure what this wrapper Listbox component is for? Much like the disclosure one. Listboxes aren't related to eachother any more than <select>.

chancestrickland

comment created time in 4 months

Pull request review commentreach/reach-ui

New component API notes 📝

+## ❓: Breadcrumbs++```jsx+// High level API+<Breadcrumbs separator={<FancyIcon />}>+  <Breadcrumb as="a" href="/">Home</Breadcrumb>+  <Breadcrumb as={StyledLink} href="/components">Parent</Breadcrumb>+  <Breadcrumb current>Current Page</Breadcrumb>+</Breadcrumbs>++// Lower level API+<Breadcrumbs>+  <BreadcrumbList>+    <BreadcrumbItem>+      <a href="/">Home</a>+      <FancyIcon aria-hidden />+    </BreadcrumbItem>+    <BreadcrumbItem>+      <StyledLink href="/components">Parent</StyledLink>+      <FancyIcon aria-hidden />+    </BreadcrumbItem>+    <BreadcrumbItem>+      <span>Current Page</span>+    </BreadcrumbItem>+  </BreadcrumbList>+</Breadcrumbs>+```++## ❓: Button++TBD++## 🛠: Carousel++```jsx+<Carousel>+  <CarouselSlides>+    <CarouselSlide>Slide 1</CarouselSlide>+    <CarouselSlide>Slide 2</CarouselSlide>+    <CarouselSlide>Slide 3</CarouselSlide>+  </CarouselSlides>+  <CarouselArrows />+  <CarouselIndicators />+</Carousel>+```++```ts+type CarouselProps = {+  activeIndex: // starting index for controlled components+  // if multiple slides are visible, this positions the currently active slide.+  // default: center+  activeSlidePosition: 'center' | 'start' | 'end';+  autoPlay: boolean | number; // number = speed in MS+  controls: React.RefObject; // turns the slider into a control for another slider.+  defaultActiveIndex: number; // default starting index for uncontrolled components+  disabled: boolean;+  draggable: boolean; // default: true+  // whether or not the slider should be an infinite loop+  // imagining we'd need our component to control this if the user wants to+  // control animation between the last and first slides+  // (might also affect how indicators are rendered + arrow behavior)+  loopSlides: boolean;+  onChange(event, trigger): void;+  orientation: 'horizontal' | 'vertical'; // default: 'horizontal'+  rtl: boolean; // sets read/swipe mode to RTL+  slidesShown?: number; // default: 1+};+```++## 🛠: Disclosure++> NOTE: This should follow the Accordion, as it's essentially the same basic behavior with less flexibility and no custom focus management.++```jsx+<Disclosure>+  <DisclosureItem>+    <DisclosureTrigger>Open the box</DisclosureTrigger>

Would probably go with <DisclosureButton/> and render a button so we don't have to screw around with keyboard/focus/click etc.

chancestrickland

comment created time in 4 months

Pull request review commentreach/reach-ui

New component API notes 📝

+## ❓: Breadcrumbs++```jsx+// High level API+<Breadcrumbs separator={<FancyIcon />}>+  <Breadcrumb as="a" href="/">Home</Breadcrumb>+  <Breadcrumb as={StyledLink} href="/components">Parent</Breadcrumb>+  <Breadcrumb current>Current Page</Breadcrumb>+</Breadcrumbs>++// Lower level API+<Breadcrumbs>+  <BreadcrumbList>+    <BreadcrumbItem>+      <a href="/">Home</a>+      <FancyIcon aria-hidden />+    </BreadcrumbItem>+    <BreadcrumbItem>+      <StyledLink href="/components">Parent</StyledLink>+      <FancyIcon aria-hidden />+    </BreadcrumbItem>+    <BreadcrumbItem>+      <span>Current Page</span>+    </BreadcrumbItem>+  </BreadcrumbList>+</Breadcrumbs>+```++## ❓: Button++TBD++## 🛠: Carousel++```jsx+<Carousel>+  <CarouselSlides>+    <CarouselSlide>Slide 1</CarouselSlide>+    <CarouselSlide>Slide 2</CarouselSlide>+    <CarouselSlide>Slide 3</CarouselSlide>+  </CarouselSlides>+  <CarouselArrows />+  <CarouselIndicators />+</Carousel>+```++```ts+type CarouselProps = {+  activeIndex: // starting index for controlled components+  // if multiple slides are visible, this positions the currently active slide.+  // default: center+  activeSlidePosition: 'center' | 'start' | 'end';+  autoPlay: boolean | number; // number = speed in MS+  controls: React.RefObject; // turns the slider into a control for another slider.+  defaultActiveIndex: number; // default starting index for uncontrolled components+  disabled: boolean;+  draggable: boolean; // default: true+  // whether or not the slider should be an infinite loop+  // imagining we'd need our component to control this if the user wants to+  // control animation between the last and first slides+  // (might also affect how indicators are rendered + arrow behavior)+  loopSlides: boolean;+  onChange(event, trigger): void;+  orientation: 'horizontal' | 'vertical'; // default: 'horizontal'+  rtl: boolean; // sets read/swipe mode to RTL+  slidesShown?: number; // default: 1+};+```++## 🛠: Disclosure++> NOTE: This should follow the Accordion, as it's essentially the same basic behavior with less flexibility and no custom focus management.++```jsx+<Disclosure>

Not sure why we'd need a parent element here? Don't think we need to necessarily group them. I'd imagine we only need:

<Disclosure>
  <DisclosureButton>Open the box</DisclosureButton>
  <DisclosureContent>Some content</DisclosureContent>
</DisclosureItem>
chancestrickland

comment created time in 4 months

pull request commentReactTraining/react-router

[wip] experimenting with the new suspense hooks

I don't think that this is an improvement compared to the "current" usage of the suspense api

@MeiKatz We're not trying to wrap the suspense API in something "better", just providing a way for the parent to fire off fetches on route changes. The demos in the docs do this on button click, we do it when matching routes, but we're not replacing any suspense APIs here.

Anyway, we should wait for the resource to be loaded before we render what's inside the <Route />.

It's not our job to "wait" for the data, the app gets to decide, that's what Suspense is all about!

ryanflorence

comment created time in 4 months

pull request commentReactTraining/react-router

[wip] experimenting with the new suspense hooks

To be clear, just going for an API design here, the implementation has many problems and some new stuff we've been working on will enable us to fix them if we go this direction.

ryanflorence

comment created time in 4 months

PR opened ReactTraining/react-router

[wip] experimenting with the new suspense hooks

React Router + Suspense

In order for React Router to support suspense we need a few pieces.

Convert classes to functions

We need to use the useTransition hook to allow suspense boundaries to actually suspend. While suspense will still work with our class components, they never suspend, the always go to the loading state.

This will be a breaking change for <BrowserRouter/> and friends, so we either do it in 6.0 or we ship two versions of everything (Router, BrowserRouter, HashRouter, etc.).

Update history to replace calls to push while suspended

We already know this. When a user clicks a link, then clicks another link before the resources are done loading, they'll end up with ghost entries in the history stack--meaning they can click "back" and see an unexpected page.

Provide an simple entry point to preload data for the route

The React team's experience with suspense has shown that using render to kick of fetches in the component that needs the data leads to too many waterfalls. While they had hoped people would preload in some top-level component, they didn't. It would be unfortunate if suspense actually caused longer load times because the API encouraged waterfall requests.

The vast majority of data fetches in an app are initiated on route changes, so we're in a great position to help developers out. In fact, I was able to implement a very quick proof-of-concept in a few minutes, check it out:

<Route preload={(params, location) => resource} />

This prop will be called on render whenever a route matches and mounts. It passes in the params and location and expects a resource of any shape to be returned. It's up to the app to determine the api for the resource, we just provide a way to kick of a preload of it.

It might look something like:

const resource = createResource(key)

// Then if you try to read from it and it's not yet resolved,
// it will throw the promise to trigger suspense
resource.read()

So we might have a route like this:

<Route
  path="/invoice/:invoiceId"
  preload={(params) => createInvoiceResource(params.invoiceId)}
>
  <Invoice />
</Route>

useResource()

This hook is used inside a component to get access to the resource returned in the <Route preload/> prop. It will find the nearest resource and return it. Route components can use this hook to read data from the resource. Continuing with the invoice example:

import { useResource } from 'react-router-dom'

function Invoice() {
  const invoice = useResource().read()
  // returns the invoice if resolved, triggers suspense if not
  // ...
}

useResourcesPending()

This indicates that resources are being loaded. While suspense has a timeout before falling back or transitioning to a partial page, sometimes you want to add loading indicators to the first page. The useTransition hook returns an isPending value. This simply gives route components access to it.

const isPending = useResourcesPending()

That's it! Check out the demo in /fixtures/suspense

Here's a

+10941 -7

0 comment

26 changed files

pr created time in 4 months

push eventReactTraining/react-router

Ryan Florence

commit sha a6923f1e25dc71e44fef1b7cb9babb483f7c55c3

[wip] experimenting with the new suspense hooks

view details

push time in 4 months

create barnchReactTraining/react-router

branch : experiment-suspense

created branch time in 4 months

issue commenthasura/graphql-engine

relay support

We're hoping to tightly integrate React Router and Relay soon, we were hoping to recommend hasura (and use it ourselves!)

Would love to see this get pushed over the finish line!

srghma

comment created time in 4 months

Pull request review commentreach/reach-ui

@reach/checkbox: For review

+import React from "react";

Let's make CheckboxGroup an example for now. Great candidate for Reach DS.

chancestrickland

comment created time in 4 months

pull request commentreach/reach-ui

Lift menu list class name to outer div

Anything wrong with styling reach-menu-list instead? The portal and the wrapper aren't important (and we should probably work the code to remove the wrapper).

brendancarney

comment created time in 4 months

CommitCommentEvent
MemberEvent

issue commentreach/router

Wildcard doesn't include query parameters

Go for it!

Hypnosphi

comment created time in 5 months

issue closedreach/router

Children without paths

Warning to anyone who ever tries to do something like this (spoiler: it doesn't work):

function Register({ store }) {
    const homeUrl= getUrl("/home");
    return (
        <Router>
            <NotFound default />
            <RegisterPanel path="/" />
            <AccountLayout path="success" pageName="Registration Received">
                <>
                    <p>Your registration has been received.</p>
                    <p>Please check your email for a confirmation link.</p>
                </>
            </AccountLayout>
            <AccountLayout path="already-exists" pageName="Account Already Exists">
                <>
                    <p>An account has already been created for this email address.</p>
                    <p>
                        Please <Link to={homeUrl}>log in</Link>.
                    </p>
                </>
            </AccountLayout>
        </Router>
    );
}

export default observer(Register);

If you test the routes "success" or "already-exists", you will get an error you might find unhelpful:

Uncaught TypeError: Cannot convert a Symbol value to a string

If you convert those fragments (<>...</>) to divs (<div>...</div>), you will get a somewhat more useful error:

Children of <Router> must have a path or default prop, or be a <Redirect>

@ryanflorence,

It might be worth considering an "all or nothing" approach to requiring a path attribute on children--i.e., for a given node, allow no child or all children to have a path, but don't allow a mixture of children with and without a path.

At the very least, in the case of a React fragment, it would be better to have the "must have a path" error instead of the "cannot convert a Symbol" one.

closed time in 5 months

devuxer

issue commentreach/router

Children without paths

#289 fixed this

devuxer

comment created time in 5 months

issue commentreach/router

Params not Propagated to Nested Routes with default prop

default is for the no match case, so it shouldn't have a path in the first place.

Though it would be nice to pass a nested default path the parameters of its parent, it's an unusual case (the parent would normally display that).

If you absolute need it, you can put it on context and read it in the default route, but in the future we'll have useParams() and we'll account for this.

Also happy to merge a PR that figures out how to get the params to a nested default route, here's a failing test:

  it("passes params in nested default components", () => {
    const NotFound = ({ groupId }) => {
      return <div>Not Found but still has {groupId}</div>;
    };
    snapshot({
      pathname: `/groups/book-club/matches-nothing-else`,
      element: (
        <Router>
          <Group path="groups/:groupId">
            <Reports path="reports" />
            <NotFound default />
          </Group>
        </Router>
      )
    });
  });
recipher

comment created time in 5 months

issue closedreach/router

Ranks Comments Err

https://github.com/reach/router/blob/4cd2e31297f2293ba701a6f1146fb55a29a26c26/src/lib/utils.js#L218

https://github.com/reach/router/blob/4cd2e31297f2293ba701a6f1146fb55a29a26c26/src/lib/utils.js#L221

https://github.com/reach/router/blob/4cd2e31297f2293ba701a6f1146fb55a29a26c26/src/lib/utils.js#L222

https://github.com/reach/router/blob/4cd2e31297f2293ba701a6f1146fb55a29a26c26/src/lib/utils.js#L233

https://github.com/reach/router/blob/4cd2e31297f2293ba701a6f1146fb55a29a26c26/src/lib/utils.js#L235

According to code, the rank should be

static > dynamic > root > splat

rather than

https://github.com/reach/router/blob/4cd2e31297f2293ba701a6f1146fb55a29a26c26/src/lib/utils.js#L16

image

As the picture shows.

closed time in 5 months

FengShangWuQi

issue commentreach/router

Ranks Comments Err

The tie breaker is index

https://github.com/reach/router/blob/4cd2e31297f2293ba701a6f1146fb55a29a26c26/src/lib/utils.js#L247

Those two paths are a tie.

FengShangWuQi

comment created time in 5 months

issue commentreach/router

Can't use wildcard with redirect

Will happily merge a PR for this

Hypnosphi

comment created time in 5 months

issue commentreach/router

Wildcard doesn't include query parameters

Will happily merge a PR for this.

Hypnosphi

comment created time in 5 months

issue closedreach/router

Link with query param is not marked as active

Think you found a bug?

Codesandbox example: https://hhylx.csb.dev/ Source: https://codesandbox.io/s/reach-router-starter-v1-hhylx

The link "Home" is expected to become bold when it is active, but it does not, because is has a query-parameter in to prop, but the library considers only location.pathname.

The expected behavior, that the "Home" link is getting highlighted as well as "Dashboard".

closed time in 5 months

just-boris

issue commentreach/router

Link with query param is not marked as active

It's not super convenient, but location gets passed to getProps, so you can do whatever matching you need to there on location.pathname and location.search.

We're going to be making query params 1st class in React Router v6.

just-boris

comment created time in 5 months

issue closedreach/router

Nested router not working in npm package

[bug]

I have a nested route/nested app with following code

packages/subpage/src/index.js

import React from 'react';
import { Router, Link } from '@reach/router';

const Home = () => <h2>Subpage Home</h2>;
const Sub1 = () => <h2>Sub1</h2>;
const Sub2 = () => <h2>Sub2</h2>;

const Subpage = () => {
  return (
    <div>
      <h1>Subpage</h1>
      <nav>
        <Link to="./">Home</Link> - <Link to="sub1">Sub1</Link> -{' '}
        <Link to="sub2">Sub2</Link>
      </nav>
      <Router>
        <Home path="/" />
        <Sub1 path="sub1" />
        <Sub2 path="sub2" />
      </Router>
    </div>
  );
};

export default Subpage;

and a main app using the Subpage from a monorepo

main/src/index.js

import Subpage from 'subpage'

<Router>
    <Dashboard path="/" />
    <Subpage path="subpage/*" />
</Router>

Problem

if Subpage is created as a file in main app, (./subpage.js), everything is working as expected

if Subpage is imported as a package (monorepo/npm) and main file is pointing to the same file, clicking route in subpageB will have a wrong URL (if you click "Sub2", URL should be '/subpage/sub2', but it is '/sub2' instead)

Expected: clicking "Sub2" should have url of '/subpage/sub2', Actual: clicking "Sub2" have url of '/sub2'

Note: if I copy content of subpage/dist/index.js and create another file in main app and paste there, it is working as expected as well

closed time in 5 months

huchenme

issue closedreach/router

Router prevents some layouts from scrolling on spacebar press

Hello!

I think I've found a bug related to scrolling on a page layout that uses flexbox to create a sticky header.


Reproduction steps:

  1. Visit: https://codesandbox.io/s/23ryykp4pn
  2. Notice that pressing spacebar does not scroll the Lorem Ipsum text. (You may want to "Open in a new window.")
  3. Open up Chrome inspector and remove tabindex="0" from root router element.
  4. Notice that pressing spacebar now scrolls the text.

Confirmed in latest Chrome, Firefox, and Safari for OS X.


Hope the reproduction was helpful and thanks for the hard work on this!

closed time in 5 months

danoc

issue commentreach/router

Router prevents some layouts from scrolling on spacebar press

Next version gives programmers more control over what gets focus and won't have this issue.

Also, who knew that browsers don't scroll with spacebar on an element with tabIndex="-1"? I certainly didn't.

Please note, smug replies like @CheshireSwift's aren't really welcome around here, we're charting new territory in web accessibility here and we're gonna goof some things up. More unhelpful know-it-all comments will earn a block because we don't need to deal with that crap.

danoc

comment created time in 5 months

issue commentreach/router

Support unicode character in path

FYI: you can use encodeURIComponent on the route and the link and this will work fine.

I need to take a deeper look at what we're doing in React Router before we make any decisions here. Anytime I've started encoding/decoding URIs for people you end up with an opposite problem (usually without a workaround!).

johnking

comment created time in 5 months

push eventreach/router

Ryan Florence

commit sha f045ba8ed272053678bafe07c9431a45d0988f6a

Failing test for memory history location.search Refs #251

view details

Ryan Florence

commit sha 28a79e7fc3a3487cb3304210dc3501efb8a50eba

Add `?` to location.search in memory history Fixes #251

view details

push time in 5 months

issue closedreach/router

Memory source location.search does not have a leading question mark

Whereas in chrome and jsdom location.search always has a leading question mark.

This unit tests passes, while it should not:

  it('should have a proper search', () => {
    history.pushState(null, '', '/?asdf');
    expect(location.search).toEqual('?asdf');

    const source = createMemorySource('/');
    source.history.pushState(null, '', '/?asdf');
    expect(source.location.search).toEqual('asdf');
  })

This code causes the issue: https://github.com/reach/router/blob/4cd2e31297f2293ba701a6f1146fb55a29a26c26/src/lib/history.js#L90-L94

closed time in 5 months

0xR

push eventreach/router

Ryan Florence

commit sha 0a8af930398570d07d380ef1b6f80833501c8a29

Don’t copy everything from history.location Closes #252 Closes #264

view details

push time in 5 months

PR closed reach/router

[fix] missing own property on some android devices bug

For details, see issue #252

+12 -1

1 comment

1 changed file

xinkule

pr closed time in 5 months

issue closedreach/router

window.location.hasOwnProperty('pathname') === false

On some Android devices, some own properties in location object exist on its prototype, so when it executes here:

let getLocation = source => {
  return {
    ...source.location,
    state: source.history.state,
    key: (source.history.state && source.history.state.key) || "initial"
  };
};

which will compile to use Object.assign or it's polyfill (use Object.prototype.hasOwnProperty) to implement the spread operator, the result location will not have these properties such as 'pathname', 'host', 'search', etc. And this leads to bugs when the program continues.

Currently my best option is to modify the compiled source code in node_modules by rewriting the Object.assign, so I don't have to change any of my business logic. Although I know it's the device's bug, but I am wondering if I can make a PR to make reach-router more compatible ?

closed time in 5 months

xinkule

push eventreach/router

rubenmoya

commit sha 77fa23342aa34fcf6dc24bb59f7fb7fda7a3005a

Add displayName to Link

view details

push time in 5 months

PR merged reach/router

Add displayName to Link

This PR adds a displayName of Link to the Link component.

By reading the Forwarding Refs docs you can do both, name the function passed to forwardRef or set the displayName.

I've added the displayName because I like it being explicit, but I could change it to a named function.

Closes #291

+2 -0

0 comment

1 changed file

rubenmoya

pr closed time in 5 months

issue closedreach/router

Link appears as Anonymous in Devtools

image image

Then I hide the Context and Location, emmm...

image

So, does it need a displayName?

closed time in 5 months

YouWillBe

issue commentreach/router

Redirect has flashing behavior - possible to "synchronously" redirect?

You'll still get the error, I don't believe you'll get flashing, but I haven't tested it lately. Can you make a test case in code sandbox?

samselikoff

comment created time in 5 months

issue commentReactTraining/react-router

Fix <Route children>

Additionally, before this commit:

<Route path="/home">
  <HomePage/>
</Route>

Is identical in behavior to:

<HomePage/>

That doesn't make any sense to have as an API. We already have <Fragment> if you want wrappers that don't do anything (which of course is pointless)

mjackson

comment created time in 5 months

issue closedreach/router

Allow absolute urls (url schema != component nesting structure)

Nested components in a Router create a nested URL structure, but sometimes I'd like my nested component to use an absolute URL:

function UrlRouter() {
  return (
    <Router>
      <AppFrame
        path="/app">
        <MainPage
          path="main" 
          comment="relative path"/>
        <SignInPage
          path="/site-root/please/sign-in/"
          comment="absolute path"/>
      </AppFrame>
    </Router>
  );
}

closed time in 5 months

mnieber

issue commentreach/router

Allow absolute urls (url schema != component nesting structure)

We supported that in React Router v1-3, but there were composition tradeoffs we quit making in v4 (and also here in @reach/router).

Looks like AppFrame wraps all of your routes, so it doesn't need to be a route component at all:

function UrlRouter() {
  return (
    <AppFrame>
      <Router>
        <MainPage path="app/main" />
        <SignInPage path="site-root/please/sign-in" />
      </Router>
    </AppFrame>
  )
}

If it doesn't wrap all routes, then you can do all sorts of things to get the UI and the URLs that you want:

function SignInPage() {
  return (
    <AppFrame>
      <div>stuff</div>
    </AppFrame>
  )
}

function Register() {
  return <div/>
}

function UrlRouter() {
  return (
    <Router>
      <AppFrame path="app">
        <MainPage path="main" />
        {/* these are wrapped w/ the route + layout coupling */}
      </AppFrame>

      {/* other nested paths can also get it automatically*/}
      <AppFrame path="account">
        <Profile path="profile" />
      </AppFrame>

      {/* this can use it in its own render logic (see SignInPage) */}
      <SignInPage path="site-root/please/sign-in" />

      {/* and this doesn't use it at all */}
      <Register path="not/using/app/layout" />
    </Router>
  )
}
mnieber

comment created time in 5 months

more