Description
Discussed in #921
Originally posted by AhmedSeraje January 21, 2025
A functionality such as the preview component in django-viewcomponent (https://testdriven.io/blog/django-reusable-components/#previewing-components) would be very welcomed :) Will easily help in speeding up the development
@burakyilmaz321 replied:
I had done a PoC study about this burakyilmaz321/django-components-storybook. I haven't developed it for a long time, but it's possible to integrate Storybook with a Django component.
@JuroOravec replied:
@burakyilmaz321 Oh now this slaps! 🔥
Do you have an example of how it's used in the wild?
IMO we could develop this into a full package that could live under the django-components org and make it the recommended way for previewing our components.
@burakyilmaz321 would you be interested in championing this? As you can see beow, currently I have too little practical experience with Storybook to be able to tell what the abstractions should look like.
1. Passing Storybook params to components
In the demo, I saw that components had defined
storybook_parameters
attribute.I think this would make more sense to be a static function (maybe called just
storybook
), so that it's possible to pre-process the data that's sent from Storybook's UI, before we pass it toComponent.render()
.The default implementation could look like this (assuming that the the Storybook server uses GET requests):
class Button(Component): @staticmethod def storybook(cls, request): return cls.render(kwargs=requests.GET)But since it would be a function, then you could do something like this, where in the UI you would choose an option as a plain string, but in the Python code it'd be "hydrated" to more:
STORYBOOK_PRESETS = { "primary": { ... }, "secondary": { ... }, } class Button(Component): ... @staticmethod def storybook(cls, request): preset_name = requests.GET.get("preset", "primary") kwargs = STORYBOOK_PRESETS[preset_name] return cls.render(kwargs=kwargs)2. ❌ Automatically generate inputs?
Looking at the PoC, I'm missing how things would be set up in Storybook (I haven't used it personally yet). Where and how do you declare which components you have and what parameters they accept? Do you define them manually? If manually, do you define them next to the component definition (so inside the Django codebase), or separately?
If I remember right, Storybook allows you to pass in components arguments from within the UI (they call it parameters). Our Components can have their input types declared for type hints. We could make use of this and have a CLI command that would:
- Search for all components within the Django codebase
- For each component class, generate the Storybook
*.stories.js
file. (WHERE?)- Inside the Storybook files, we'd generate the component parameters from Component's typing, so that those parameters can be set from within Storybook's UI.
‼️ ‼️ NOTE: Actually NO. I think we'd have to create Python representations for individual stories if we wanted to do it right, and that feels like going too deep into Storybook.3. ❓ Automatically generate Storybook component bindings for django components?
Instead, what makes more sense is following:
In the Storybook docs, I see that they refer to the components like so:
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite) import type { Meta } from '@storybook/your-framework'; import { Button } from './Button'; const meta: Meta<typeof Button> = { component: Button, //👇 Creates specific parameters at the component level parameters: { backgrounds: { default: 'dark', }, }, }; export default meta;I haven't checked, but I assume that the
Button
is a function that accepts the parameters and returns a rendered HTML, so like a React component.What we could do to make the integration with Storybook easier is to generate these functions. E.g. on the inside, our implementation of
Button
would make a call to server with given params:
‼️ ‼️ NOTE: ...But then again, maybe this is redundant, because Storybook already has the feature to support server-side rendered components?Looking at the implementation, it is a bit outdated - in views.py, it should be now sufficient to just call
Component.render
, asComponent.get_context_data
shouldn't be called from the outside. So the update view should be:def component_render_view(request, component_id: str): component_cls: Type[Component] = component_registry.all().get(component_id) component: Component = component_cls() try: storybook_parameters = { param: request.GET.get(param) for param in component.storybook_parameters } except AttributeError: storybook_parameters = {} rendered_component = component.render( kwargs=storybook_parameters, ) return HttpResponse( rendered_component, headers={"Access-Control-Allow-Origin": "*"}, )