@payfit/unity-illustrations 2.55.22 → 2.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/package.json +11 -4
  2. package/skills/unity-illustrations/SKILL.md +39 -0
  3. package/src/agent-references/illustrations-catalog.json +1996 -0
  4. package/src/agent-references/manifest.json +16 -0
  5. package/dist/esm/scripts/agent-references/illustration-reference.d.ts +0 -17
  6. package/src/agent-references/README.mdx +0 -17
  7. package/src/agent-references/illustrations.mdx +0 -74
  8. package/src/components/illustration/Illustration.stories.tsx +0 -362
  9. package/src/components/illustration/Illustration.tsx +0 -169
  10. package/src/components/illustration/figma/IllustrationLarge.figma.ts +0 -258
  11. package/src/components/illustration/figma/IllustrationMedium.figma.ts +0 -258
  12. package/src/components/illustration/figma/IllustrationSmall.figma.ts +0 -258
  13. package/src/components/lazy-illustration/LazyIllustration.stories.tsx +0 -204
  14. package/src/components/lazy-illustration/LazyIllustration.tsx +0 -125
  15. package/src/components/lazy-illustration/figma/LazyIllustrationLarge.figma.ts +0 -255
  16. package/src/components/lazy-illustration/figma/LazyIllustrationMedium.figma.ts +0 -255
  17. package/src/components/lazy-illustration/figma/LazyIllustrationSmall.figma.ts +0 -255
  18. package/src/docs/1-Introduction/1-Welcome.mdx +0 -111
  19. package/src/docs/1-Introduction/2-Getting Started.mdx +0 -25
  20. package/src/docs/1-Introduction/3-Usage.mdx +0 -114
  21. package/src/docs/1-Introduction/4-Changelog.mdx +0 -6
  22. package/src/docs/2-Reference/1-Find my illustration.mdx +0 -12
  23. package/src/docs/2-Reference/2-Find my animation.mdx +0 -14
  24. package/src/docs/2-Reference/3-Multi brand support.mdx +0 -146
  25. package/src/docs/blocks/AnimationLibrary.tsx +0 -181
  26. package/src/docs/blocks/BrandSupportBadge.tsx +0 -190
  27. package/src/docs/blocks/BrandSupportFilter.tsx +0 -72
  28. package/src/docs/blocks/Cards.tsx +0 -74
  29. package/src/docs/blocks/IllustrationLibrary.tsx +0 -179
  30. package/src/docs/blocks/useStorybookTheme.ts +0 -73
  31. package/src/index.ts +0 -4
  32. package/src/scripts/agent-references/illustration-reference.test.ts +0 -100
  33. package/src/scripts/agent-references/illustration-reference.ts +0 -140
  34. package/src/scripts/generate-assets.ts +0 -649
  35. package/src/scripts/templates/assetTemplate.ts +0 -224
  36. package/src/scripts/templates/baseTypes.ts +0 -45
  37. package/src/scripts/templates/indexTemplate.ts +0 -109
  38. package/src/unity.css +0 -5
@@ -1,146 +0,0 @@
1
- import { Canvas, Meta } from '@storybook/addon-docs/blocks'
2
- import * as IllustrationStories from '../../components/illustration/Illustration.stories'
3
- import * as LazyIllustrationStories from '../../components/lazy-illustration/LazyIllustration.stories'
4
-
5
- <Meta title="Reference/Multi brand support" />
6
-
7
- # Multi brand support
8
-
9
- Unity illustrations can resolve different visual assets for the legacy and rebrand themes while keeping the same logical illustration name.
10
-
11
- The theme-aware behavior is available through both rendering APIs:
12
-
13
- - `Illustration`, when you import an asset directly from `@payfit/unity-illustrations/assets/{Name}`
14
- - `LazyIllustration`, when you render an asset by its generated `name`
15
-
16
- Both APIs ultimately render the same generated asset contract.
17
-
18
- ## Generated asset contract
19
-
20
- Every generated asset keeps the original `url` field for backwards compatibility and can also expose a `urls` object keyed by Unity theme.
21
-
22
- ```tsx
23
- const FAQ = {
24
- type: 'svg',
25
- url: legacySvgUrl,
26
- urls: {
27
- legacy: legacySvgUrl,
28
- rebrand: rebrandSvgUrl,
29
- },
30
- animated: false,
31
- name: 'FAQ',
32
- }
33
- ```
34
-
35
- `url` remains the default source. `urls.legacy` and `urls.rebrand` are used by Unity illustration components when a `UnityThemeProvider` is active.
36
-
37
- ## Runtime resolution
38
-
39
- `Illustration` reads the active theme from `UnityThemeProvider` and resolves the rendered source as:
40
-
41
- ```tsx
42
- const resolvedUrl = asset.urls?.[theme] ?? asset.url
43
- ```
44
-
45
- `LazyIllustration` first loads the asset by name from the generated lazy map, then delegates rendering to `Illustration`. This means `LazyIllustration` has the same brand-aware behavior after the asset has loaded.
46
-
47
- ## Direct asset imports
48
-
49
- Use direct imports when you know the asset at build time and want the import to stay explicit.
50
-
51
- <Canvas of={IllustrationStories.ThemeVariants} meta={IllustrationStories} sourceState="shown" />
52
-
53
- ```tsx
54
- import { UnityThemeProvider } from '@payfit/unity-themes'
55
- import { Illustration } from '@payfit/unity-illustrations'
56
- import FAQ from '@payfit/unity-illustrations/assets/FAQ'
57
-
58
- function Example() {
59
- return (
60
- <UnityThemeProvider theme="rebrand">
61
- <Illustration src={FAQ} variant="icon" size="lg" alt="FAQ icon" />
62
- </UnityThemeProvider>
63
- )
64
- }
65
- ```
66
-
67
- ## Lazy illustrations
68
-
69
- Use `LazyIllustration` when the illustration name is selected dynamically or when the asset should be loaded on demand.
70
-
71
- <Canvas of={LazyIllustrationStories.ThemeVariants} meta={LazyIllustrationStories} sourceState="shown" />
72
-
73
- ```tsx
74
- import { UnityThemeProvider } from '@payfit/unity-themes'
75
- import { LazyIllustration } from '@payfit/unity-illustrations'
76
-
77
- function Example() {
78
- return (
79
- <UnityThemeProvider theme="rebrand">
80
- <LazyIllustration name="FAQ" variant="icon" size="lg" alt="FAQ icon" />
81
- </UnityThemeProvider>
82
- )
83
- }
84
- ```
85
-
86
- ## Brand support states
87
-
88
- The Reference catalogs show a brand-support badge for each illustration. Those badges describe the visual migration state, not only whether two generated URLs exist.
89
-
90
- ### Dual brand
91
-
92
- An illustration is considered `Dual brand` when the legacy and rebrand sources are both available and their optimized asset contents are different.
93
-
94
- When the active theme is `legacy`, the legacy source is rendered. When the active theme is `rebrand`, the rebrand source is rendered.
95
-
96
- ### Legacy only
97
-
98
- An illustration is considered `Legacy only` when there is no distinct rebrand visual yet.
99
-
100
- This includes two cases:
101
-
102
- - The generated asset only has a legacy/default source.
103
- - A rebrand source file exists, but its optimized contents are identical to the legacy SVG.
104
-
105
- In the second case, the runtime may still resolve `urls.rebrand` under the rebrand theme, but the rendered image is visually the same as the legacy asset. The catalog keeps this in `Legacy only` so it can be used as a migration checklist.
106
-
107
- ### Rebrand only
108
-
109
- An illustration is considered `Rebrand only` when it exists only in the rebrand catalog.
110
-
111
- For compatibility, these assets populate every URL field with the rebrand source:
112
-
113
- ```tsx
114
- const RebrandOnlyAsset = {
115
- url: rebrandSvgUrl,
116
- urls: {
117
- legacy: rebrandSvgUrl,
118
- rebrand: rebrandSvgUrl,
119
- },
120
- }
121
- ```
122
-
123
- This means the same rebrand image renders in both themes.
124
-
125
- ## Catalog filters
126
-
127
- Use the brand-support filters in [Find my illustration](/?path=/docs/reference-find-my-illustration--docs) and [Find my animation](/?path=/docs/reference-find-my-animation--docs) to inspect migration progress:
128
-
129
- - `All` shows every generated asset in that catalog.
130
- - `Dual` shows assets that visibly switch between legacy and rebrand.
131
- - `Legacy only` shows assets that still render legacy visuals.
132
- - `Rebrand only` shows assets that only exist in the rebrand catalog.
133
-
134
- Animated WEBP assets keep the same generated image URL unless a future asset pipeline introduces theme-specific animated sources.
135
-
136
- ## Figma automation
137
-
138
- The Figma sync no longer distinguishes between illustrated icons and illustrated pictures. Static exports are treated as illustrations and are written to `assets/rebrand/` with the exact Figma variant filename.
139
-
140
- The current source nodes are configured in `.figma-sync.json` under `download.illustrations.nodeIds`:
141
-
142
- ```json
143
- ["19134:5", "19134:8607", "19134:9880"]
144
- ```
145
-
146
- `assets/legacy/` is the frozen fallback catalog. The sync does not create, update, or delete files there. Filename properties such as `Type`, `Name`, `With background`, and `Size` remain the source of truth for generating the public Unity illustration name.
@@ -1,181 +0,0 @@
1
- import React, { memo, useCallback, useMemo, useState } from 'react'
2
-
3
- import { UnityThemeProvider } from '@payfit/unity-themes'
4
-
5
- import { LazyIllustration } from '../../components/lazy-illustration/LazyIllustration'
6
- import type { UnityIllustrationName } from '../../generated/illustrationAssets'
7
-
8
- import { illustrationNames } from '../../generated/illustrationAssets'
9
- import type { BrandSupportFilter } from './BrandSupportFilter'
10
-
11
- import {
12
- BrandSupportBadge,
13
- useBrandSupportRecords,
14
- } from './BrandSupportBadge'
15
- import {
16
- BrandSupportFilterControl,
17
- matchesBrandSupportFilter,
18
- } from './BrandSupportFilter'
19
- import { useStorybookTheme } from './useStorybookTheme'
20
-
21
- const debounce = <P = unknown,>(
22
- callback: (...args: P[]) => unknown,
23
- wait: number,
24
- ) => {
25
- let timeoutId: number
26
- return (...args: P[]) => {
27
- if (timeoutId) {
28
- window.clearTimeout(timeoutId)
29
- }
30
- timeoutId = window.setTimeout(() => {
31
- callback(...args)
32
- }, wait)
33
- }
34
- }
35
-
36
- const AnimationItem = memo(
37
- ({
38
- children,
39
- name,
40
- copyText,
41
- onClick,
42
- }: React.PropsWithChildren<{
43
- name: UnityIllustrationName
44
- copyText: string
45
- onClick: () => void
46
- }>) => {
47
- return (
48
- <li>
49
- <button
50
- onClick={onClick}
51
- className="uy:group uy:flex uy:h-full uy:w-full uy:flex-col uy:gap-150 uy:p-200 uy:transition-all uy:rounded-lg uy:bg-surface-neutral uy:shadow-raising uy:hover:shadow-flying uy:focus-visible:shadow-flying uy:focus-visible:outline-none uy:focus-visible:ring-2 uy:focus-visible:ring-ring uy:focus-visible:ring-offset-2 uy:focus-visible:ring-utility-focus-ring uy:focus-visible:border-border-form-hover uy:focus-visible:bg-surface-neutral-low-hover uy:border uy:border-solid uy:border-border-neutral"
52
- >
53
- <figure className="uy:flex uy:h-full uy:w-full uy:flex-col uy:items-center uy:gap-150">
54
- <figcaption className="uy:w-full uy:text-left">
55
- <span className="uy:block uy:w-full uy:truncate uy:font-mono uy:text-50 uy:leading-normal uy:text-content-neutral">
56
- {name}
57
- </span>
58
- </figcaption>
59
- <div
60
- className="uy:flex uy:h-[160px] uy:w-full uy:items-center uy:justify-center uy:transition-all uy:group-hover:scale-105"
61
- style={{ maxWidth: '200px' }}
62
- >
63
- {children}
64
- </div>
65
- <div className="uy:mt-auto uy:flex uy:w-full uy:items-center uy:justify-between uy:gap-100">
66
- <BrandSupportBadge name={name} />
67
- <span className="uy:typography-action-small uy:bg-transparent uy:theme-legacy:text-content-primary uy:theme-rebrand:text-content-neutral uy:theme-rebrand:underline uy:whitespace-nowrap">
68
- {copyText}
69
- </span>
70
- </div>
71
- </figure>
72
- </button>
73
- </li>
74
- )
75
- },
76
- )
77
-
78
- const AnimationLibrary = () => {
79
- const theme = useStorybookTheme()
80
- const [query, setQuery] = useState<string>('')
81
- const [copiedAnimation, setCopiedAnimation] = useState<string | null>(null)
82
- const [brandFilter, setBrandFilter] = useState<BrandSupportFilter>('all')
83
-
84
- // Filter only animation names (those ending with 'Animation')
85
- const animationList = useMemo(() => {
86
- return illustrationNames.filter(name => name.endsWith('Animation'))
87
- }, [])
88
- const brandSupportByName = useBrandSupportRecords(animationList)
89
-
90
- const copyToClipboard = useCallback((animationName: string) => {
91
- const importStatement = `import ${animationName} from '@payfit/unity-illustrations/assets/${animationName}'`
92
-
93
- navigator.clipboard
94
- .writeText(importStatement)
95
- .then(() => {
96
- setCopiedAnimation(animationName)
97
- setTimeout(() => {
98
- setCopiedAnimation(null)
99
- }, 5000)
100
- })
101
- .catch((error: unknown) => {
102
- console.error('Failed to copy: ', error)
103
- })
104
- }, [])
105
-
106
- const updateSearchQuery = debounce((animationName: string) => {
107
- setQuery(animationName)
108
- }, 150)
109
-
110
- const animationListMatchingSearch = animationList.filter(animationName =>
111
- animationName.toLowerCase().includes(query.toLowerCase()) &&
112
- matchesBrandSupportFilter(
113
- brandSupportByName.get(animationName) ?? 'loading',
114
- brandFilter,
115
- ),
116
- )
117
-
118
- return (
119
- <UnityThemeProvider key={theme} theme={theme}>
120
- <section className="uy:flex uy:flex-col uy:gap-300 uy:max-w-[80vmax] uy:mx-auto">
121
- <header>
122
- <h3>Animation Gallery</h3>
123
- <p className="uy:m-0!">
124
- Click on any animation to copy its import path to the clipboard. All animations are displayed with a maximum width of 600px to ensure optimal viewing.
125
- </p>
126
- </header>
127
- <div className="uy:flex uy:flex-col uy:gap-150">
128
- <BrandSupportFilterControl
129
- name="animation-brand-support-filter"
130
- value={brandFilter}
131
- onChange={setBrandFilter}
132
- />
133
- <input
134
- type="search"
135
- aria-label="Search for an animation"
136
- placeholder="Search for an animation..."
137
- className="uy:flex uy:h-500 uy:w-full uy:text-content-form-enabled uy:rounded-100 uy:border uy:border-solid uy:border-border-form-enabled uy:bg-background uy:px-150 uy:py-100 uy:typography-body uy:placeholder:text-content-neutral-lowest uy:hover:border-border-form-hover uy:focus-visible:outline-none uy:focus-visible:ring-2 uy:focus-visible:ring-ring uy:focus-visible:ring-offset-2 uy:focus-visible:ring-utility-focus-ring"
138
- onChange={e => {
139
- updateSearchQuery(e.target.value)
140
- }}
141
- />
142
- </div>
143
-
144
- {animationListMatchingSearch.length === 0 ? (
145
- <div className="uy:text-center uy:py-800">
146
- <p className="uy:typography-body uy:text-content-tertiary">
147
- {query
148
- ? `No animations found matching "${query}"`
149
- : 'No animations available'}
150
- </p>
151
- </div>
152
- ) : (
153
- <ul className="sb-unstyled uy:list-none uy:grid uy:grid-cols-[repeat(auto-fill,minmax(200px,_1fr))] uy:gap-200">
154
- {animationListMatchingSearch.map(name => {
155
- return (
156
- <AnimationItem
157
- name={name}
158
- copyText={
159
- copiedAnimation === name ? 'Copied!' : 'Click to Copy'
160
- }
161
- key={name}
162
- onClick={() => {
163
- copyToClipboard(name)
164
- }}
165
- >
166
- <LazyIllustration
167
- variant="picture"
168
- alt={`${name} animation`}
169
- name={name}
170
- />
171
- </AnimationItem>
172
- )
173
- })}
174
- </ul>
175
- )}
176
- </section>
177
- </UnityThemeProvider>
178
- )
179
- }
180
-
181
- export default AnimationLibrary
@@ -1,190 +0,0 @@
1
- import { useSyncExternalStore } from 'react'
2
-
3
- import type {
4
- UnityIllustrationAsset,
5
- UnityIllustrationName,
6
- } from '../../generated/illustrationAssets'
7
-
8
- import { lazyIllustrationsMap } from '../../generated/illustrationAssets'
9
-
10
- export type BrandSupport = 'loading' | 'dual' | 'legacy-only' | 'rebrand-only'
11
-
12
- const brandSupportByName = new Map<UnityIllustrationName, BrandSupport>()
13
- const pendingNames = new Set<UnityIllustrationName>()
14
- const listenersByName = new Map<UnityIllustrationName, Set<() => void>>()
15
- const listeners = new Set<() => void>()
16
- let version = 0
17
-
18
- async function haveSameContent(firstUrl: string, secondUrl: string) {
19
- if (typeof fetch === 'undefined') {
20
- return false
21
- }
22
-
23
- const [firstResponse, secondResponse] = await Promise.all([
24
- fetch(firstUrl),
25
- fetch(secondUrl),
26
- ])
27
- const [firstBuffer, secondBuffer] = await Promise.all([
28
- firstResponse.arrayBuffer(),
29
- secondResponse.arrayBuffer(),
30
- ])
31
-
32
- if (firstBuffer.byteLength !== secondBuffer.byteLength) {
33
- return false
34
- }
35
-
36
- const firstBytes = new Uint8Array(firstBuffer)
37
- const secondBytes = new Uint8Array(secondBuffer)
38
-
39
- return firstBytes.every((byte, index) => byte === secondBytes[index])
40
- }
41
-
42
- async function classifyBrandSupport(
43
- asset: UnityIllustrationAsset,
44
- ): Promise<BrandSupport> {
45
- const legacyUrl = asset.urls?.legacy ?? asset.url
46
- const rebrandUrl = asset.urls?.rebrand ?? asset.url
47
-
48
- if (
49
- legacyUrl !== rebrandUrl &&
50
- !(await haveSameContent(legacyUrl, rebrandUrl))
51
- ) {
52
- return 'dual'
53
- }
54
-
55
- if (legacyUrl.includes('.rebrand.')) {
56
- return 'rebrand-only'
57
- }
58
-
59
- return 'legacy-only'
60
- }
61
-
62
- function notify(name: UnityIllustrationName) {
63
- version += 1
64
- listenersByName.get(name)?.forEach(listener => {
65
- listener()
66
- })
67
- listeners.forEach(listener => {
68
- listener()
69
- })
70
- }
71
-
72
- function loadBrandSupport(name: UnityIllustrationName) {
73
- if (brandSupportByName.has(name) || pendingNames.has(name)) {
74
- return
75
- }
76
-
77
- pendingNames.add(name)
78
-
79
- void lazyIllustrationsMap[name]()
80
- .then(async module => {
81
- brandSupportByName.set(
82
- name,
83
- await classifyBrandSupport(module.default),
84
- )
85
- })
86
- .catch(() => {
87
- brandSupportByName.set(name, 'legacy-only')
88
- })
89
- .finally(() => {
90
- pendingNames.delete(name)
91
- notify(name)
92
- })
93
- }
94
-
95
- function subscribeToBrandSupport(
96
- name: UnityIllustrationName,
97
- onStoreChange: () => void,
98
- ) {
99
- const listeners = listenersByName.get(name) ?? new Set<() => void>()
100
- listeners.add(onStoreChange)
101
- listenersByName.set(name, listeners)
102
-
103
- loadBrandSupport(name)
104
-
105
- return () => {
106
- listeners.delete(onStoreChange)
107
-
108
- if (listeners.size === 0) {
109
- listenersByName.delete(name)
110
- }
111
- }
112
- }
113
-
114
- function getBrandSupportSnapshot(name: UnityIllustrationName): BrandSupport {
115
- return brandSupportByName.get(name) ?? 'loading'
116
- }
117
-
118
- function useBrandSupport(name: UnityIllustrationName): BrandSupport {
119
- return useSyncExternalStore(
120
- onStoreChange => subscribeToBrandSupport(name, onStoreChange),
121
- () => getBrandSupportSnapshot(name),
122
- () => 'loading',
123
- )
124
- }
125
-
126
- function subscribeToBrandSupportRecords(
127
- names: readonly UnityIllustrationName[],
128
- onStoreChange: () => void,
129
- ) {
130
- listeners.add(onStoreChange)
131
- names.forEach(loadBrandSupport)
132
-
133
- return () => {
134
- listeners.delete(onStoreChange)
135
- }
136
- }
137
-
138
- export function useBrandSupportRecords(
139
- names: readonly UnityIllustrationName[],
140
- ) {
141
- useSyncExternalStore(
142
- onStoreChange => subscribeToBrandSupportRecords(names, onStoreChange),
143
- () => version,
144
- () => version,
145
- )
146
-
147
- return new Map(
148
- names.map(name => [name, getBrandSupportSnapshot(name)] as const),
149
- )
150
- }
151
-
152
- const badgeContent: Record<
153
- BrandSupport,
154
- { label: string; className: string; title: string }
155
- > = {
156
- loading: {
157
- label: 'Checking brand',
158
- className: 'uy:bg-surface-neutral-low uy:text-content-neutral',
159
- title: 'Checking illustration brand support',
160
- },
161
- dual: {
162
- label: 'Dual brand',
163
- className: 'uy:bg-surface-success-low uy:text-content-success-high',
164
- title: 'This illustration switches between legacy and rebrand assets',
165
- },
166
- 'legacy-only': {
167
- label: 'Legacy only',
168
- className: 'uy:bg-surface-warning-low uy:text-content-warning-high',
169
- title: 'This illustration uses the legacy asset for every theme',
170
- },
171
- 'rebrand-only': {
172
- label: 'Rebrand only',
173
- className: 'uy:bg-surface-info-low uy:text-content-info-high',
174
- title: 'This illustration uses the rebrand asset for every theme',
175
- },
176
- }
177
-
178
- export function BrandSupportBadge({ name }: { name: UnityIllustrationName }) {
179
- const brandSupport = useBrandSupport(name)
180
- const { label, className, title } = badgeContent[brandSupport]
181
-
182
- return (
183
- <span
184
- className={`uy:typography-action-small uy:inline-flex uy:h-300 uy:items-center uy:rounded-50 uy:px-100 ${className}`}
185
- title={title}
186
- >
187
- {label}
188
- </span>
189
- )
190
- }
@@ -1,72 +0,0 @@
1
- import type { BrandSupport } from './BrandSupportBadge'
2
-
3
- export type BrandSupportFilter = Exclude<BrandSupport, 'loading'> | 'all'
4
-
5
- const options: ReadonlyArray<{
6
- label: string
7
- value: BrandSupportFilter
8
- }> = [
9
- { label: 'All', value: 'all' },
10
- { label: 'Dual', value: 'dual' },
11
- { label: 'Legacy only', value: 'legacy-only' },
12
- { label: 'Rebrand only', value: 'rebrand-only' },
13
- ]
14
-
15
- export function matchesBrandSupportFilter(
16
- brandSupport: BrandSupport,
17
- filter: BrandSupportFilter,
18
- ) {
19
- return filter === 'all' || brandSupport === filter
20
- }
21
-
22
- export function BrandSupportFilterControl({
23
- name,
24
- value,
25
- onChange,
26
- }: {
27
- name: string
28
- value: BrandSupportFilter
29
- onChange: (value: BrandSupportFilter) => void
30
- }) {
31
- return (
32
- <fieldset className="uy:m-0 uy:border-0 uy:p-0">
33
- <legend className="uy:sr-only">Filter illustrations by brand support</legend>
34
- <div className="uy:flex uy:w-fit uy:gap-50 uy:rounded-75 uy:bg-surface-neutral-low uy:px-50 uy:py-50">
35
- {options.map(option => {
36
- const isSelected = option.value === value
37
-
38
- return (
39
- <label
40
- key={option.value}
41
- className={`uy:group uy:flex uy:h-400 uy:cursor-pointer uy:items-center uy:gap-25 uy:rounded-50 uy:px-150 uy:py-50 ${
42
- isSelected
43
- ? 'uy:bg-surface-neutral-enabled uy:shadow-raising'
44
- : 'uy:hover:bg-surface-neutral-hover'
45
- }`}
46
- >
47
- <input
48
- type="radio"
49
- name={name}
50
- value={option.value}
51
- checked={isSelected}
52
- onChange={() => {
53
- onChange(option.value)
54
- }}
55
- className="uy:sr-only"
56
- />
57
- <span
58
- className={`uy:flex uy:items-center uy:gap-50 uy:typography-action ${
59
- isSelected
60
- ? 'uy:text-content-neutral'
61
- : 'uy:text-content-neutral-enabled uy:group-hover:text-content-neutral-hover'
62
- }`}
63
- >
64
- {option.label}
65
- </span>
66
- </label>
67
- )
68
- })}
69
- </div>
70
- </fieldset>
71
- )
72
- }
@@ -1,74 +0,0 @@
1
- import React from 'react'
2
-
3
- type CardProps = React.PropsWithChildren
4
-
5
- export const Card: React.FC<CardProps> = ({ children }) => {
6
- return (
7
- <article className="sb-unstyled uy:bg-surface-neutral uy:border uy:border-solid uy:border-border-neutral uy:shadow-raising uy:rounded-200 uy:p-300 uy:flex uy:flex-col uy:gap-150">
8
- {children}
9
- </article>
10
- )
11
- }
12
-
13
- export const CardWithCover: React.FC<
14
- CardProps & { coverSrc: string; coverAlt: string }
15
- > = ({ children, coverSrc, coverAlt }) => {
16
- return (
17
- <article className="sb-unstyled uy:bg-surface-neutral uy:border uy:border-solid uy:border-border-neutral uy:shadow-raising uy:rounded-200 uy:flex uy:flex-col uy:overflow-hidden uy:sm:flex-row">
18
- <img
19
- src={coverSrc}
20
- alt={coverAlt}
21
- className="sb-unstyled uy:w-full uy:h-[180px] uy:object-contain uy:bg-blue-l3 uy:sm:object-fill uy:sm:h-full uy:sm:max-w-[200px]"
22
- />
23
- <section className="uy:p-300 uy:space-y-100">{children}</section>
24
- </article>
25
- )
26
- }
27
- export const CardHeader: React.FC<React.PropsWithChildren> = ({ children }) => {
28
- return <header>{children}</header>
29
- }
30
-
31
- export const CardTitle: React.FC<React.PropsWithChildren> = ({ children }) => {
32
- return <h3 className="sb-unstyled uy:typography-h3 uy:m-0">{children}</h3>
33
- }
34
-
35
- export const CardContent: React.FC<React.PropsWithChildren> = ({
36
- children,
37
- }) => {
38
- return <section className="uy:typography-body">{children}</section>
39
- }
40
-
41
- export const CardActions: React.FC<React.PropsWithChildren> = ({
42
- children,
43
- }) => {
44
- return <footer className="uy:flex uy:flex-col uy:gap-100">{children}</footer>
45
- }
46
-
47
- export const Link: React.FC<React.ComponentProps<'a'>> = ({
48
- children,
49
- ...rest
50
- }) => {
51
- return (
52
- <a
53
- className="uy:typography-action uy:block uy:text-content-primary uy:underline"
54
- {...rest}
55
- >
56
- {children}
57
- </a>
58
- )
59
- }
60
-
61
- type CardGridProps = {
62
- children: React.ReactNode
63
- cols?: number
64
- }
65
-
66
- export const CardGrid: React.FC<CardGridProps> = ({ children, cols = 2 }) => {
67
- return (
68
- <div
69
- className={`uy:grid uy:grid-cols-1 uy:gap-150 uy:md:grid-cols-${cols}`}
70
- >
71
- {children}
72
- </div>
73
- )
74
- }