@fluido/ui-containers
Version:
A framework for create front-end easy and fast
482 lines (345 loc) • 11.1 kB
Markdown
# Writing styles should be Fluid
Writing style rules for your custom components should be enjoyable. Doesn't matter if you're an experienced front-end developer or not.
If you already used styled-components (or goober, or emotion, or similar) you should be able to write the styling for components using the same syntax as you're already accustomed. Just go ahead and try it.
But if you are more used to the tsx world, prefers a less verbose syntax, or just don't like the CSS-in-JS syntaxes, there's another way: the new syntax created by the team behind Fluido UI, called FSS (or Fluid Style Sheet).
Keep reading if you want to know more about FSS, how it compares to styled-components and how to use it in your components.
Let's get started!
## Styling based on a prop
The most basic use, putting a dynamic value in a style rule (aka: using variables).
### styled-components tsx
Even in this very basic use case, we found that tsx's syntax is a bit verbose.
1. First you have to tell the template string that this is a dynamic value using `${...}`,
2. Create a function `() =>`,
3. Pass `(props)` as a parameter inside the function or _optionally_ destructure individual props `({ var1, var2 })`,
4. Only to finally pass the variable `var` or `props.var`.
```tsx
const Component = styled('div')`
padding: ${({ p }) => p};
`
/* or */
const Component = styled('div')`
padding: ${(props) => props.p};
`
```
### FSS
Using the FSS's approach you just pass the variable with a dolar sign `$` in front of it, like you would using sass. That's it.
```tsx
const Component = styled('div')`
padding: $p;
`
```
---
## Styling a prop using logical operators
When you want to style something if one or more conditions are met.
For example, do something if some property(ies) is(are) present.
Javascript is pretty straightforward with this, just use the logical operators and (`&&`), or (`||`), equal(`==`), _et cetera_.
The problem here is that it is even more verbose. You have to type every prop twice or `props.prop` a lot!
### Logical AND
If _all_ of the previous values are true, use the **last**.
```tsx
let varA = 10 > 5 // should be true
let varB = true
let varC = 'Whiskey in the Jar'
```
Consider the fictional example:
```
<!-- The code bellow **IS NOT** a real syntax -->
varA [AND] varB [AND] varC
expected output: "Whiskey in the Jar"
```
Now compare the following real syntax differences.
#### styled-components tsx
```tsx
const Component = styled('div')`
user-select: ${({ preventSelection }) => preventSelection && 'none'};
`
/* or */
const Component = styled('div')`
user-select: ${(props) => props.preventSelection && 'none'};
`
// checking multiple props
const Component = styled('div')`
padding-inline: ${({ isFeatured, outlined }) =>
isFeatured && outlined && '2rem'};
`
/* or */
const Component = styled('div')`
padding-inline: ${(props) => props.isFeatured && props.outlined && '2rem'};
`
```
#### FSS
In FSS we use the logical operators as special functions, passing comma separated values.
```tsx
const Component = styled('div')`
user-select: #and($preventSelection, none);
`
// checking multiple props
const Component = styled('div')`
padding-inline: #and($isFeatured, $outlined, 2rem);
```
#### Using the component
Here's how it should work
Example 1:
```tsx
// Since there's no `preventSelection` prop, it evaluates as `false`.
// The `user-select: none` wont be used
<Text>I'm a selectable piece of text</Text>
```
Example 2:
```tsx
// `preventSelection` was used, so it's `true`.
// The `user-select: none` will be applied.
<Text preventSelection>You won't be able to select me</Text>
```
Example 3:
```tsx
// Has the `isFeatured` prop, but not the `outlined` prop.
// The `padding-inline: 2rem;` will NOT be applied.
<Button isFeatured>My padding should be the default</Button>
```
Example 4:
```tsx
// Has both `isFeatured` and `outlined` props.
// The `padding-inline: 2rem;` will be applied.
<Button isFeatured outlined>
My padding should be the default
</Button>
```
> You can read it like this:
> If it has isFeatured prop, proceed, if not, fail;
> If it has outlined prop, proceed, if not, fail;
> If all of the above are true, use 2rem.
### Logical OR
Use the **first** if it's true, if not, use the _next one_…
```tsx
let varA = 5 > 10 // should be false
let varB = 'Gimme fuel, gimme fire…'
let varC = 'Gimme that which I desire!'
```
Consider the fictional example:
```
<!-- The code bellow **IS NOT** a real syntax -->
varA [OR] varB [OR] varC
expected output: "Gimme fuel, gimme fire…"
```
Now compare the following real syntax differences.
#### styled-components tsx
```tsx
const Component = styled('div')`
border-color: ${({ borderColor }) => borderColor || 'currentColor'};
`
/* or */
const Component = styled('div')`
border-color: ${(props) => props.borderColor || 'currentColor'};
`
// checking multiple props
const Component = styled('div')`
border-color: ${({ borderColor, theme }) =>
borderColor || theme.borderColor || 'currentColor'};
`
/* or */
const Component = styled('div')`
border-color: ${(props) =>
props.borderColor || props.theme.borderColor || 'currentColor'};
`
```
#### FSS
In FSS we use the logical operators as special functions, passing comma separated values.
```tsx
const Component = styled('div')`
border-color: #or($borderColor, currentColor);
`
// checking multiple props
const Component = styled('div')`
border-color: #or($borderColor, $theme.borderColor, currentColor);
`
```
#### Using the component
Here's how it should work
Example 1:
```tsx
// Since neither `borderColor` or `theme` props are present, use `currentColor`.
// The `border-color: currentColor;` will be applied.
<Box border color='red'>
My border will have the same color as the text (which is red).
</Box>
```
Example 2:
```tsx
// Since `borderColor` isn't present the `theme` is applied, never reaches `currentColor`.
// The `border-color: var(--theme-border-color);` will be applied.
<Box border theme={midnightTheme}>
Using the theme's border color!
</Box>
```
Example 3:
```tsx
// `borderColor` is present, so it never reaches the `theme`.
// The `border-color: blue;` will be applied.
<Box border borderColor='blue' theme={midnightTheme}>
I'll have a blue border, no matter what the theme is.
</Box>
```
> You can read it like this:
> if it has borderColor, use it;
> if not, if it has theme.borderColor, use it;
> if none of the above, use currentColor.
---
## Conditionally styling multiple rules based on a prop
### styled-components tsx
```tsx
const Component = styled('div')`
${({ border, borderColor, borderStyle }) =>
border &&
`
border-color: ${borderColor || 'currentColor'};
border-style: ${borderStyle || 'solid'};
border-width: ${border}`}
`
/* or */
const Component = styled('div')`
${({ border, borderColor, borderStyle }) =>
border && {
borderColor: borderColor || 'currentColor',
borderStyle: borderStyle || 'solid',
borderWidth: border,
}}
`
```
### FSS
```tsx
const Component = styled('div')`
#if($border) {
border-color: #or($borderColor, currentColor);
border-style: #or($borderStyle, solid);
border-width: $border;
}
`
```
---
## Combining multiple props into a single rule
### styled-components tsx
```tsx
const Component = `${({ hue, saturation, lightness, colorOpacity }) => `
color: hsla(${hue}, ${saturation}, ${lightness}, ${colorOpacity});
`}`
```
### FSS
```tsx
const Component = `
color: hsla($hue, $saturation, $lightness, $colorOpacity);
`
```
---
## Two or more conditions
### styled-components tsx
```tsx
const Component = styled('div')`
${({ allowOverflow, preserveRatio }) =>
(allowOverflow || preserveRatio) && `position: absolute`}
`
```
### FSS
```tsx
const Component = `
#if($allowOverflow || $preserveRatio) {
position: absolute;
}
`
```
---
## Dynamic selector
```ts
// stripes: 'even' | 'odd' | undefined
```
### styled-components tsx
```tsx
const Component = styled('div')`
${({ stripes }) =>
stripes &&
`
& > :nth-of-type(${stripes}) {
background-color: #eee;
}`}
`
```
### FSS
```tsx
#if($stripes) {
& > :nth-of-type($stripes) {
background-color: #eee;
}
}
```
### styled-components tsx
```tsx
const Component = styled('div')`
& > :nth-last-child(n + ${({ limit }) => limit && limit + 1}),
& > :nth-last-child(n + ${({ limit }) => limit && limit + 1}) ~ \* {
flex-basis: 100%;
}
`
```
### FSS
```tsx
const Component = styled('div')`
& > :nth-last-child(n + #math($limit + 1)),
& > :nth-last-child(n + #math($limit + 1)) ~ \* {
flex-basis: 100%;
}
`
```
## Running functions
### styled-components tsx
Using tsx syntax, you can pass functions directly inside the style rules, this is a _really_ nice behavior.
Although it can lead to long, hard to read, rules or selectors, like the example bellow:
1. First import the function
```tsx
import { makeNthChildSelector } from 'ui-utilities'
```
2. Then use it inside the style declaration
```tsx
const Component = styled('div')`
& > :is(${({ fill }) => (fill && makeNthChildSelector(fill)) || ''}) {
flex: 1;
}
`
```
### FSS
Using FSS is a little different, you'll need an extra step in favor of legibility.
1. First import the function
```tsx
import { makeNthChildSelector } from 'ui-utilities'
```
2. Then you can pass it into a functions object as a second argument to the styled function
```tsx
const Component = styled('div', { functions: { makeNthChildSelector } })``
```
3. You can then use it in your template using with the `#func` keyword
```tsx
const Component = styled('div', { functions: { makeNthChildSelector } })`
& > :is(#func(makeNthChildSelector, $fill)) {
flex: 1;
}
`
```
// or with types
const selector = <T extends Object = {}>({ fill }: T) =>
(fill && makeNthChildSelector(fill)) || ''
/_ or with generic props _/
const generic = (propName) => (props) =>
(props[propName] && makeNthChildSelector(props[propName])) || ''
// with types
const generic =
<T extends Object = {}>(propName: keyof T) =>
(props: T) =>
(props[propName] && makeNthChildSelector(props[propName])) || ''
// define selector function
const selector = generic('fill')
// with types
const selector = generic<Props>('fill')
```
> Notice that javascript variables must be expressed using dolar sign `$` +
> curly braces `{}`. Here\'s a comparison — regular variable\: `$var`\;
> javascript variable\: `${var}`.
```