-
Notifications
You must be signed in to change notification settings - Fork 6
Component Configuration Objects #8
Comments
@mattrosno thanks for setting up this issue. We've done a lot of re-factoring with this concept (it was a big part of our re-architecture we just finished). You're correct that at the end of the day there are 3 things that we care about, the element type, the classes that get applied (which technically could be an attribute), and attributes. The structure you have defined above is what really gets put in to the template but it's not very user friendly which is why we have separated our
We have multiple components that export multiple "generate" functions. Take a look at our list, checkbox, and core/table generate files for examples of how we handled this. |
@elizabethsjudd great. Each component to export zero to many generate functions, where each function takes in an options object (e.g. give me a disabled primary button with an icon), (e.g. give me an accordion item that's expanded). Regarding what the generate functions return, With that, your example would read like so: {
"root": {
"element": "button",
"classNames": "my-custom-class bx--btn bx--btn--primary",
"attributes": {
"type": "button",
"disabled": ""
}
},
"content": {
"classNames": "bx--btn__content"
},
"icon": {
"classNames": "bx--btn__icon"
}
} And carbon-components-x would take it the rest of the way. As for the icon itself... nested/composite components need to be addressed, but I'm thinking that if the button generate function knows that there's going to be an icon in the button, it sets relevant attributes or classes on button nodes Then, it's up to carbon-components-x to use the icon component spec to generate the actual icon and place it in the button template that is set up expecting there to be an icon. |
The "output" (what I've been referring to as the We considered doing each node as a key and for shorthand we removed the need for root as every component had this key but I would be fine keeping it how you have above for clarity. Also, we only provide the default "content" ONLY IF it's required for the component. That way we know that someone is not creating a button without content and allows for quicker prototyping. As far as nested components, the parent component will have to know to include it and will need to pass along properties to that sub/child component. Button won't care that the "information" icon is being used but developers will need to provide the button that it should use an icon. It's then up to button to make sure that it's children get "generated" as well. You'll see us reference a Button is kind of a difficult example of these structures as it's so simple. The portion of the PAL re-architecture for configuration/template objects really focused on consistency across components since we had done a few more complex ones at that point. If you look in our When you compare the The
|
don't forget the
|
@scottnath you're absolutely right. @mattrosno when you are looking at the typedefs in PAL, you can see that we make short hand ways to defined content. They aren't always called |
@scottnath if the spec is receiving content, and returning the exact same content, what's the point? Is it for simplicity so carbon-components-x has to merge less, and should have everything that it needs from the spec config object to then render the component? Is it for defaults, in case somebody chooses not to, or forgets to, pass in content and the default content is applicable? Is it for content modification? I can't think of any example, especially if it's user-generated content, but if we're passing content through the spec, the spec is capable of doing something to the content via generate functions in the future? Content of course is important when using a component. Just making sure that it needs to pass through the spec. |
The So my comment is in relation to the definition of the ie: If our working theory is that each c-c-frameworkx should accept the |
Still trying to catch up on spec stuff, could you help me understand why one would leverage elements/classes in this object? Initial thoughts in this space would be that component APIs encapsulate the content/domain model of the component and not accepting elements/classes as those are implementation details (that should be defined as a rule in the spec). When thinking about this in a test runner setup, I would imagine something like: /* React.js framework test file */
// Proposed API
import { run } from '@carbon/spec';
import React from 'react';
import ReactDOM from 'react-dom';
import { ComponentName } from '../entrypoint';
describe('ComponentName', () => {
let mountNode;
beforeEach(() => {
mountNode = document.createElement('div');
document.body.appendChild(mountNode);
});
afterEach(() => {
mountNode.parentNode.removeChild(mountNode);
});
it('should follow the carbon spec', async () => {
const runner = run('ComponentName');
// Similar to `mount` function, provide the node you rendered the
// component to
runner.beforeEach(descriptor => {
// descriptor provides content model for initialization
const { title, children } = descriptor;
ReactDOM.render(
<ComponentName title={title}>{mapToChildren(children)}</ComponentName>,
mountNode
);
return mountNode;
});
// Could be async if we want to parallelize tests
await runner.run();
});
}); |
I don't think that the spec repo would necessarily "leverage" these properties. It's more of a documentation of what the |
With the end goal being symmetry between the frameworks, that should include the elements because final HTML should match exactly. The elements need to be known also because they are defined within the For your example, I assume that classNames may be applied in the
regardless whether it's inside or outside a test runner, having a consistent api would mean one could add classnames: I agree that a component shouldn't receive an actual |
@elizabethsjudd Gotcha, could you share an example of how a component might be built using this in something like React or Vue?
@scottnath this feels like it might be overlapping with the frameworks here. I definitely agree that part of a component API is that you can supply a custom class string, but I think that's a developer affordance versus a specification requirement. I also don't quite understand how the generate function works with respect to calling a component. As an app developer, I would assume the following API for supplying a custom class name: <ComponentName title={title} className="custom-classname">
<ChildA />
<ChildB />
<ChildC />
</ComponentName> Is the idea that we would instead do: import { generate } from '@carbon/spec/components/name';
const props = generate({
// options
class: 'custom-class',
});
<ComponentName {...props}>
<ChildA />
<ChildB />
<ChildC />
</ComponentName> ? |
Custom class names - let's say you want to render a React button: <Button content="My Button" className="some-class" /> In the component API, you would: const button = buttonConfig('bx');
const myButton = button.generate({
disabled: true, // TODO get from props
size: 'small', // TODO get from props
variant: 'danger', // TODO get from props
content: 'My Button' // TODO get from props
}); And the spec returns this configuration object: {
"root": {
"attributes": {
"type": "button",
"disabled": ""
},
"classNames": "bx--btn bx--btn--danger bx--btn--sm",
"element": "button",
"content": "Button"
},
"content": {
"attributes": {},
"classNames": "bx--btn__content"
},
"icon": {
"attributes": {
"aria-hidden": true
},
"classNames": "bx--btn__icon"
}
} Which can get used to Either the component API takes If part of spec is verifying that component APIs support custom class names, and if providing component config objects through the spec, then ya, it makes sense to pass the custom class names through the spec. const button = buttonConfig('bx');
const myButton = button.generate({
disabled: true,
size: 'small',
variant: 'danger',
content: 'My Button',
classes: {
root: 'some-class',
icon: 'another-class',
},
}); Returning: {
"root": {
"attributes": {
"type": "button",
"disabled": ""
},
"classNames": "some-class bx--btn bx--btn--danger bx--btn--sm",
"element": "button",
"content": "My Button"
},
"content": {
"attributes": {},
"classNames": "bx--btn__content"
},
"icon": {
"attributes": {
"aria-hidden": true
},
"classNames": "another-class bx--btn__icon"
}
} |
This feels like it could be a good amount of work to maintain for the spec. Could we simplify this process so that it initially is doing verification testing? I am anxious about working with this pattern in something like DataTable because of its hierarchy. With complex components like that, it would seem like we would have to do one of the following patterns:
If we take the Even though this helper is only responsible for a subset of attributes, we do end up with differences for things like If the spec is already running rulesets that are looking for what is in a configuration object, do we need a runtime strategy to use it in frameworks or could we just defer to the framework maintainers to make the right choice for them as long as it follows the spec? |
@joshblack We've been talking about changing the definition of the POC that Matt is currently working on. I'm in agreement that there is some of this work does not belong in this repo. Scott and i were trying to get a consistent "input" terminology which could be part of the spec but how/what the framework does with that input is specific to the framework. |
Continuing discussion from #6...
Regardless of what this is called... generate... pre-generate... there's the need for the spec to output configuration objects to help each carbon-components-x build their templates. If we call it a "generate function", we pass in component options (e.g. I want a primary button), and you get back a configuration object. I believe we're really only concerned about three properties here,
element
,attributes
andclasses
.Button example:
As carbon-components-x, I want to render a primary button. Spec returns:
element
andattributes
important for a11y,classes
important for styling. Everything else can be left to each carbon-components-x.Button example (as an anchor):
As carbon-components-x, I want to render a primary button that is an anchor tag. Spec returns:
root.element
androot.attributes.role
are updated accordingly, and as long as carbon-components-x properly implements the configuration object from the spec, the button remains accessible as an anchor.Multiple generate functions
Where button may only need one generate function, other components might need many. Take accordion for example. The primary
generate
function would tell the component that it needs to be an unordered list with certain classes, but we'd also want agenerateItem
function for each accordion item to render.Accordion item (open) example:
As carbon-components-x, I want to render an accordion item that's open. Spec returns:
Zero to many generate functions that return configuration objects. Those config objects only contain
element
,attributes
andclasses
for their relevant nodes, and are just enough to inform each carbon-component-x to accessibly render templates, without being over-prescriptive.The text was updated successfully, but these errors were encountered: