Skip to content

Commit

Permalink
feat(Theming): add VisibilityByTheme as a shared component (#2280)
Browse files Browse the repository at this point in the history
Co-authored-by: Anders <[email protected]>
  • Loading branch information
tujoworker and langz committed May 31, 2023
1 parent 02656a7 commit 2592658
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ title: 'Sbanken Development'

## Write dedicated documentation

If you need to show some documentation only for when the Sbanken theme is choose, you can do so:
If you need to show some documentation only for when the Sbanken theme is selected, you can do so:

```md
<FilterByTheme name="sbanken">
<VisibilityByTheme visible="sbanken">

## Sbanken examples

Text

<SpecialExample />

</FilterByTheme>
</VisibilityByTheme>
```

More details in the [Theme docs](/uilib/usage/customisation/theming).
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ The documentation is written in enhanced Markdown, called MDX. It allows us to i

## Handling themes

If you need to show some documentation only for when a certain theme is choose, you can do so:
If you need to show some documentation only for when a certain theme is selected, you can do so:

```md
<FilterByTheme name="eiendom">
<VisibilityByTheme visible="eiendom">

## Eiendom examples

Text

<SpecialExample />

</FilterByTheme>
</VisibilityByTheme>
```
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ It is **not** recommended to use the tertiary button without an icon. Looking fo

<TertiaryButtonSizes />

<VisibilityByTheme hidden="sbanken">

For variant signal, the recommended sizes are `default` and `large`.

<SignalButtonSizes />

</VisibilityByTheme>

Icon buttons come in all sizes.

<IconButtonSizes />
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ The color will be set based on the parent, inherited `color` by using `currentCo

<LogoInheritColorExample />

<FilterByTheme name="sbanken">
<VisibilityByTheme visible="sbanken">

### Logo with compact variant

<LogoCompactVariantExample />

</FilterByTheme>
</VisibilityByTheme>
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Eufemia supports theming right inside where all the style-sources lives. Having

Themes are independent style packages, which should not be imported in parallel. But rather either – or.

## Theme Component and useTheme Hook
### Theme Component and useTheme Hook

Eufemia has a theming helper, that lets us create nested theming solutions. As of now, it does not deliver any extra features beside the principle.

Expand All @@ -46,11 +46,47 @@ render(
)
```

In addition, you can use this helper function to show/hide content based on the theme.

```tsx
import { Theme, VisibilityByTheme } from '@dnb/eufemia/shared'

render(
<Theme>
<VisibilityByTheme visible="sbanken">
Only shown in Sbanken theme
</VisibilityByTheme>

<VisibilityByTheme hidden="eiendom">
Only hidden in Eiendom theme
</VisibilityByTheme>

<VisibilityByTheme visible={['sbanken', 'eiendom']}>
Only shown in Sbanken or Eiendom theme
</VisibilityByTheme>

<VisibilityByTheme
visible={[{ name: 'sbanken' }, { name: 'eiendom' }]}
>
Only shown in Sbanken or Eiendom theme
</VisibilityByTheme>

<VisibilityByTheme
visible={[{ name: 'sbanken' }, { name: 'eiendom', variant: 'blue' }]}
>
Only shown in Sbanken then or Eiendom theme – that also includes the
fictive variant="blue".
</VisibilityByTheme>
</Theme>
)
```

`<Theme>` Will create a `div` wrapper by default, when no custom element is defined (`element="span"`). It sets these CSS classes:

- `eufemia-theme__{theme-name}`
- `eufemia-theme__{theme-name}--{theme-variant}`


### Brand theming

Eufemia is a design system that aims to help DNB brands unleash their power.
Expand Down

This file was deleted.

4 changes: 2 additions & 2 deletions packages/dnb-design-system-portal/src/shared/tags/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import Table from './Table'
import Anchor from './Anchor'
import Header from './AutoLinkHeader'
import Copy from './Copy'
import FilterByTheme from './FilterByTheme'
import VisibilityByTheme from '@dnb/eufemia/src/shared/VisibilityByTheme'

export default {
Copy,
FilterByTheme,
VisibilityByTheme,
// img: Img, // -> <figure> cannot appear as a descendant of <p>
h1: (props) => <Header level="1" {...props} />,
h2: (props) => <Header level="2" {...props} />,
Expand Down
63 changes: 63 additions & 0 deletions packages/dnb-eufemia/src/shared/VisibilityByTheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react'
import useTheme from './useTheme'
import type { ThemeNames, ThemeProps } from './Theme'

type VisibilityByThemeProps = {
/**
* A valid theme name or object.
* Will pass children on a match.
*/
visible?: ThemeParams

/**
* A valid theme name or object.
* Will omit passing children on a match.
* NB: "visible" takes presense over "hidden"
*/
hidden?: ThemeParams

/**
* Any kind of a React Node that should render on a match.
*/
children: React.ReactNode
}

type ThemeItem = ThemeNames | ThemeProps
type ThemeParams = ThemeItem | Array<ThemeItem>

export default function VisibilityByTheme({
children,
visible,
hidden,
}: VisibilityByThemeProps) {
const theme = useTheme()

const visibleList = Array.isArray(visible) ? visible : [visible]
const hiddenList = Array.isArray(hidden) ? hidden : [hidden]

if (visible) {
if (!visibleList.some(match(theme))) {
return null
}
} else if (hidden) {
if (hiddenList.some(match(theme))) {
return null
}
}

return children as JSX.Element

function match(theme: ThemeProps) {
return (themeItem: ThemeItem) => {
return typeof themeItem === 'string'
? theme.name === themeItem
: matchObject(theme, themeItem)
}
}

function matchObject(theme: ThemeProps, themeItem: ThemeItem) {
return Object.keys(themeItem).every((key) => {
return theme[key] === themeItem[key]
})
}
}
159 changes: 159 additions & 0 deletions packages/dnb-eufemia/src/shared/__tests__/VisibilityByTheme.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import React from 'react'
import { render } from '@testing-library/react'
import Theme from '../Theme'
import VisibilityByTheme from '../VisibilityByTheme'

describe('VisibilityByTheme', () => {
it('renders content if not visible or hidden was given', () => {
const Component = (props) => (
<Theme name="eiendom" {...props}>
<VisibilityByTheme>
<p>I'm visible</p>
</VisibilityByTheme>
</Theme>
)

render(<Component />)

expect(document.body.textContent).toBe("I'm visible")
})

it('renders content on name match', () => {
const Component = (props) => (
<Theme name="eiendom" {...props}>
<VisibilityByTheme visible="eiendom">
<p>I'm visible</p>
</VisibilityByTheme>
</Theme>
)

const { rerender } = render(<Component />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="sbanken" />)

expect(document.body.textContent).toBe('')
})

it('skips render when hidden matches', () => {
const Component = (props) => (
<Theme name="eiendom" {...props}>
<VisibilityByTheme hidden="sbanken">
<p>I'm visible</p>
</VisibilityByTheme>
</Theme>
)

const { rerender } = render(<Component />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="sbanken" />)

expect(document.body.textContent).toBe('')

rerender(<Component name="ui" />)

expect(document.body.textContent).toBe("I'm visible")
})

it('prefers visible over hidden', () => {
const Component = (props) => (
<Theme name="eiendom" {...props}>
<VisibilityByTheme visible="eiendom" hidden="sbanken">
<p>I'm visible</p>
</VisibilityByTheme>
</Theme>
)

const { rerender } = render(<Component />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="sbanken" />)

expect(document.body.textContent).toBe('')

rerender(<Component name="eiendom" />)

expect(document.body.textContent).toBe("I'm visible")
})

it('renders content on match from names in an array', () => {
const Component = (props) => (
<Theme name="eiendom" {...props}>
<VisibilityByTheme visible={['eiendom', 'sbanken']}>
<p>I'm visible</p>
</VisibilityByTheme>
</Theme>
)

const { rerender } = render(<Component />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="sbanken" />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="ui" />)

expect(document.body.textContent).toBe('')
})

it('renders content on match from names in an object inside an array', () => {
const Component = (props) => (
<Theme name="eiendom" {...props}>
<VisibilityByTheme
visible={[{ name: 'eiendom' }, { name: 'sbanken' }]}
>
<p>I'm visible</p>
</VisibilityByTheme>
</Theme>
)

const { rerender } = render(<Component />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="sbanken" />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="ui" />)

expect(document.body.textContent).toBe('')
})

it('renders content on match by several theme criterias', () => {
const Component = (props) => (
<Theme name="eiendom" variant="red" {...props}>
<VisibilityByTheme
visible={[
{ name: 'eiendom', variant: 'red' },
{ name: 'sbanken', variant: 'blue' },
]}
>
<p>I'm visible</p>
</VisibilityByTheme>
</Theme>
)

const { rerender } = render(<Component />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="sbanken" />)

expect(document.body.textContent).toBe('')

rerender(<Component name="sbanken" variant="blue" />)

expect(document.body.textContent).toBe("I'm visible")

rerender(<Component name="sbanken" variant="red" />)

expect(document.body.textContent).toBe('')
})
})
2 changes: 2 additions & 0 deletions packages/dnb-eufemia/src/shared/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Context from './Context'
import Provider from './Provider'
import Theme from './Theme'
import useTheme from './useTheme'
import VisibilityByTheme from './VisibilityByTheme'
import MediaQuery from './MediaQuery'
import useMediaQuery from './useMediaQuery'
import useMedia from './useMedia'
Expand All @@ -16,6 +17,7 @@ export {
Provider,
Theme,
useTheme,
VisibilityByTheme,
MediaQuery,
useMediaQuery,
useMedia,
Expand Down

0 comments on commit 2592658

Please sign in to comment.