From bd9a30de6f329b69f04a2f420070cee26c7d3f26 Mon Sep 17 00:00:00 2001 From: m2rt Date: Fri, 9 Oct 2026 15:36:26 +0300 Subject: [PATCH 1/4] feat(icon): Allow Loading A Single Icon Font Style #41 Bundle core's index-without-icons and ship index-without-icons.css plus icons/{outlined,rounded,sharp,all}.css next to index.css, which still declares all three Material Symbols styles. Pin @tedi-design-system/core to 6.11.2. Document the single-style setup and the PWA precache note in Get started. --- .storybook/preview.tsx | 1 + package-lock.json | 8 ++-- package.json | 8 ++-- skills/tedi-react/SKILL.md | 9 ++++- .../components/base/icon/icon.stories.tsx | 6 ++- src/tedi/components/base/icon/icon.tsx | 3 +- src/tedi/docs/getStarted.mdx | 40 +++++++++++++++++++ src/tedi/styles/index.scss | 4 +- vite.lib.config.ts | 27 ++++++++++++- 9 files changed, 94 insertions(+), 12 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index e45b4e13f..789b391bf 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -5,6 +5,7 @@ import StorybookDecorator from './storybook-decorator'; import '../src/tedi/styles/index.scss'; import '../src/community/styles/index.scss'; +import '../node_modules/@tedi-design-system/core/icons/_all.scss'; import '../node_modules/@tedi-design-system/core/tedi-storybook-styles.scss'; import { PrintingProvider } from '../src/tedi/providers/printing-provider'; diff --git a/package-lock.json b/package-lock.json index 231e627f5..d43e3f676 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@mui/material": "^5.15.13", "@mui/x-date-pickers": "^5.0.20", "@tanstack/react-table": "^8.13.2", - "@tedi-design-system/core": "^6.9.0", + "@tedi-design-system/core": "6.11.2", "classnames": "^2.5.1", "draft-js": "^0.11.7", "draftjs-md-converter": "^1.5.2", @@ -10560,9 +10560,9 @@ } }, "node_modules/@tedi-design-system/core": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@tedi-design-system/core/-/core-6.9.0.tgz", - "integrity": "sha512-+RPnP8d9xcgabfFODJRd472JPzRIT8OR6XMd67RtbKyLOMNHKl8RwT22DCKcMnH4FEMv/v7Uay1jwgWL9Wp/Rg==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@tedi-design-system/core/-/core-6.11.2.tgz", + "integrity": "sha512-iYKcXyJV1KJCpcjwxN4bQVTi9jnIkvp/5bUtR8jRes2BMvTpx7PKuoFnOnjiBZmRpm2hhgiqzhP/SpvwJCRR9A==", "engines": { "node": ">=24.0.0", "npm": ">=11.0.0" diff --git a/package.json b/package.json index ac3f88743..63bd86c9a 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,14 @@ "import": "./community.es.js", "require": "./community.cjs.js" }, - "./index.css": "./index.css" + "./index.css": "./index.css", + "./index-without-icons.css": "./index-without-icons.css", + "./icons/*.css": "./icons/*.css" }, "scripts": { "prepare": "husky", "start": "storybook dev -p 4400 -c .storybook", - "build": "rm -rf dist && vite build --config vite.lib.config.ts && replace-in-file //fonts//g \"./fonts/\" dist/index.css --isRegex", + "build": "rm -rf dist && vite build --config vite.lib.config.ts && replace-in-file //fonts//g \"./fonts/\" dist/index.css dist/index-without-icons.css --isRegex", "build:sb": "storybook build -c .storybook -o dist/storybook-static --stats-json && replace-in-file //fonts//g \"../fonts/\" \"dist/storybook-static/assets/*.css\" --isRegex", "lint": "npx stylelint \"src/**/*.scss\" --fix && eslint \"src/**/*.{ts,tsx}\" --no-ignore --fix", "lint:ci": "npx stylelint \"src/**/*.scss\" --quiet && npx eslint . --quiet", @@ -47,7 +49,7 @@ "@mui/material": "^5.15.13", "@mui/x-date-pickers": "^5.0.20", "@tanstack/react-table": "^8.13.2", - "@tedi-design-system/core": "^6.9.0", + "@tedi-design-system/core": "6.11.2", "classnames": "^2.5.1", "draft-js": "^0.11.7", "draftjs-md-converter": "^1.5.2", diff --git a/skills/tedi-react/SKILL.md b/skills/tedi-react/SKILL.md index 7973820d7..1f5768a26 100644 --- a/skills/tedi-react/SKILL.md +++ b/skills/tedi-react/SKILL.md @@ -132,6 +132,13 @@ function App() { import '@tedi-design-system/react/index.css'; ``` +`index.css` declares all three Material Symbols fonts. When the app uses one `Icon` `type` (recommended), load only that font instead: + +```tsx +import '@tedi-design-system/react/index-without-icons.css'; +import '@tedi-design-system/react/icons/outlined.css'; // or rounded.css / sharp.css +``` + Or in SCSS: ```scss @use '@tedi-design-system/core/scss' as tedi; @@ -278,7 +285,7 @@ sendNotification({ type: 'success', title: 'Done', children: 'Task completed' }) A handful of mistakes account for most TEDI integration issues. Avoid them up front: -- **Import from `/tedi` or `/community`, never the package root.** `@tedi-design-system/react` is not a valid import path — the package has explicit entry points (`@tedi-design-system/react/tedi`, `@tedi-design-system/react/community`, `@tedi-design-system/react/index.css`). Importing from the root will fail or silently miss types. +- **Import from `/tedi` or `/community`, never the package root.** `@tedi-design-system/react` is not a valid import path — the package has explicit entry points (`@tedi-design-system/react/tedi`, `@tedi-design-system/react/community`, `@tedi-design-system/react/index.css`, `@tedi-design-system/react/index-without-icons.css`, `@tedi-design-system/react/icons/*.css`). Importing from the root will fail or silently miss types. - **Prefer TEDI-Ready over Community whenever possible.** Several Community components are deprecated in favor of TEDI-Ready equivalents, and the set with no TEDI-Ready alternative yet shifts over time — check the manifest's `status` field and the component JSDoc for the current deprecation status before reaching into Community. See [references/components.md](references/components.md). - **Always pass `id` to form controls.** `TextField`, `Select`, `Checkbox`, `Radio`, etc. require it — it's how the label/helper/aria wiring works. There is no auto-generated fallback. - **Use design tokens, not hardcoded colors.** Prefer the semantic roles (`var(--general-surface-primary)`, `var(--general-text-secondary)`) and drop to a `--tedi-*` primitive only when no semantic token fits. This is what makes theme switching and brand overrides work. Look names up in `node_modules/@tedi-design-system/core/tokens.json` rather than recalling them — see [references/theming.md](references/theming.md). diff --git a/src/tedi/components/base/icon/icon.stories.tsx b/src/tedi/components/base/icon/icon.stories.tsx index 3703d30c3..4593e06ba 100644 --- a/src/tedi/components/base/icon/icon.stories.tsx +++ b/src/tedi/components/base/icon/icon.stories.tsx @@ -11,7 +11,11 @@ import { Icon, IconProps } from './icon'; * Zeroheight ↗
* Official Google Material Icons homepage icons ↗
* Material Icons Figma ↗
- * Figma Material Symbols plugin ↗ + * Figma Material Symbols plugin ↗
+ * + * Use a single icon `type` throughout your application. Each type is a separate font file, so import + * `index-without-icons.css` together with only the style you use (e.g. `icons/outlined.css`) instead of + * `index.css`. See [Get started](/docs/documentation-get-started--get-started) for details and the PWA precache note. */ const meta: Meta = { title: 'Tedi-Ready/Base/Icon', diff --git a/src/tedi/components/base/icon/icon.tsx b/src/tedi/components/base/icon/icon.tsx index 22ae45227..6cffc4e99 100644 --- a/src/tedi/components/base/icon/icon.tsx +++ b/src/tedi/components/base/icon/icon.tsx @@ -33,7 +33,8 @@ export interface IconSharedProps { className?: string; /** * Type of icon - * It is recommended to only use one type throughout your app + * It is recommended to only use one type throughout your app, and to load only that type's font + * (`icons/.css` with `index-without-icons.css`) instead of all three via `index.css`. * @default outlined */ type?: IconType; diff --git a/src/tedi/docs/getStarted.mdx b/src/tedi/docs/getStarted.mdx index aab226647..c368bcdac 100644 --- a/src/tedi/docs/getStarted.mdx +++ b/src/tedi/docs/getStarted.mdx @@ -98,3 +98,43 @@ currently not supported — if you need to, please reach out on Slack. When using SSR you can pass the `defaultServerBreakpoint` prop to every component that uses [BreakpointProps](/docs/helpers-usebreakpointprops--usebreakpointprops). This lets you declare which breakpoint the component should render at on the server, avoiding layout shifts. + +### Icon fonts + +`Icon` renders [Material Symbols](https://fonts.google.com/icons) in three styles (`outlined`, +`rounded`, `sharp`), each a separate font file of roughly 350–430 KB. `index.css` declares all three, +so every style stays available. Use **one icon style** throughout your application and load only that +font: + +```tsx +// Everything except the icon fonts… +import '@tedi-design-system/react/index-without-icons.css'; +// …plus the one style you use (outlined is the Icon default). +import '@tedi-design-system/react/icons/outlined.css'; +``` + +| Import | Font file loaded | +| ---------------------------------------------- | --------------------------------- | +| `@tedi-design-system/react/icons/outlined.css` | `material-symbols-outlined.woff2` | +| `@tedi-design-system/react/icons/rounded.css` | `material-symbols-rounded.woff2` | +| `@tedi-design-system/react/icons/sharp.css` | `material-symbols-sharp.woff2` | +| `@tedi-design-system/react/icons/all.css` | all three | + +An `Icon` whose `type` has no imported style renders its ligature text instead of the glyph. + +Browsers only download an icon font once a glyph uses it, so in a regular web app the unused styles +cost nothing at runtime. In a **PWA**, the service worker precaches every matching build asset, +including fonts your CSS never references. Exclude the unused styles from the precache, for example +with `vite-plugin-pwa`: + +```ts +VitePWA({ + workbox: { + globPatterns: ['**/*.{js,css,html,woff2}'], + globIgnores: ['**/material-symbols-{rounded,sharp}.woff2'], + }, +}); +``` + +See the [core icons guide](https://github.com/TEDI-Design-System/core/blob/rc/docs/icons.md) for +SCSS usage and custom font paths. diff --git a/src/tedi/styles/index.scss b/src/tedi/styles/index.scss index 9a50cfb60..1f4691f8f 100644 --- a/src/tedi/styles/index.scss +++ b/src/tedi/styles/index.scss @@ -1 +1,3 @@ -@use '@tedi-design-system/core/index'; +// Icon fonts are not bundled here: the build appends them to index.css and ships per-style +// entries under icons/, so consumers can load a single Material Symbols style. See react#41. +@use '@tedi-design-system/core/index-without-icons'; diff --git a/vite.lib.config.ts b/vite.lib.config.ts index 8fe5e062e..290e082c5 100644 --- a/vite.lib.config.ts +++ b/vite.lib.config.ts @@ -1,9 +1,10 @@ /* eslint-disable prettier/prettier */ import react from '@vitejs/plugin-react'; +import { appendFileSync, copyFileSync, readFileSync } from 'fs'; import { resolve } from 'path'; import preserveDirectives from 'rollup-plugin-preserve-directives'; import { visualizer } from 'rollup-plugin-visualizer'; -import { defineConfig, UserConfig } from 'vite'; +import { defineConfig, Plugin, UserConfig } from 'vite'; import checker from 'vite-plugin-checker'; import dts from 'vite-plugin-dts'; import { viteStaticCopy } from 'vite-plugin-static-copy'; @@ -12,6 +13,25 @@ import packageJson from './package.json' with { type: 'json' }; const externalDependencies = [...Object.keys(packageJson.peerDependencies), 'react/jsx-runtime']; +const coreIconsDir = './node_modules/@tedi-design-system/core/icons'; + +/** + * The bundled CSS has no icon fonts (styles use core's index-without-icons). Keep index.css + * backward compatible by appending all three Material Symbols styles to it, and ship the + * icon-less bundle as index-without-icons.css so apps can pair it with one icons/