Open jjenzz opened 10 months ago
i've done a PoC for an API like this instead:
// same as before
const button = css({ '--padding': 4 });
<button style={button(null, { '--color': 'red' })} />
// html output:
// <button style="--padding:4;--color:red" />
//
// css output:
// [style*="--color:"] {
// color: var(--color);
// }
// [style*="--padding:"] {
// padding: calc(var(--grid)* var(--padding));
// }
// with auto-generated class
const button = css.compose({ '--padding': 4 });
<button className={button.class()} style={button(null, { '--color': 'red' })} />
// html output:
// <button class="tk2343" style="--color:red" />
// css output:
// [style*="--color:"] {
// color: var(--color);
// }
// .tk2343 {
// padding: calc(var(--grid)* var(--padding));
// }
// .tk2343 {
// --padding: 4;
// }
my only concern is passing through a class name to button.class
e.g. button.class(props.className)
. in this case, the class passed might be first in the stylesheet so the button styles would override.
for this, the css
utility can check if the props.className
was generated by foo.class()
(symbol) and if so, it would keep foo's base styles in the style attribute so they would override as expected. that way, styles would be in stylesheet except for overrides where they'd always be inline.
further work on this led me to an API as follows:
const styles = css.compose({
button: {
'--all': 'unset',
variants: {
size: {
small: { '--padding': 2 },
medium: { '--padding': 4 },
}
},
},
buttonIcon: {
'--color': 'var(--color_sky-500)',
},
});
the reason for the compound parts approach within one styles
object was to allow the class name to be generated from the css.compose
keys instead of the styles within the object. generating the class based on the styles would mean busting HTML cache every time the styles changed which defeats the point of exploring this solution.
the keyed class name approach solves the HTML cache busting, however, the whole class name idea in general would prevent something like the following from working because tokenami statically generates styles and cannot execute your code:
const styles = css.compose({
button: {
...sharedStyles,
},
});
this works currently because values are added in the style attribute, and tokenami will generate the correct atomic properties for sharedStyles
when it finds the sharedStyles
declaration in another file.
the class name approach breaks this because we need to move the values to the stylesheet (see below) but the static extraction cannot determine what the values are.
.button {
--all: unset;
}
.button-icon {
--color: var(--color_sky-500);
}
this same problem exists with responsiveVariants
currently 🙈 so, i need to think some more. do we break spreading styles and provide a lint rule... or try to avoid this problem.
thinking about providing an extend
api like SASS. any spreading within compose cld be flagged by ts plugin semantic diagnostics.
// button.tsx
const styles = css.compose({
button: {
'--bg-color': 'var(--color_sky-200)',
'--outline': 'none',
variants: {
size: {
small: { '--padding': 2 },
medium: { '--padding': 4 },
}
},
},
});
// link.tsx
import { styles as buttonStyles } from './button';
const styles = css.compose({
link: {
extend: [buttonStyles.button],
'--text-decoration': 'none',
},
});
css.compose({})
it will chain the classes css({})
it will inline the properties in style
attrgetSuggestionDiagnostics
) when compose contains a key to use as a class name that has already been declared elsewhere (no need for unique hashed classnames)tokenami.config
can allow a composeClassPrefix
so .tk-button
can become .acme-button
const Link = ({ size, ...props }) => {
// call with variants bcso they will adjust styles and classes in future
const [cn, style] = styles.link({ size });
// merge returned class/style functions with props
return <a className={cn(props.className)} style={style(props.style)} />;
// or don't
return <a className={cn} style={style} />;
};
first iteration output will look as follows (i'll work on variant classes later)
.tk-button, .tk-link {
--background-color: var(--color_sky-200);
}
/* keep property/value pairs atomic. lightningcss wld collapse this example
* but the more styles grow, the more atomic it'd become */
.tk-button, .tk-link {
--outline: none;
}
.tk-link {
--text-decoration: none;
}
the following code examples are old, see more recent comments for proposed changes.
components imply reuse which means we can end up with the same style rules repeated multiple times in our markup each time the component is consumed. this is where classes are useful bcos you repeat a small class string instead and the styles are defined once in the stylesheet (not repeated).
the difference in size could be negligible with compression however, a component's base styles rarely change and so moving them into the stylesheet allows them to be heavily cached.
the
css
utility would be needed for this but i'd ideally like to keep the ability to prototype quickly inline with it. some thoughts on an API are an optionalapply()
call added to thecss
utility:this would generate something like the following in the stylesheet:
with this:
Variants and overrides would be passed as usual and would end up in the style attribute due to their dynamic nature (variants change via props, overrides can be different each time a component is consumed):
i might be able to
@apply
the variants in stylesheet also eventually e.g. generate an.acme-button--size-small
class, but that can be a later iteration.Concerns
there have been some concerns around tailwind's desire to deprecate
@apply
due to issues it has caused for them but i don't believe similar issues are as likely (if at all) with the tokenami approach. i recreated an example Adam provided using the tokenami approach and it is not an issue there: https://codepen.io/jjenzz/pen/WNmwBQK.