I've been hard at work moving kremling over to a monorepo (i'll write another issue here about that soon), and it's really close! the last thing left is figuring out what to do with the classname utilities: always() maybe() toggle()
Background
This utility uses a string object instead of a string primitive so we can pass values around and make it chainable. It works, albeit there’s some weird caveats like sometimes requiring toString() if you’re using prop types, or if you’re working in other libraries like solidjs, this solution just flat out fails.
Also, typing it with typescript is a major headache - i’ve spent too much time arguing with chatgpt about the best approach to solving this... tldr: we’d have to assert the type every time we use it, or we use toString() at the end of every implementation 🤮
option 1 - leave it be
we keep it as-is and don’t convert it to typescript - also keep the caveats I mentioned above ^ - this works because the type definitions that were manually written for it extend the output of each method with & String which satisfies react’s className type
option 2 - proposal:
we rewrite it:
className={a('one')}
className={m(condition, 'two')}
className={t(condition, 'three', 'four')}
this looks very similar to the old way… except when we compose them together - instead of chaining, we pipe them in with always:
className={a('one', m(condition, 'two'), t(condition, 'three', 'four'))}
the always method is crucial because it allows infinite string arguments: a(...strArgs: string[]): string joining all the strings together leaving a simple string primitive!
benefits:
- output for all methods is always a string primitive, you’ll never need to
toString()
- no chaining
- can trivially write and test in typescript
- makes splitting out lots of utility classes readable
- only import the methods you need - if you never use
t(), it’ll be tree-shaken out
downsides:
- you don’t automatically get all methods by importing one of them… each method will need to be imported like rxjs’s pipe methods…
- using 2 or more methods without the need of
always still requires always to combine them: a(m(), m()) (although i would argue that not using a() is an edge case in this scenario)
BONUS QUESTION:
do we keep classname helpers in kremling? is this useful enough to pull out and make it's own library?
I've been hard at work moving kremling over to a monorepo (i'll write another issue here about that soon), and it's really close! the last thing left is figuring out what to do with the classname utilities:
always() maybe() toggle()Background
This utility uses a string object instead of a string primitive so we can pass values around and make it chainable. It works, albeit there’s some weird caveats like sometimes requiring
toString()if you’re using prop types, or if you’re working in other libraries like solidjs, this solution just flat out fails.Also, typing it with typescript is a major headache - i’ve spent too much time arguing with chatgpt about the best approach to solving this... tldr: we’d have to assert the type every time we use it, or we use
toString()at the end of every implementation 🤮option 1 - leave it be
we keep it as-is and don’t convert it to typescript - also keep the caveats I mentioned above ^ - this works because the type definitions that were manually written for it extend the output of each method with
& Stringwhich satisfies react’s className typeoption 2 - proposal:
we rewrite it:
this looks very similar to the old way… except when we compose them together - instead of chaining, we pipe them in with
always:the
alwaysmethod is crucial because it allows infinite string arguments:a(...strArgs: string[]): stringjoining all the strings together leaving a simple string primitive!benefits:
toString()t(), it’ll be tree-shaken outdownsides:
alwaysstill requiresalwaysto combine them:a(m(), m())(although i would argue that not usinga()is an edge case in this scenario)BONUS QUESTION:
do we keep classname helpers in kremling? is this useful enough to pull out and make it's own library?