tajo / ladle

🥄 Develop, test and document your React story components faster.
https://www.ladle.dev
MIT License
2.63k stars 93 forks source link

Type-safe object args #434

Closed twiddler closed 1 year ago

twiddler commented 1 year ago

Is your feature request related to a problem? Please describe. I am using ladle with TypeScript. Some of my components expect objects to be passed in. AFAIK, ladle controls are flat, i.e. to control object args I must

interface FooProps {
    foo: {
        bar: boolean
    }
}

const FooTemplate = (props: FooProps) => …

const FooStory: Story<Record<string, any>> = (props) => (
    <FooTemplate
        foo={ bar: props['foo.bar'] }
    />
)

export const myStory = FooStory.bind({})

myVariant.args = {
    'foo.bar': true,
}

However, I lose type-safety this way.

Describe the solution you'd like

const FooStory = (props) => <FooTemplate { ...props } />

export const myStory = FooStory.bind({})

myVariant.args = {
    'foo': {
        'bar': true,
    }
}

Describe alternatives you've considered To not lose type-safety, I flatten the parameters of the component I want to control. In the story, I deflatten the individual parameters into objects again.

We might want to include a helper like Flatten in ladle itself.

import type { Story } from '@ladle/react'
import { FooTemplate } from './Foo'

// Credits to Joe "jcalz" Calzaretta (https://stackoverflow.com/users/2887218) for this type. Source: https://stackoverflow.com/a/66620803
export type Flatten<T extends object> = object extends T
    ? object
    : {
          [K in keyof T]-?: (
              x: NonNullable<T[K]> extends infer V
                  ? V extends object
                      ? V extends readonly any[]
                          ? Pick<T, K>
                          : Flatten<V> extends infer FV
                          ? {
                                [P in keyof FV as `${Extract<
                                    K,
                                    string | number
                                >}.${Extract<P, string | number>}`]: FV[P]
                            }
                          : never
                      : Pick<T, K>
                  : never
          ) => void
      } extends Record<keyof T, (y: infer O) => void>
    ? O extends infer _
        ? { [K in keyof O]: O[K] }
        : never
    : never

// ladle doesn't support nested objects, so we have to flatten those parts we want to control in the UI.
type FooStoryParameters = Flatten<Parameters<typeof FooTemplate>[0]>

const FooStory: Story<FooStoryParameters> = (props) => {
    return (
            <FooTemplate
                foo={{
                    bar: props['state.bar'],
                }}
            />
    )
}

export const myStory = FooStory.bind({})

myVariant.args = {
    'foo.bar': true,
}
tajo commented 1 year ago

This is not something that exists in Storybook either, right?

Is the ask to add Flatten to Ladle so you can import it from there or some other changes as well?

twiddler commented 1 year ago

I don't know. 🤷

I'm very much open to change my workflow if someone has some advice on how they deal with such situations.

tajo commented 1 year ago

Not really sure what to do here and there is a user land solution anyway.