Closed EmilStenstrom closed 4 days ago
Unfortunately it won't be that simple :/ This is the same problem I faced with vuetify.
While converting the templating itself could be maybe done with LLM, the biggest challenge is translating the component JS logic. And without it, the components would be much less useful, maybe even stop working.
The RedixUI is built on React. E.g.see this component .
The component above sets up React bindings (useState
, etc), and renders the template in a single step / single function call. This was the same with vuetify.
For this to be useable with Django components we'd need to do 4 things:
split the HTML template and component JS logic. So we'd have "headless" components, that don't generate the HTML themselves, but instead are bound to already-existing HTML at runtime, and manage it
if we'd want to have the UI library without react, then we'd also need to replace the react API. I did similar for vuetify, where I recreated Vue reactivity API in AlpineJS, and then in the actual component code I replaced the imports
another challenge with vuetify was that the whole setup might need to support two kinds of props-passing:
The reason for this is that some components may require inputs that are available only in runtime, like a variable holding an HTML element.
For alpine, I ported Vue's props passing and validation, as well as data emitting behavior, so that the data can still travel up and down the JS components
lastly, when I used AlpineJS, there was the upside that AlpineJS already offers a way of define components via HTML attributes, using x-data
. Or some other light HTML manipulations with x-show
, etc.
Don't know if there's something similar for React. But if not, then one would need to manage that themselves. In other words, when the page HTML is loaded, some JS script will need to go through the HTML and identify what JS components are declared, and instantiate them. And ideally this would be recursive, so that once a component is initialized, we look into its HTML, and search for nested components, so we can pass the parents data to the child components as props.
And only after all this is done, then the last step would be to generate Django components that render the HTML, whereas the HTML would initialize the runtime components once loaded in browser.
For vuetify it was about 3 weeks of part-time (if you're passionate and know the library) to get to the point where the only thing remaining is the django layer.
The cool thing about this approach tho, is that the same UI component library could then be used in other server-side framework and even other languages. E.g. one could have a Go web server and Go port that would define the same components as the Django one.
I recommend this to be built by someone who would be using this setup right away in their project(s). As there's a lot or things that can go wrong across the whole stack. So the easiest way to debug is if you just NEED the things to work.
Thanks for sharing! I'm convinced that a straight conversation isn't a great idea unless you're willing to put in all the necessary work, like you have with vuetify!
Another approach is to see the list of components as inspiration and try to build a separate set of server-backed versions of them. That wouldn't lean too heavily on the actual JSX code, but more on the fact that a set of components like that is probably a pretty good view of what components are in most demand.
For fun, here's Claude 3.5 Sonnet's best translation of the Form component above into django-components. It actually knows the django-components API rather well, likely from reading the README during training...
START CLAUDE OUTPUT:
Certainly! I'll try to expand this component to include more features similar to the RadixUI version. This will involve creating multiple interconnected components and adding more advanced functionality. Keep in mind that this is still a simplified version and may not capture all the nuances of the RadixUI component, but it should give you a more comprehensive starting point.
Here's an expanded version:
from django_components import component from django import forms from django.core.exceptions import ValidationError from django.utils.safestring import mark_safe import json # Custom form field for enhanced validation class EnhancedCharField(forms.CharField): def __init__(self, *args, **kwargs): self.custom_validators = kwargs.pop('custom_validators', []) super().__init__(*args, **kwargs) def validate(self, value): super().validate(value) for validator in self.custom_validators: if not validator(value): raise ValidationError(f"Custom validation failed: {validator.__name__}") # Main Form Component @component.register("form") class FormComponent(component.Component): template_name = "components/form.html" class Form(forms.Form): name = EnhancedCharField( max_length=100, custom_validators=[lambda x: len(x) > 3] ) email = forms.EmailField() def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form'] = self.Form() return context def post(self, request, *args, **kwargs): form = self.Form(request.POST) if form.is_valid(): # Process the valid form data pass return self.render_to_response({'form': form}) class Media: css = { 'all': ('css/form.css',) } js = ('js/form.js',) # Form Field Component @component.register("form_field") class FormFieldComponent(component.Component): template_name = "components/form_field.html" def get_context_data(self, field, **kwargs): return { 'field': field, 'field_id': f"id_{field.name}", 'label_id': f"label_{field.name}", 'error_id': f"error_{field.name}", } # Form Label Component @component.register("form_label") class FormLabelComponent(component.Component): template_name = "components/form_label.html" def get_context_data(self, field, label_id, field_id, **kwargs): return { 'field': field, 'label_id': label_id, 'field_id': field_id, } # Form Control Component @component.register("form_control") class FormControlComponent(component.Component): template_name = "components/form_control.html" def get_context_data(self, field, field_id, error_id, **kwargs): return { 'field': field, 'field_id': field_id, 'error_id': error_id, } # Form Message Component @component.register("form_message") class FormMessageComponent(component.Component): template_name = "components/form_message.html" def get_context_data(self, field, error_id, **kwargs): return { 'field': field, 'error_id': error_id, } # Form Submit Component @component.register("form_submit") class FormSubmitComponent(component.Component): template_name = "components/form_submit.html" # Template files # components/form.html {% load django_components %} <form method="post" id="dynamic-form"> {% csrf_token %} {% for field in form %} {% component "form_field" field=field %} {% endfor %} {% component "form_submit" %} </form> # components/form_field.html {% load django_components %} <div class="form-field" data-field-name="{{ field.name }}"> {% component "form_label" field=field label_id=label_id field_id=field_id %} {% component "form_control" field=field field_id=field_id error_id=error_id %} {% component "form_message" field=field error_id=error_id %} </div> # components/form_label.html <label id="{{ label_id }}" for="{{ field_id }}"> {{ field.label }} </label> # components/form_control.html {{ field }} # components/form_message.html <div id="{{ error_id }}" class="error-message" style="display: none;"></div> # components/form_submit.html <button type="submit">Submit</button> # Static files # static/js/form.js document.addEventListener('DOMContentLoaded', function() { const form = document.getElementById('dynamic-form'); form.addEventListener('submit', function(e) { e.preventDefault(); validateForm(); }); function validateForm() { const formData = new FormData(form); fetch(form.action, { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest', } }) .then(response => response.json()) .then(data => { if (data.valid) { form.submit(); } else { displayErrors(data.errors); } }); } function displayErrors(errors) { for (const [fieldName, fieldErrors] of Object.entries(errors)) { const errorElement = document.getElementById(`error_${fieldName}`); if (errorElement) { errorElement.textContent = fieldErrors.join(', '); errorElement.style.display = 'block'; } } } }); # static/css/form.css .form-field { margin-bottom: 1rem; } .error-message { color: red; font-size: 0.8rem; }
This expanded version includes:
- A main
FormComponent
that handles the overall form structure and submission.- Separate components for form fields, labels, controls, and error messages.
- An
EnhancedCharField
that allows for custom validation.- Client-side form submission and validation using JavaScript.
- Basic CSS for styling.
To use this in your Django project:
- Ensure you have django-components installed and configured.
- Place the component classes in an appropriate Python file (e.g.,
components.py
).- Create the HTML templates in your templates directory.
- Place the JS and CSS files in your static files directory.
- In your Django template, you can now use:
{% load django_components %} {% component "form" %}
This expanded version provides more flexibility and closer alignment with the structure of the RadixUI component. However, it's still not a complete replication and lacks some of the more advanced features. To fully replicate the RadixUI component's functionality, you would need to implement:
- More advanced state management on the client-side.
- More sophisticated accessibility features.
- Support for nested form fields.
- A more comprehensive system for custom validators and error messages.
Remember that Django's server-side rendering model means that some features of the RadixUI component (which is designed for client-side rendering) may not be directly translatable or may require significant client-side JavaScript to implement.
Ha, nice one!
Thanks for sharing! I'm convinced that a straight conversation isn't a great idea unless you're willing to put in all the necessary work, like you have with vuetify!
Another approach is to see the list of components as inspiration and try to build a separate set of server-backed versions > of them. That wouldn't lean too heavily on the actual JSX code, but more on the fact that a set of components like that is probably a pretty good view of what components are in most demand.
Yeah, totally can serve as inspo :)
RadixUI is a great design system built up with fully designed JSX-components that can be composed into a full interface. It would we very nice to have a set of components that would compose like they do, and have a full system behind it like RadixUI does.
Idea: I wonder if we could use an LLM to convert all of RadixUI:s components from JSX to django-components?