@edgeandnode/eds-react 1.0.1 → 1.0.2
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.
- package/README.md +20 -3
- package/dist/EDSContext.d.ts +3 -0
- package/dist/EDSContext.d.ts.map +1 -1
- package/dist/EDSContext.js +7 -0
- package/dist/EDSContext.js.map +1 -1
- package/dist/EDSProvider.d.ts +1 -1
- package/dist/EDSProvider.d.ts.map +1 -1
- package/dist/EDSProvider.js +15 -8
- package/dist/EDSProvider.js.map +1 -1
- package/dist/components/Avatar.d.ts.map +1 -1
- package/dist/components/Avatar.js +3 -2
- package/dist/components/Avatar.js.map +1 -1
- package/dist/components/Breadcrumbs.parts.d.ts +4 -3
- package/dist/components/Breadcrumbs.parts.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.parts.js +15 -14
- package/dist/components/Breadcrumbs.parts.js.map +1 -1
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +3 -2
- package/dist/components/Button.js.map +1 -1
- package/dist/components/CodeBlock.parts.d.ts.map +1 -1
- package/dist/components/CodeBlock.parts.js +16 -20
- package/dist/components/CodeBlock.parts.js.map +1 -1
- package/dist/components/CopyButton.d.ts.map +1 -1
- package/dist/components/CopyButton.js +3 -2
- package/dist/components/CopyButton.js.map +1 -1
- package/dist/components/Icon.d.ts.map +1 -1
- package/dist/components/Icon.js +3 -3
- package/dist/components/Icon.js.map +1 -1
- package/dist/components/LocalTheme.d.ts +16 -0
- package/dist/components/LocalTheme.d.ts.map +1 -0
- package/dist/components/LocalTheme.js +22 -0
- package/dist/components/LocalTheme.js.map +1 -0
- package/dist/components/Menu.parts.d.ts.map +1 -1
- package/dist/components/Menu.parts.js +6 -4
- package/dist/components/Menu.parts.js.map +1 -1
- package/dist/components/Modal.parts.d.ts.map +1 -1
- package/dist/components/Modal.parts.js +21 -10
- package/dist/components/Modal.parts.js.map +1 -1
- package/dist/components/SegmentedControl.parts.d.ts.map +1 -1
- package/dist/components/SegmentedControl.parts.js +3 -2
- package/dist/components/SegmentedControl.parts.js.map +1 -1
- package/dist/components/Select.parts.d.ts +4 -4
- package/dist/components/Select.parts.d.ts.map +1 -1
- package/dist/components/Select.parts.js +2 -1
- package/dist/components/Select.parts.js.map +1 -1
- package/dist/components/TabSet.parts.d.ts.map +1 -1
- package/dist/components/TabSet.parts.js +3 -2
- package/dist/components/TabSet.parts.js.map +1 -1
- package/dist/components/Table.parts.d.ts.map +1 -1
- package/dist/components/Table.parts.js +3 -4
- package/dist/components/Table.parts.js.map +1 -1
- package/dist/components/ThemeSwitcher.js +2 -2
- package/dist/components/ThemeSwitcher.js.map +1 -1
- package/dist/components/Tooltip.parts.js +2 -2
- package/dist/components/Tooltip.parts.js.map +1 -1
- package/dist/components/base/Addon.d.ts.map +1 -1
- package/dist/components/base/Addon.js +2 -4
- package/dist/components/base/Addon.js.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.d.ts.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.js +2 -1
- package/dist/components/base/ButtonOrLink.parts.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/hooks/useCSSProp.js +1 -1
- package/dist/hooks/useCSSProp.js.map +1 -1
- package/dist/hooks/useEDS.d.ts +16 -1
- package/dist/hooks/useEDS.d.ts.map +1 -1
- package/dist/hooks/useEDS.js +18 -2
- package/dist/hooks/useEDS.js.map +1 -1
- package/dist/hooks/useStyleObserver.d.ts.map +1 -1
- package/dist/hooks/useStyleObserver.js +2 -1
- package/dist/hooks/useStyleObserver.js.map +1 -1
- package/dist/icons/CalendarDynamicIcon.d.ts.map +1 -1
- package/dist/icons/CalendarDynamicIcon.js +3 -1
- package/dist/icons/CalendarDynamicIcon.js.map +1 -1
- package/dist/utils/getReactNodeKey.d.ts.map +1 -1
- package/dist/utils/getReactNodeKey.js +31 -13
- package/dist/utils/getReactNodeKey.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/internal/getTooltipProps.d.ts +9 -0
- package/dist/utils/internal/getTooltipProps.d.ts.map +1 -0
- package/dist/utils/internal/getTooltipProps.js +18 -0
- package/dist/utils/internal/getTooltipProps.js.map +1 -0
- package/dist/utils/internal/index.d.ts +4 -0
- package/dist/utils/internal/index.d.ts.map +1 -0
- package/dist/utils/internal/index.js +4 -0
- package/dist/utils/internal/index.js.map +1 -0
- package/dist/utils/internal/resolveLazy.d.ts +29 -0
- package/dist/utils/internal/resolveLazy.d.ts.map +1 -0
- package/dist/utils/internal/resolveLazy.js +40 -0
- package/dist/utils/internal/resolveLazy.js.map +1 -0
- package/dist/utils/internal/styleScheduler.d.ts.map +1 -0
- package/dist/utils/internal/styleScheduler.js.map +1 -0
- package/dist/utils/isComponentType.d.ts +9 -0
- package/dist/utils/isComponentType.d.ts.map +1 -0
- package/dist/utils/isComponentType.js +22 -0
- package/dist/utils/isComponentType.js.map +1 -0
- package/dist/utils/isReactNode.d.ts +7 -0
- package/dist/utils/isReactNode.d.ts.map +1 -1
- package/dist/utils/isReactNode.js +11 -0
- package/dist/utils/isReactNode.js.map +1 -1
- package/dist/utils/reactNodeToString.d.ts +3 -0
- package/dist/utils/reactNodeToString.d.ts.map +1 -1
- package/dist/utils/reactNodeToString.js +6 -0
- package/dist/utils/reactNodeToString.js.map +1 -1
- package/dist/utils/styleObserver.js +1 -1
- package/dist/utils/trimReactNode.d.ts +3 -0
- package/dist/utils/trimReactNode.d.ts.map +1 -1
- package/dist/utils/trimReactNode.js +6 -0
- package/dist/utils/trimReactNode.js.map +1 -1
- package/package.json +8 -7
- package/scripts/update-skill.cjs +18 -5
- package/skill/SKILL.md +13 -9
- package/skill/references/tokens.md +2 -8
- package/src/EDSContext.ts +9 -0
- package/src/EDSProvider.tsx +34 -9
- package/src/components/Avatar.tsx +3 -2
- package/src/components/Breadcrumbs.parts.tsx +31 -15
- package/src/components/Button.tsx +3 -2
- package/src/components/CodeBlock.parts.tsx +20 -24
- package/src/components/CopyButton.tsx +3 -2
- package/src/components/Icon.tsx +3 -9
- package/src/components/LocalTheme.tsx +39 -0
- package/src/components/Menu.parts.tsx +5 -3
- package/src/components/Modal.parts.tsx +20 -10
- package/src/components/SegmentedControl.parts.tsx +3 -8
- package/src/components/Select.parts.tsx +6 -5
- package/src/components/TabSet.parts.tsx +3 -5
- package/src/components/Table.parts.tsx +3 -3
- package/src/components/ThemeSwitcher.tsx +2 -2
- package/src/components/Tooltip.parts.tsx +2 -2
- package/src/components/base/Addon.tsx +3 -4
- package/src/components/base/ButtonOrLink.parts.tsx +2 -1
- package/src/components/index.ts +1 -0
- package/src/hooks/useCSSProp.ts +1 -1
- package/src/hooks/useEDS.ts +18 -2
- package/src/hooks/useStyleObserver.ts +2 -1
- package/src/icons/CalendarDynamicIcon.tsx +1 -0
- package/src/utils/getReactNodeKey.ts +30 -14
- package/src/utils/index.ts +1 -1
- package/src/utils/internal/getTooltipProps.ts +25 -0
- package/src/utils/internal/index.ts +3 -0
- package/src/utils/internal/resolveLazy.ts +44 -0
- package/src/utils/isComponentType.ts +27 -0
- package/src/utils/isReactNode.ts +11 -0
- package/src/utils/reactNodeToString.ts +7 -0
- package/src/utils/styleObserver.ts +1 -1
- package/src/utils/trimReactNode.tsx +7 -0
- package/dist/utils/styleScheduler.d.ts.map +0 -1
- package/dist/utils/styleScheduler.js.map +0 -1
- package/src/__snapshots__/compiled.css +0 -22499
- package/styles/tailwind.storybook.css +0 -19
- /package/dist/utils/{styleScheduler.d.ts → internal/styleScheduler.d.ts} +0 -0
- /package/dist/utils/{styleScheduler.js → internal/styleScheduler.js} +0 -0
- /package/src/utils/{styleScheduler.ts → internal/styleScheduler.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edgeandnode/eds-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "React components for the Edge Design System",
|
|
5
5
|
"author": "Edge & Node and contributors",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,8 +35,9 @@
|
|
|
35
35
|
"src",
|
|
36
36
|
"!src/**/*.test.*",
|
|
37
37
|
"!src/**/*.stories.*",
|
|
38
|
+
"!src/**/__snapshots__",
|
|
39
|
+
"!styles/tailwind.storybook.css",
|
|
38
40
|
"skill",
|
|
39
|
-
"!skill/.benchmark",
|
|
40
41
|
"scripts/update-skill.cjs"
|
|
41
42
|
],
|
|
42
43
|
"peerDependencies": {
|
|
@@ -48,14 +49,14 @@
|
|
|
48
49
|
"dependencies": {
|
|
49
50
|
"@base-ui/react": "^1.8.0",
|
|
50
51
|
"@base-ui/utils": "^0.4.0",
|
|
51
|
-
"@edgeandnode/eds-css": "1.0.
|
|
52
|
-
"@edgeandnode/eds-icons": "1.0.
|
|
53
|
-
"@edgeandnode/eds-utils": "1.0.
|
|
52
|
+
"@edgeandnode/eds-css": "1.0.2",
|
|
53
|
+
"@edgeandnode/eds-icons": "1.0.2",
|
|
54
|
+
"@edgeandnode/eds-utils": "1.0.2",
|
|
54
55
|
"@react-aria/utils": "^3.34.1",
|
|
55
56
|
"@react-hookz/web": "^26.1.0",
|
|
56
57
|
"@ver0/deep-equal": "^2.0.1",
|
|
57
58
|
"escape-string-regexp": "^5.0.0",
|
|
58
|
-
"motion": "^13.4.
|
|
59
|
+
"motion": "^13.4.5",
|
|
59
60
|
"react-aria": "^3.52.1",
|
|
60
61
|
"react-keyed-flatten-children": "^5.1.3",
|
|
61
62
|
"react-markdown": "^10.1.0",
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
"@tailwindcss/cli": "^4.3.3",
|
|
78
79
|
"@tailwindcss/postcss": "^4.3.3",
|
|
79
80
|
"@tailwindcss/vite": "^4.3.3",
|
|
80
|
-
"@types/node": "^26.6.
|
|
81
|
+
"@types/node": "^26.6.3",
|
|
81
82
|
"@types/react": "^19.3.0",
|
|
82
83
|
"@types/react-dom": "^19.3.0",
|
|
83
84
|
"@types/react-is": "^19.3.0",
|
package/scripts/update-skill.cjs
CHANGED
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
mkdirSync,
|
|
15
15
|
copyFileSync,
|
|
16
16
|
unlinkSync,
|
|
17
|
+
rmSync,
|
|
17
18
|
statSync,
|
|
18
19
|
} = require('fs')
|
|
19
20
|
const { join, dirname, relative } = require('path')
|
|
@@ -87,14 +88,11 @@ function compareSemver(a, b) {
|
|
|
87
88
|
return 0
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
/**
|
|
91
|
-
* Recursively copy a directory, overwriting existing files. Only copies files that are included in
|
|
92
|
-
* the published package (skips dotdirs).
|
|
93
|
-
*/
|
|
91
|
+
/** Recursively copy a directory, overwriting existing files. */
|
|
94
92
|
function copyDir(src, dest) {
|
|
95
93
|
mkdirSync(dest, { recursive: true })
|
|
96
94
|
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
97
|
-
// Skip dotdirs (e.g.
|
|
95
|
+
// Skip dotfiles and dotdirs (e.g. `.DS_Store` when the package is linked from a workspace)
|
|
98
96
|
if (entry.name.startsWith('.')) continue
|
|
99
97
|
|
|
100
98
|
const srcPath = join(src, entry.name)
|
|
@@ -145,6 +143,21 @@ for (const targetDir of foundDirs) {
|
|
|
145
143
|
// If no version field, this may be a custom skill named "eds" — skip silently
|
|
146
144
|
if (!installedVersion) continue
|
|
147
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The skill benchmark used to live in `skill/.benchmark`, so `npx skills add` (which copies the
|
|
148
|
+
* whole skill folder from GitHub) installed it too. It now lives outside the skill folder, so
|
|
149
|
+
* remove any stale copy. This runs even when the skill is up to date, so that a removal that
|
|
150
|
+
* failed (e.g. because a file was locked) is retried on the next install instead of failing it.
|
|
151
|
+
*/
|
|
152
|
+
const benchmarkDir = join(targetDir, '.benchmark')
|
|
153
|
+
try {
|
|
154
|
+
rmSync(benchmarkDir, { recursive: true, force: true })
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.warn(
|
|
157
|
+
`Couldn't remove the stale EDS skill benchmark at ${relative(initCwd, benchmarkDir)}: ${error.message}`,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
148
161
|
// If installed version is >= package version, skip (prevents downgrades in monorepos)
|
|
149
162
|
if (compareSemver(installedVersion, packageVersion) >= 0) {
|
|
150
163
|
console.log(`EDS skill is up to date (v${installedVersion}) at ${relative(initCwd, targetDir)}`)
|
package/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eds
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.2
|
|
4
4
|
description: The Edge Design System (EDS) is for building user interfaces for Edge & Node products. Use this skill when designing, implementing, reviewing, or polishing Edge & Node product UIs, when using `@edgeandnode/eds-react`, or when EDS is mentioned. Covers component selection, design patterns, tokens, and best practices – not just APIs. APIs are fully typed in the npm package.
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- mcp__storybook__get-changed-stories
|
|
@@ -28,7 +28,7 @@ EDS is the design system for Edge & Node products. This skill teaches you how to
|
|
|
28
28
|
"mcpServers": {
|
|
29
29
|
"storybook": {
|
|
30
30
|
"type": "http",
|
|
31
|
-
"url": "https://
|
|
31
|
+
"url": "https://e4b81a8--6ab2abb8934bb76c7e8e086c.chromatic.com/mcp"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -43,7 +43,7 @@ Add this to the project's `.mcp.json` file (create it if it doesn't exist). For
|
|
|
43
43
|
```json
|
|
44
44
|
{
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@edgeandnode/eds-react": "^1.0.
|
|
46
|
+
"@edgeandnode/eds-react": "^1.0.2",
|
|
47
47
|
"react": "^19.0.0",
|
|
48
48
|
"react-dom": "^19.0.0"
|
|
49
49
|
},
|
|
@@ -67,6 +67,8 @@ Add this to the project's `.mcp.json` file (create it if it doesn't exist). For
|
|
|
67
67
|
|
|
68
68
|
**Important:** `@edgeandnode/eds-css`, which `@edgeandnode/eds-react` depends on, disables Tailwind's default content scanning as an optimization, so you must explicitly [register your source paths with `@source` directives](https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-registering-sources). Make sure to include all paths where Tailwind classes are used.
|
|
69
69
|
|
|
70
|
+
**Browser targets:** EDS's color tokens rely on native `light-dark()`, so the app must target Chrome 123, Safari 17.5, and Firefox 120 or newer. Otherwise, build tools transpile it (e.g. Lightning CSS, which Vite and Next.js use) and components inside `<Dark>`/`<Light>` get the page's colors (`EDSProvider` warns in the console; with Vite, only in production builds). In Next.js, add `"browserslist": ["chrome >= 123", "edge >= 123", "firefox >= 120", "safari >= 17.5", "ios_saf >= 17.5"]` to `package.json`; in Vite, set `build.target: ['chrome123', 'edge123', 'firefox120', 'safari17.5', 'ios17.5']` (it also covers CSS, and unlike `build.cssTarget`, a Vite-based Storybook picks it up).
|
|
71
|
+
|
|
70
72
|
### App Root
|
|
71
73
|
|
|
72
74
|
```tsx
|
|
@@ -80,7 +82,7 @@ function App() {
|
|
|
80
82
|
}
|
|
81
83
|
```
|
|
82
84
|
|
|
83
|
-
**Only one `EDSProvider` per app** — rendering more than one will throw an error. To
|
|
85
|
+
**Only one `EDSProvider` per app** — rendering more than one will throw an error. To render part of the UI in a different theme, wrap it in `<Dark>` or `<Light>` instead (e.g. `<Dark as="section">`).
|
|
84
86
|
|
|
85
87
|
### Theming
|
|
86
88
|
|
|
@@ -89,12 +91,12 @@ function App() {
|
|
|
89
91
|
To read or change the theme from anywhere in the app, use the `useEDS()` hook:
|
|
90
92
|
|
|
91
93
|
```tsx
|
|
92
|
-
import { useEDS } from '@edgeandnode/eds-react'
|
|
94
|
+
import { useEDS } from '@edgeandnode/eds-react/hooks'
|
|
93
95
|
|
|
94
96
|
const { theme, activeTheme, setTheme } = useEDS()
|
|
95
97
|
```
|
|
96
98
|
|
|
97
|
-
`theme` is `'dark' | 'light' | 'system'`. `activeTheme` resolves `'system'` to `'dark'` or `'light'` based on the user's OS preference. `setTheme(theme)` changes it (use `<ThemeSwitcher>` for the UI).
|
|
99
|
+
`theme` is `'dark' | 'light' | 'system'`. `activeTheme` resolves `'system'` to `'dark'` or `'light'` based on the user's OS preference. `setTheme(theme)` changes it (use `<ThemeSwitcher>` for the UI). `localTheme` is the theme of the nearest `<Dark>` or `<Light>` ancestor, or `undefined` if there's none (including inside a `Modal`, which always uses the page's theme) — use `localTheme ?? activeTheme` when rendering something that depends on the theme in JavaScript. `localThemeClass` is that ancestor's class (or `undefined` if there's none) — add it to content you render outside of that ancestor in the DOM (e.g. with `createPortal`) so it keeps its theme.
|
|
98
100
|
|
|
99
101
|
### Router Integration (Next.js)
|
|
100
102
|
|
|
@@ -212,7 +214,7 @@ import { EDSProvider } from '@edgeandnode/eds-react'
|
|
|
212
214
|
</Button>
|
|
213
215
|
```
|
|
214
216
|
|
|
215
|
-
15. **Pass icons as components, not elements, in addon props.** Use `addonBefore={PlayIcon}` not `addonBefore={<PlayIcon alt="" />}`. The element form (`<PlayIcon alt="..." />`) is only needed when passing a non-empty `alt` or a `variant`, or when
|
|
217
|
+
15. **Pass icons as components, not elements, in addon props.** Use `addonBefore={PlayIcon}` not `addonBefore={<PlayIcon alt="" />}`. The element form (`<PlayIcon alt="..." />`) is only needed when passing a non-empty `alt` or a `variant`, or when passing it from a server component (server components can't pass functions to client components — use `addonBefore={<PlayIcon alt="" />}`). Addons have a default size tied to the parent component — don't set `size` on them.
|
|
216
218
|
|
|
217
219
|
16. **EDS icons don't shrink.** Icons from `@edgeandnode/eds-react/icons` have `shrink-0` built in. Don't add it manually — it's redundant. However, if you wrap an icon in another element (e.g. a `div`), that wrapper may still need `shrink-0` if it's a child of a `flex` container.
|
|
218
220
|
|
|
@@ -523,6 +525,7 @@ Memorize this list before any UI work to avoid reimplementing existing component
|
|
|
523
525
|
| `CodeInline` | Inline code for variables, function names, etc. |
|
|
524
526
|
| `CopyButton` | Copy to clipboard. Extends `Button`. |
|
|
525
527
|
| `CurrencyInput` | Crypto amounts. Use with `useCurrencyInput` hook. |
|
|
528
|
+
| `Dark` / `Light` | Renders part of the page in the dark or light theme. |
|
|
526
529
|
| `DescriptionList` | Key-value pairs, metadata. Underused. |
|
|
527
530
|
| `Divider` | Visual separation. Horizontal or vertical. |
|
|
528
531
|
| `Icon` | Wraps icons. `src` and `alt` required. |
|
|
@@ -664,7 +667,7 @@ Some components expand to fill available width by default: `Card`, `CodeBlock`,
|
|
|
664
667
|
|
|
665
668
|
- **Address:** Handles truncation, ENS/friendly names (via `children`), copy (`'auto'` by default — shows on hover/focus). **Note:** `Address` renders its own `Avatar` using `createIdenticon(address)` — don't add a separate `Avatar` next to it or you'll get duplicate avatars.
|
|
666
669
|
- **Button & ButtonGroup:** Set `size` on the group, not nested buttons. Can mix `tertiary` and `secondary`. `orientation="vertical"` for modals/panes. `fullWidth` makes buttons equal width; `className="w-full"` makes them grow proportionally. Danger takes over primary's visual role. **Sizing:** Product UIs should default to `size="small"` for most buttons; use medium (default) only for important actions. `size="large"` is for marketing pages only.
|
|
667
|
-
- **Breadcrumbs:** Structure: `Product Name > Section > Current Page`.
|
|
670
|
+
- **Breadcrumbs:** Structure: `Product Name > Section > Current Page`. The last item defaults to `current`, so only set `current` to override that.
|
|
668
671
|
- **Chip.Group:** Requires `aria-label`.
|
|
669
672
|
- **DescriptionList:** Long values truncate with gradient fade, scroll on mobile.
|
|
670
673
|
- **Input:** For number inputs, use the `useNumberInput` hook (or `useCurrencyInput` for currency amounts) — it handles parsing, formatting, and min/max/required validation, and returns `props` to spread on `<Input>` (or `<CurrencyInput>`).
|
|
@@ -689,6 +692,7 @@ Some components carry no built-in visual styles — only behavior, semantics, or
|
|
|
689
692
|
- **`Checkbox.Area` / `Radio.Area` / `Switch.Area`** — clickable label wrappers for card-style checkable patterns; style them as `Card`s, rows, tiles, whatever the design calls for. See the "Checkable Cards" section in [patterns.md](references/patterns.md).
|
|
690
693
|
- **`Radio.Group`** — semantic wrapper around a group of `<Radio>`s. Apply your own layout (`flex`, `gap`, etc.).
|
|
691
694
|
- **`Pane`** — responsive sidebar container. Provides positioning behavior but no visual styling.
|
|
695
|
+
- **`Dark` / `Light`** — render their element (`as`, `div` by default) in the dark or light theme. They only set the color scheme and default text color, so style them as the section needs (e.g. `bg-default p-8`). `dark:`/`light:` variants on the element itself still follow the outer theme (style queries only look at ancestors), so use them on its descendants.
|
|
692
696
|
- **`ButtonOrLink`** (from `@edgeandnode/eds-react/base`) — semantic `<button>`/`<a>` wrapper with no visual styling. The other base components (`Transition`, `ExperimentalTransition`, `Presence`, `Render`, `Portal`) have transition or structural styles you shouldn't override.
|
|
693
697
|
|
|
694
698
|
### Base components
|
|
@@ -724,7 +728,7 @@ import { formatBigInt, parseBigInt, numberToBigInt, bigIntToNumber } from '@edge
|
|
|
724
728
|
```
|
|
725
729
|
|
|
726
730
|
- `cn` — concatenate class names, filtering out falsy values (like `clsx`)
|
|
727
|
-
- `useEDS` — read and change the current theme (`{ theme, activeTheme,
|
|
731
|
+
- `useEDS` — read and change the current theme (`{ theme, setTheme, activeTheme, localTheme, localThemeClass, dirProps }`); see "Theming"
|
|
728
732
|
- `useControlled` — manage controlled/uncontrolled state
|
|
729
733
|
- `useNumberInput` — numeric input validation and formatting (use with `Input`)
|
|
730
734
|
- `useCurrencyInput` — currency input validation (use with `CurrencyInput`)
|
|
@@ -166,15 +166,9 @@ Standard Tailwind spacing works (4px grid). EDS does **not** override spacing to
|
|
|
166
166
|
|
|
167
167
|
When in doubt, add more space. Dense UIs look cheap.
|
|
168
168
|
|
|
169
|
-
## Theme
|
|
169
|
+
## Theme Sections
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
| ------------ | ---------------------------------------------------------- |
|
|
173
|
-
| `eds-dark` | Forces dark color scheme for this element and descendants |
|
|
174
|
-
| `eds-light` | Forces light color scheme for this element and descendants |
|
|
175
|
-
| `eds-system` | Follows the OS color scheme preference |
|
|
176
|
-
|
|
177
|
-
These can be applied to any element to create nested theme regions. The `EDSProvider` `theme` prop sets the root theme; these classes override it locally.
|
|
171
|
+
The `EDSProvider` `theme` prop sets the root theme. To render part of the UI in another theme, wrap it in `<Dark>` or `<Light>` (e.g. `<Dark as="section">`). They can be nested, and components inside them can read the section's theme with `useEDS().localTheme`. Menus, selects, and tooltips use the theme of the section they're in, while modals use the page's theme.
|
|
178
172
|
|
|
179
173
|
## Breakpoints
|
|
180
174
|
|
package/src/EDSContext.ts
CHANGED
|
@@ -6,6 +6,12 @@ import type { DirectionProvider } from '@base-ui/react/direction-provider'
|
|
|
6
6
|
export type Theme = 'dark' | 'light' | 'system'
|
|
7
7
|
export type ResolvedTheme = Exclude<Theme, 'system'>
|
|
8
8
|
|
|
9
|
+
export const THEME_CLASSES: Record<Theme, string> = {
|
|
10
|
+
dark: 'eds-dark',
|
|
11
|
+
light: 'eds-light',
|
|
12
|
+
system: 'eds-system',
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
export type ThemePersistence =
|
|
10
16
|
| 'localStorage'
|
|
11
17
|
| 'cookie'
|
|
@@ -23,3 +29,6 @@ export interface EDSContextValue {
|
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export const EDSContext = createContext<EDSContextValue | null>(null)
|
|
32
|
+
|
|
33
|
+
/** The theme of the nearest `Dark` or `Light` ancestor, if any. */
|
|
34
|
+
export const LocalThemeContext = createContext<ResolvedTheme | undefined>(undefined)
|
package/src/EDSProvider.tsx
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ComponentProps,
|
|
11
|
+
} from 'react'
|
|
4
12
|
import { mergeProps } from '@base-ui/react'
|
|
5
13
|
import { DirectionProvider } from '@base-ui/react/direction-provider'
|
|
6
14
|
import { Tooltip } from '@base-ui/react/tooltip'
|
|
7
15
|
|
|
8
16
|
import { ButtonOrLink, type ButtonOrLinkConfigProps } from './components/base/ButtonOrLink.tsx'
|
|
9
17
|
import { Pane } from './components/Pane.tsx'
|
|
10
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
EDSContext,
|
|
20
|
+
THEME_CLASSES,
|
|
21
|
+
type ResolvedTheme,
|
|
22
|
+
type Theme,
|
|
23
|
+
type ThemePersistence,
|
|
24
|
+
} from './EDSContext.ts'
|
|
11
25
|
import { useControlled } from './hooks/useControlled.ts'
|
|
12
26
|
import { useIsHydrated } from './hooks/useIsHydrated.ts'
|
|
13
27
|
import { cn } from './utils/index.ts'
|
|
@@ -15,12 +29,6 @@ import { cn } from './utils/index.ts'
|
|
|
15
29
|
const PERSISTENCE_KEY = 'eds-active-theme'
|
|
16
30
|
const COOKIE_REGEX = new RegExp(`(?:^|; )${PERSISTENCE_KEY}=([^;]+)`)
|
|
17
31
|
|
|
18
|
-
const THEME_CLASSES: Record<Theme, string> = {
|
|
19
|
-
dark: 'eds-dark',
|
|
20
|
-
light: 'eds-light',
|
|
21
|
-
system: 'eds-system',
|
|
22
|
-
}
|
|
23
|
-
|
|
24
32
|
const VALID_THEMES: readonly Theme[] = ['dark', 'light', 'system']
|
|
25
33
|
|
|
26
34
|
function isValidTheme(value: unknown): value is Theme {
|
|
@@ -153,7 +161,7 @@ export type { EDSContextValue, ResolvedTheme, Theme, ThemePersistence } from './
|
|
|
153
161
|
* Provides global configuration and enhancements for EDS components.
|
|
154
162
|
*
|
|
155
163
|
* Note: Only one EDSProvider should be used per app, typically at the root. If you need different
|
|
156
|
-
* themes for different parts of the app, use the `
|
|
164
|
+
* themes for different parts of the app, use the `Dark` and `Light` components instead.
|
|
157
165
|
*
|
|
158
166
|
* **Next.js App Router:** add `suppressHydrationWarning` to your `<html>` tag in `app/layout.tsx`.
|
|
159
167
|
* The pre-hydration script applies the theme class to `<html>` before React hydrates (preventing a
|
|
@@ -370,6 +378,23 @@ export function EDSProvider({
|
|
|
370
378
|
}
|
|
371
379
|
}, [])
|
|
372
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Warn if build tools transpiled `light-dark()` (e.g. Lightning CSS with older browser targets).
|
|
383
|
+
* Its fallback resolves tokens on `:root`, so components inside `Dark` and `Light` would get the
|
|
384
|
+
* page's colors.
|
|
385
|
+
*/
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
const lightDarkTest = getComputedStyle(document.documentElement).getPropertyValue(
|
|
388
|
+
'--eds-light-dark-test',
|
|
389
|
+
)
|
|
390
|
+
if (lightDarkTest !== '' && !lightDarkTest.includes('light-dark')) {
|
|
391
|
+
// oxlint-disable-next-line no-console
|
|
392
|
+
console.warn(
|
|
393
|
+
"[EDSProvider] `light-dark()` was transpiled by your build tools, so components inside `Dark` and `Light` will use the page's colors. Target Chrome 123, Safari 17.5, and Firefox 120 or newer (see the `@edgeandnode/eds-react` README).",
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
}, [])
|
|
397
|
+
|
|
373
398
|
const edsContextValue = useMemo(
|
|
374
399
|
() => ({
|
|
375
400
|
theme,
|
|
@@ -9,7 +9,8 @@ import avatarPlaceholderRounded from '@edgeandnode/eds-react/assets/avatar-place
|
|
|
9
9
|
import { createIdenticon, PLACEHOLDER_ADDRESS } from '@edgeandnode/eds-utils'
|
|
10
10
|
|
|
11
11
|
import { useCSSPropsPolyfill, useEDS } from '../hooks/index.ts'
|
|
12
|
-
import { cn, getAssetUrl, getCSSPropsAttributes
|
|
12
|
+
import { cn, getAssetUrl, getCSSPropsAttributes } from '../utils/index.ts'
|
|
13
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
13
14
|
import { AvatarMeta } from './Avatar.meta.ts'
|
|
14
15
|
import { MaybeButtonOrLink, type MaybeButtonOrLinkProps } from './base/MaybeButtonOrLink.tsx'
|
|
15
16
|
import { Tooltip, type TooltipProps } from './Tooltip.tsx'
|
|
@@ -94,7 +95,7 @@ export function Avatar({
|
|
|
94
95
|
return (
|
|
95
96
|
<Tooltip
|
|
96
97
|
content={category !== 'other' ? alt : undefined}
|
|
97
|
-
{...(
|
|
98
|
+
{...getTooltipProps(tooltip)}
|
|
98
99
|
triggerProps={renderProps}
|
|
99
100
|
>
|
|
100
101
|
<Element {...elementProps}>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
createContext,
|
|
5
5
|
isValidElement,
|
|
6
6
|
useCallback,
|
|
7
|
+
useContext,
|
|
7
8
|
useMemo,
|
|
8
9
|
useRef,
|
|
9
10
|
useState,
|
|
10
11
|
type ComponentProps,
|
|
11
12
|
type CSSProperties,
|
|
12
|
-
type ReactElement,
|
|
13
13
|
} from 'react'
|
|
14
14
|
import { mergeProps } from '@base-ui/react'
|
|
15
15
|
import { useRenderCount, useThrottledEffect } from '@react-hookz/web'
|
|
@@ -21,12 +21,20 @@ import { ArrowLeftInteractiveIcon, DotsThreeIcon } from '@edgeandnode/eds-react/
|
|
|
21
21
|
|
|
22
22
|
import { useCSSPropsPolyfill, useCSSState, useEDS } from '../hooks/index.ts'
|
|
23
23
|
import { cn, getCSSPropsAttributes, splitProps } from '../utils/index.ts'
|
|
24
|
+
import { resolveLazy } from '../utils/internal/index.ts'
|
|
24
25
|
import { renderAddon, type AddonValue } from './base/Addon.tsx'
|
|
25
26
|
import { MaybeButtonOrLink, type MaybeButtonOrLinkProps } from './base/MaybeButtonOrLink.tsx'
|
|
26
27
|
import { BreadcrumbsItemMeta, BreadcrumbsMeta } from './Breadcrumbs.meta.ts'
|
|
27
28
|
import { Button } from './Button.tsx'
|
|
28
29
|
import { Menu } from './Menu.tsx'
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Whether a `Breadcrumbs.Item` is (in) the last item of `Breadcrumbs`, which makes it default to
|
|
33
|
+
* `current`. Provided through context rather than by cloning the item, so that it also works when
|
|
34
|
+
* items are wrapped in another component.
|
|
35
|
+
*/
|
|
36
|
+
const BreadcrumbsLastItemContext = createContext(false)
|
|
37
|
+
|
|
30
38
|
export interface BreadcrumbsProps
|
|
31
39
|
extends ComponentProps<'nav'>, EDSComponentProps<typeof BreadcrumbsMeta> {}
|
|
32
40
|
|
|
@@ -47,18 +55,23 @@ export function BreadcrumbsRoot({
|
|
|
47
55
|
|
|
48
56
|
const ariaLabel = passedAriaLabel !== undefined ? passedAriaLabel : 'Breadcrumbs'
|
|
49
57
|
|
|
50
|
-
// If the last `Breadcrumbs.Item` child doesn't explicitly set `current`, clone it to default
|
|
51
|
-
// `current` to `true`.
|
|
52
58
|
const processedChildren = useMemo(() => {
|
|
53
59
|
const flatChildren = flattenChildren(children)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const lastItemIndex = flatChildren.findLastIndex(
|
|
61
|
+
(child) => isValidElement(child) && resolveLazy(child.type) === BreadcrumbsItem,
|
|
62
|
+
)
|
|
63
|
+
// If items are wrapped in another component, the last element renders the last item
|
|
64
|
+
const lastIndex =
|
|
65
|
+
lastItemIndex !== -1 ? lastItemIndex : flatChildren.findLastIndex(isValidElement)
|
|
66
|
+
return flatChildren.map((child, index) =>
|
|
67
|
+
isValidElement(child) ? (
|
|
68
|
+
<BreadcrumbsLastItemContext.Provider key={child.key} value={index === lastIndex}>
|
|
69
|
+
{child}
|
|
70
|
+
</BreadcrumbsLastItemContext.Provider>
|
|
71
|
+
) : (
|
|
72
|
+
child
|
|
73
|
+
),
|
|
74
|
+
)
|
|
62
75
|
}, [children])
|
|
63
76
|
|
|
64
77
|
const hiddenContainerRef = useRef<HTMLDivElement>(null)
|
|
@@ -249,8 +262,9 @@ export declare namespace BreadcrumbsItemProps {
|
|
|
249
262
|
addonBefore?: AddonValue | undefined
|
|
250
263
|
/**
|
|
251
264
|
* Whether this breadcrumb item represents the current page. Defaults to `true` for the last
|
|
252
|
-
* `Breadcrumbs.Item` child of `Breadcrumbs
|
|
253
|
-
*
|
|
265
|
+
* `Breadcrumbs.Item` child of `Breadcrumbs` (or the item rendered by its last child, if items
|
|
266
|
+
* are wrapped in another component), and `false` for any other item. Set explicitly to override
|
|
267
|
+
* the default.
|
|
254
268
|
*/
|
|
255
269
|
current?: boolean | undefined
|
|
256
270
|
}
|
|
@@ -267,11 +281,13 @@ export type BreadcrumbsItemProps =
|
|
|
267
281
|
|
|
268
282
|
export function BreadcrumbsItem({
|
|
269
283
|
addonBefore,
|
|
270
|
-
current
|
|
284
|
+
current: passedCurrent,
|
|
271
285
|
hideLabel,
|
|
272
286
|
children,
|
|
273
287
|
...props
|
|
274
288
|
}: BreadcrumbsItemProps) {
|
|
289
|
+
const isLastItem = useContext(BreadcrumbsLastItemContext)
|
|
290
|
+
const current = passedCurrent ?? isLastItem
|
|
275
291
|
const { rootProps, nestedProps } = splitProps(props)
|
|
276
292
|
|
|
277
293
|
const [stateRef, state] = useCSSState({
|
|
@@ -7,7 +7,8 @@ import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
|
7
7
|
import { LoadingIcon } from '@edgeandnode/eds-react/icons'
|
|
8
8
|
|
|
9
9
|
import { useCSSPropsPolyfill, useCSSState, useEDS } from '../hooks/index.ts'
|
|
10
|
-
import { cn, getCSSPropsAttributes
|
|
10
|
+
import { cn, getCSSPropsAttributes } from '../utils/index.ts'
|
|
11
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
11
12
|
import { renderAddon, type AddonValue } from './base/Addon.tsx'
|
|
12
13
|
import { ButtonOrLink, type ButtonOrLinkProps } from './base/ButtonOrLink.tsx'
|
|
13
14
|
import { ButtonMeta } from './Button.meta.ts'
|
|
@@ -132,7 +133,7 @@ export function Button({
|
|
|
132
133
|
render={(renderProps, { Element, elementProps }) => (
|
|
133
134
|
<Tooltip
|
|
134
135
|
closeOnClick={typeof props.checked === 'boolean' ? false : undefined} // If this is a toggle button, keep the tooltip open on click by default
|
|
135
|
-
{...(
|
|
136
|
+
{...getTooltipProps(tooltip)}
|
|
136
137
|
triggerProps={renderProps}
|
|
137
138
|
>
|
|
138
139
|
<Element {...elementProps}>
|
|
@@ -50,15 +50,6 @@ import { TabSetContext } from './TabSet.context.ts'
|
|
|
50
50
|
import { TabSet, type TabSetTabProps } from './TabSet.tsx'
|
|
51
51
|
import { ToggleButton } from './ToggleButton.tsx'
|
|
52
52
|
|
|
53
|
-
// Can't import `@shikijs/vscode-textmate`'s `FontStyle` enum due to `verbatimModuleSyntax`
|
|
54
|
-
const FontStyle = {
|
|
55
|
-
NotSet: -1,
|
|
56
|
-
None: 0,
|
|
57
|
-
Italic: 1,
|
|
58
|
-
Bold: 2,
|
|
59
|
-
Underline: 4,
|
|
60
|
-
} as const
|
|
61
|
-
|
|
62
53
|
const THEMES = {
|
|
63
54
|
dark: 'night-owl',
|
|
64
55
|
light: 'min-light',
|
|
@@ -166,7 +157,7 @@ export function CodeBlockRoot({
|
|
|
166
157
|
children,
|
|
167
158
|
...props
|
|
168
159
|
}: CodeBlockProps) {
|
|
169
|
-
|
|
160
|
+
useEDS()
|
|
170
161
|
const tabsContext = useContext(CodeBlockTabsContext)
|
|
171
162
|
const hasAncestorTabs = tabsContext !== null
|
|
172
163
|
const hasAncestorHeader = useContext(CodeBlockHeaderContext) !== false
|
|
@@ -209,7 +200,6 @@ export function CodeBlockRoot({
|
|
|
209
200
|
highlights,
|
|
210
201
|
secrets,
|
|
211
202
|
language,
|
|
212
|
-
activeTheme,
|
|
213
203
|
highlighter,
|
|
214
204
|
hasAncestorHeader,
|
|
215
205
|
})
|
|
@@ -363,7 +353,18 @@ export function CodeBlockRoot({
|
|
|
363
353
|
<span className="select-none"> </span>
|
|
364
354
|
) : (
|
|
365
355
|
<span
|
|
356
|
+
/**
|
|
357
|
+
* `light-dark()` only works for colors, so the font styles use the `dark:`
|
|
358
|
+
* variant (and highlighted tokens need `i:font-bold` to override the weight).
|
|
359
|
+
*/
|
|
366
360
|
className="
|
|
361
|
+
*:font-(--shiki-light-font-weight)
|
|
362
|
+
*:text-[light-dark(var(--shiki-light),var(--shiki-dark))]
|
|
363
|
+
*:[font-style:var(--shiki-light-font-style)]
|
|
364
|
+
*:[text-decoration:var(--shiki-light-text-decoration)]
|
|
365
|
+
dark:*:font-(--shiki-dark-font-weight)
|
|
366
|
+
dark:*:[font-style:var(--shiki-dark-font-style)]
|
|
367
|
+
dark:*:[text-decoration:var(--shiki-dark-text-decoration)]
|
|
367
368
|
@prop-wrap-true/code-block:wrap-anywhere
|
|
368
369
|
@prop-wrap-true/code-block:whitespace-pre-wrap
|
|
369
370
|
"
|
|
@@ -379,23 +380,18 @@ export function CodeBlockRoot({
|
|
|
379
380
|
data-whitespace={chunk.content.trim() === '' || undefined}
|
|
380
381
|
data-highlighted={chunk.highlighted || undefined}
|
|
381
382
|
className={cn(
|
|
382
|
-
chunk.
|
|
383
|
-
chunk.fontStyle === FontStyle.Italic ? 'italic' : undefined,
|
|
384
|
-
chunk.fontStyle === FontStyle.Underline ? 'underline' : undefined,
|
|
385
|
-
chunk.highlighted ? `z-10 bg-code-highlighted py-1 font-bold
|
|
383
|
+
chunk.highlighted ? `z-10 bg-code-highlighted py-1
|
|
386
384
|
not-has-[+[data-highlighted]]:-me-1
|
|
387
385
|
not-has-[+[data-highlighted]]:rounded-e-4
|
|
388
386
|
not-has-[+[data-highlighted]]:pe-1
|
|
389
387
|
not-[[data-highlighted]+*]:-ms-1
|
|
390
388
|
not-[[data-highlighted]+*]:rounded-s-4
|
|
391
|
-
not-[[data-highlighted]+*]:ps-1
|
|
389
|
+
not-[[data-highlighted]+*]:ps-1
|
|
390
|
+
i:font-bold` : 'z-20',
|
|
392
391
|
// Prevent wrapping between the indentation and the code
|
|
393
392
|
chunkIndex === 1 ? '[[data-whitespace]+*]:break-all' : undefined,
|
|
394
393
|
)}
|
|
395
|
-
style={
|
|
396
|
-
color: chunk.color,
|
|
397
|
-
backgroundColor: chunk.bgColor,
|
|
398
|
-
}}
|
|
394
|
+
style={chunk.htmlStyle}
|
|
399
395
|
>
|
|
400
396
|
{chunkContent}
|
|
401
397
|
</span>
|
|
@@ -423,7 +419,6 @@ function useCodeLines({
|
|
|
423
419
|
highlights,
|
|
424
420
|
secrets,
|
|
425
421
|
language,
|
|
426
|
-
activeTheme,
|
|
427
422
|
highlighter,
|
|
428
423
|
hasAncestorHeader,
|
|
429
424
|
}: {
|
|
@@ -432,7 +427,6 @@ function useCodeLines({
|
|
|
432
427
|
highlights: string[]
|
|
433
428
|
secrets: string[]
|
|
434
429
|
language: Language
|
|
435
|
-
activeTheme: 'dark' | 'light'
|
|
436
430
|
highlighter: Highlighter
|
|
437
431
|
hasAncestorHeader: boolean
|
|
438
432
|
}): { lines: CodeLine[]; codeToCopy: string } {
|
|
@@ -461,7 +455,9 @@ function useCodeLines({
|
|
|
461
455
|
if (hasAncestorHeader) return []
|
|
462
456
|
|
|
463
457
|
const { tokens } = highlighter.codeToTokens(code, {
|
|
464
|
-
|
|
458
|
+
// Both themes' styles become CSS variables, so tokens follow the local color scheme
|
|
459
|
+
themes: THEMES,
|
|
460
|
+
defaultColor: false,
|
|
465
461
|
lang: LANGUAGES.includes(language) ? language : 'text',
|
|
466
462
|
})
|
|
467
463
|
|
|
@@ -526,7 +522,7 @@ function useCodeLines({
|
|
|
526
522
|
|
|
527
523
|
return { chunks, style: lineStyle }
|
|
528
524
|
})
|
|
529
|
-
}, [highlighter, passedCode,
|
|
525
|
+
}, [highlighter, passedCode, language, highlights, secrets, diff])
|
|
530
526
|
|
|
531
527
|
return { lines, codeToCopy }
|
|
532
528
|
}
|
|
@@ -8,7 +8,8 @@ import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
|
8
8
|
import { CopyInteractiveIcon } from '@edgeandnode/eds-react/icons'
|
|
9
9
|
|
|
10
10
|
import { useEDS } from '../hooks/index.ts'
|
|
11
|
-
import { cn
|
|
11
|
+
import { cn } from '../utils/index.ts'
|
|
12
|
+
import { getTooltipProps } from '../utils/internal/index.ts'
|
|
12
13
|
import { Button, type ButtonProps } from './Button.tsx'
|
|
13
14
|
import type { CopyButtonMeta } from './CopyButton.meta.ts'
|
|
14
15
|
import { Tooltip } from './Tooltip.tsx'
|
|
@@ -49,7 +50,7 @@ export function CopyButton({
|
|
|
49
50
|
|
|
50
51
|
const hasChildren = children != null
|
|
51
52
|
const copyIcon = <CopyInteractiveIcon copied={copied} />
|
|
52
|
-
const tooltipProps =
|
|
53
|
+
const tooltipProps = getTooltipProps(tooltip)
|
|
53
54
|
const [tooltipOpen, setTooltipOpen] = useState(false)
|
|
54
55
|
|
|
55
56
|
return (
|
package/src/components/Icon.tsx
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
createElement,
|
|
5
|
-
isValidElement,
|
|
6
|
-
type ComponentProps,
|
|
7
|
-
type ComponentType,
|
|
8
|
-
type ReactElement,
|
|
9
|
-
} from 'react'
|
|
3
|
+
import { createElement, type ComponentProps, type ComponentType, type ReactElement } from 'react'
|
|
10
4
|
import { mergeProps } from '@base-ui/react'
|
|
11
5
|
|
|
12
6
|
import type { EDSComponentProps } from '@edgeandnode/eds-css'
|
|
13
7
|
|
|
14
8
|
import { useEDS } from '../hooks/index.ts'
|
|
15
|
-
import { cn, getCSSPropsAttributes } from '../utils/index.ts'
|
|
9
|
+
import { cn, getCSSPropsAttributes, isComponentType } from '../utils/index.ts'
|
|
16
10
|
import { IconMeta } from './Icon.meta.ts'
|
|
17
11
|
import { useTooltipCollector } from './Tooltip.tsx'
|
|
18
12
|
|
|
@@ -37,7 +31,7 @@ export interface IconPropsWithSrc extends IconProps {
|
|
|
37
31
|
export function Icon({ src, alt, size, color, ...props }: IconPropsWithSrc) {
|
|
38
32
|
useEDS()
|
|
39
33
|
|
|
40
|
-
const iconContent =
|
|
34
|
+
const iconContent = isComponentType(src) ? createElement(src) : src
|
|
41
35
|
const collectTooltipContent = useTooltipCollector()
|
|
42
36
|
|
|
43
37
|
if (collectTooltipContent && alt) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps, ElementType } from 'react'
|
|
4
|
+
import { mergeProps } from '@base-ui/react'
|
|
5
|
+
|
|
6
|
+
import { LocalThemeContext, THEME_CLASSES, type ResolvedTheme } from '../EDSContext.ts'
|
|
7
|
+
|
|
8
|
+
export interface LocalThemeProps extends ComponentProps<'div'> {
|
|
9
|
+
/** @default 'div' */
|
|
10
|
+
as?: ElementType | undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function LocalTheme({
|
|
14
|
+
theme,
|
|
15
|
+
as: Element = 'div',
|
|
16
|
+
...props
|
|
17
|
+
}: LocalThemeProps & { theme: ResolvedTheme }) {
|
|
18
|
+
return (
|
|
19
|
+
<LocalThemeContext.Provider value={theme}>
|
|
20
|
+
<Element {...mergeProps({ className: THEME_CLASSES[theme] }, props)} />
|
|
21
|
+
</LocalThemeContext.Provider>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Renders its content in the dark theme, whatever the page's theme is. Components that need to know
|
|
27
|
+
* the theme in JavaScript can read it with `useEDS().localTheme`.
|
|
28
|
+
*/
|
|
29
|
+
export function Dark(props: LocalThemeProps) {
|
|
30
|
+
return <LocalTheme theme="dark" {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders its content in the light theme, whatever the page's theme is. Components that need to
|
|
35
|
+
* know the theme in JavaScript can read it with `useEDS().localTheme`.
|
|
36
|
+
*/
|
|
37
|
+
export function Light(props: LocalThemeProps) {
|
|
38
|
+
return <LocalTheme theme="light" {...props} />
|
|
39
|
+
}
|