react-spectrum icon indicating copy to clipboard operation
react-spectrum copied to clipboard

Provide a way to control UI states

Open unional opened this issue 1 year ago โ€ข 4 comments

Provide a general summary of the feature here

When creating component documentation, e.g. using storybook, I want to create a story that show the component in various states.

For example, creating a story for a button, and showing how it looks like normally, when pressed, when hover, when focused, etc.

I couldn't do that write now because using css selector doesn't change the state object in:

<Button className={(state) => ...}/>

And the ButtonContext doesn't seem to work with UI states:

<ButtonContext.Provider value={{ isPressed: true, isDisabled: true }}>
  <Button
    className={(state) => {
      console.log('state', state)
      return ''
    }}
  >
    Click me
  </Button>
</ButtonContext.Provider>

It prints isDisabled: true but isPressed: false. Also it does not have other states (like isFocusVisible).

I can do it if I break it down to one story per component per state, and use user.event() to cause the state change.

But the number of stories will explode and it is very hard to navigate.

๐Ÿค” Expected Behavior?

Have a way to do that.

๐Ÿ˜ฏ Current Behavior

No good way to do that statically.

๐Ÿ’ Possible Solution

Maybe provide a <UIStateContext.Provider/>

๐Ÿ”ฆ Context

Creating a nice documentation

๐Ÿ’ป Examples

No response

๐Ÿงข Your Company/Team

Palo Alto Networks

๐Ÿ•ท Tracking Issue

No response

unional avatar May 17 '24 05:05 unional

Duplicate of https://github.com/adobe/react-spectrum/issues/6198

snowystinger avatar May 17 '24 06:05 snowystinger

Argh, thanks. I tried to search for isPressed and focusVisible so couldn't find that issue.

btw I just think of a better alternative. The key is to extract the className handler out and apply it early in the story:


export function MyButton(props: MyButtonProps) {
  // ...
  return <Button className={state => getMyButtonClassName(props, state)} .../>
}

export function getMyButtonClassName(props, state) { ... }

// my_button.stories.tsx

export const Showcase = {
  render() {
    return <MyButton className={getMyButtonClassName({ ... }, { isPressed: true })} ... />
  }
}

unional avatar May 17 '24 07:05 unional

Ah, that's a great approach. Thanks for sharing! And no worries. I'm in here all the time, much easier for me to find a duplicate.

snowystinger avatar May 17 '24 07:05 snowystinger

Amend to the solution I have, some mocking function is needed to promote the mocked state classes to regular classes.

For example using Tailwind, hover:... needs to be promoted to ... and use tailwind-merge to enforce class order.

As I looked into these states, it seems like the <Link> component is missing the isVisited state, and isTargeted state is missing from components in general.

Reopening this for now to address the issues.

unional avatar May 17 '24 17:05 unional

I noticed that my approach doesn't work with pseudo classes like :active and :hover.

Setting the data attribute data-hovered doesn't work either.

In comparison, it works with @headlessui/react.

codesandbox

it will be a huge save if we can get the same support here.

unional avatar Aug 15 '24 21:08 unional

Actually, I think it is a bug: codesandbox.

I'll file an issue.

unional avatar Aug 15 '24 22:08 unional