Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e4ae619
feat: make `@ui5/webcomponents-fiori` peer-dependency optional
Lukas742 Aug 11, 2025
afd4e49
run `yarn i`
Lukas742 Aug 11, 2025
278641a
use IIFE for fiori assets import
Lukas742 Aug 11, 2025
ffcd750
Update generateI18n.mjs
Lukas742 Aug 11, 2025
75fa664
Merge branch 'main' into chore/fiori-optional
Lukas742 Aug 11, 2025
07b216f
phrasing
Lukas742 Aug 11, 2025
2996b71
fix typo
Lukas742 Aug 11, 2025
554b9c8
phrasing
Lukas742 Aug 11, 2025
c0439de
Merge branch 'main' into chore/fiori-optional
Lukas742 Aug 27, 2025
ec466f4
create script
Lukas742 Aug 27, 2025
4b49f0f
refactor(charts): use `index.tsx` as component root file
Lukas742 Aug 27, 2025
472c828
base pkg exports paths & mark internal utils
Lukas742 Aug 27, 2025
e3a4a05
chart pkg exports paths
Lukas742 Aug 27, 2025
4c21733
fix base `index.ts`
Lukas742 Aug 27, 2025
f008e17
compat pkg exports paths
Lukas742 Aug 27, 2025
fe04c6b
main pkg export paths
Lukas742 Aug 27, 2025
c398e88
types & package.json script
Lukas742 Aug 27, 2025
a64452d
Merge branch 'main' into feat/exports-path
Lukas742 Aug 28, 2025
0592a68
use only file path imports
Lukas742 Aug 28, 2025
380c37f
Merge branch 'main' into feat/exports-path
Lukas742 Aug 29, 2025
560b869
docs: replace root with subpath imports
Lukas742 Aug 29, 2025
1659a82
Update Import.tsx
Lukas742 Sep 1, 2025
7cd6203
Update README.md
Lukas742 Sep 3, 2025
ed9d87e
Update DocsHeader.tsx
Lukas742 Sep 4, 2025
509385d
Merge branch 'main' into docs/subpath-imports
Lukas742 Sep 4, 2025
42c7de7
Merge branch 'main' into docs/subpath-imports
MarcusNotheis Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .storybook/components/DocsHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const InfoTable = ({
const supportsClipboardApi = typeof ClipboardItem !== 'undefined';

const handleCopy = async (e) => {
const importStatementCell = e.currentTarget.parentElement.querySelector('[data-import]');
const importStatementCell = e.currentTarget.parentElement.parentElement.querySelector('[data-import-cell]');
if (supportsClipboardApi && importStatementCell) {
const html = new Blob([importStatementCell.outerHTML], { type: 'text/html' });
const text = new Blob([importStatementCell.outerText], { type: 'text/plain' });
Expand All @@ -89,7 +89,7 @@ export const InfoTable = ({
<Label>Usage</Label>
</th>
<td data-import-cell={supportsClipboardApi}>
<Import moduleNames={[moduleName]} componentId={preparedMeta.componentId} />
<Import moduleName={moduleName} componentId={preparedMeta.componentId} />
{supportsClipboardApi && (
<Button
design={ButtonDesign.Transparent}
Expand Down Expand Up @@ -142,7 +142,12 @@ export const InfoTable = ({
<Label>Subcomponents</Label>
</th>
<td data-import-cell={supportsClipboardApi}>
<Import moduleNames={subComps} componentId={preparedMeta.componentId} />
{subComps.map((subComp) => (
<>
<Import key={subComp} moduleName={subComp} componentId={preparedMeta.componentId} />
<br />
</>
))}
{supportsClipboardApi && (
<Button
design={ButtonDesign.Transparent}
Expand Down
82 changes: 20 additions & 62 deletions .storybook/components/Import.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Fragment } from 'react';

interface ImportStatementPropTypes {
/**
* Names of module/component (e.g. "Button")
* Name of module/component (e.g. "Button")
*/
moduleNames?: string[];
moduleName?: string;
/**
* Package name (e.g. "@ui5/webcomponents-react")
*/
Expand All @@ -24,34 +22,23 @@ interface FromPathPropTypes extends Pick<ImportStatementPropTypes, 'packageName'
deepPath?: null | undefined | DeepPath;
}

function FromPath({ packageName, deepPath }: FromPathPropTypes) {
function FromPath({ packageName }: FromPathPropTypes) {
return (
<>
<span style={{ color: 'rgb(0, 0, 136)', fontSize: '14px' }}>from</span>
<span> </span>
<span style={{ color: 'rgb(0, 136, 0)', fontSize: '14px' }}>
{deepPath ? packageName.slice(0, -1) : packageName}
{deepPath && deepPath.path}
{deepPath && "'"}
</span>
<span style={{ color: 'rgb(0, 136, 0)', fontSize: '14px' }}>{packageName}</span>
<span style={{ fontSize: '14px' }}>;</span>
{deepPath && <br />}
</>
);
}

FromPath.displayName = 'FromPath';

export const ImportStatement = ({ moduleNames, packageName, defaultImport }: ImportStatementPropTypes) => {
if (!moduleNames) {
export const ImportStatement = ({ moduleName, packageName, defaultImport }: ImportStatementPropTypes) => {
if (!moduleName) {
return null;
}
const isCompat = packageName.includes('compat');
const paths = isCompat
? moduleNames.map((item) => {
return { path: `/dist/components/${item}/index.js`, moduleName: item };
})
: [null];

return (
<pre
Expand All @@ -68,45 +55,16 @@ export const ImportStatement = ({ moduleNames, packageName, defaultImport }: Imp
}}
>
<code style={{ whiteSpace: 'pre' }}>
{!paths[0] && <span style={{ color: 'rgb(0, 0, 136)', fontSize: '14px' }}>import</span>}
{paths.map((deepPath) => {
if (!deepPath) {
return (
<span style={{ fontSize: '14px' }} key="0">
{!defaultImport && ' {'}
{moduleNames.length > 2 ? (
<>
{moduleNames.map((item) => {
return (
<Fragment key={item}>
<br />
&nbsp;&nbsp;
{item},
</Fragment>
);
})}
<br />
</>
) : (
<>&nbsp;{moduleNames.join(', ')}&nbsp;</>
)}
{!defaultImport && '} '}
</span>
);
} else {
return (
<Fragment key={deepPath.path}>
<span style={{ color: 'rgb(0, 0, 136)', fontSize: '14px' }}>import</span>
<span style={{ fontSize: '14px' }}>
{' '}
{'{'}&nbsp;{deepPath.moduleName}&nbsp;{'}'}{' '}
</span>
<FromPath packageName={packageName} deepPath={deepPath} />
</Fragment>
);
}
})}
{!paths[0] && <FromPath packageName={packageName} />}
<span style={{ color: 'rgb(0, 0, 136)', fontSize: '14px' }}>import</span>
<span style={{ fontSize: '14px' }}>
{!defaultImport && ' {'}
<>
&nbsp;&nbsp;
{moduleName}{' '}
</>
{!defaultImport && '} '}
</span>
<FromPath packageName={packageName} />
</code>
</pre>
);
Expand All @@ -118,19 +76,19 @@ interface ImportProps {
/**
* Names of module/component (e.g. "Button")
*/
moduleNames: ImportStatementPropTypes['moduleNames'];
moduleName: ImportStatementPropTypes['moduleName'];
componentId: string;
}

export const Import = (props: ImportProps) => {
const { componentId, moduleNames } = props;
const { componentId, moduleName } = props;
const isChart = componentId.startsWith('charts-');
const isCompat = componentId.startsWith('legacy-');

return (
<ImportStatement
moduleNames={moduleNames}
packageName={`'@ui5/webcomponents-react${isChart ? '-charts' : isCompat ? '-compat' : ''}'`}
moduleName={moduleName}
packageName={`'@ui5/webcomponents-react${isChart ? '-charts' : isCompat ? '-compat' : ''}/${moduleName}'`}
/>
);
};
Expand Down
11 changes: 6 additions & 5 deletions .storybook/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type * as CEM from '@ui5/webcomponents-tools/lib/cem/types-internal';
import { useMemo } from 'react';
// @ts-expect-error: storybook can handle this
import cemAi from './custom-element-manifests/ai.json';
Expand Down Expand Up @@ -50,16 +51,16 @@ const replaceSubComps = {
TableHeaderCellActionBase: ['TableHeaderCellActionAI'],
};

function findSubComponentsRecursively(moduleName: string, cem: any): string[] {
function findSubComponentsRecursively(moduleName: string, cem: CEM.Package): string[] {
const subComponentsSet = new Set<string>();

const recursiveFind = (moduleName: string) => {
const module = cem?.modules.find((module: any) => module.path === `dist/${moduleName}.js`);
const module = cem?.modules.find((module: CEM.Module) => module.path === `dist/${moduleName}.js`);
if (!module) return;

module.declarations.forEach((decl: any) => {
(decl.slots || []).forEach((slot: any) => {
(slot._ui5type?.references || []).forEach((ref: any) => {
module.declarations.forEach((decl: CEM.CustomElement) => {
(decl.slots || []).forEach((slot: CEM.Slot) => {
(slot._ui5type?.references || []).forEach((ref: CEM.TypeReference) => {
const name = ref.name.replace(/^I([A-Z])/g, '$1');
const subComps = replaceSubComps[name] || [name];
subComps.forEach((subComp: string) => {
Expand Down
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,30 @@ Since version `v2.14.0` of `@ui5/webcomponents-react`, `@ui5/webcomponents-fiori
- You want to use the [VariantManagement](https://sap.github.io/ui5-webcomponents-react/v2/?path=/docs/inputs-variantmanagement--docs) component.
- You import anything from the `@ui5/webcomponents-fiori` package.

**Note:** Most popular bundlers enable tree-shaking for production builds, so there’s no difference in the final bundle size between the recommended and minimal installations.

```sh
npm install @ui5/webcomponents-react @ui5/webcomponents
```

**Note:** Most popular bundlers enable tree-shaking for production builds, so there’s no difference in the final bundle size between the recommended and minimal installations.

> ⚠️ **Warning**
>
> If your bundler does **not** support tree-shaking, you must use **subpath imports**.
>
> Otherwise, since `@ui5/webcomponents-react` re-exports all components, **every component** (including those that depend on the `@ui5/webcomponents-fiori` package) will be included in your bundle, which will lead to errors due to the missing module.
>
> **✅ Do:**
>
> ```tsx
> import { Button } from '@ui5/webcomponents-react/Button';
> ```
>
> **❌ Don’t:**
>
> ```tsx
> import { Button } from '@ui5/webcomponents-react';
> ```

## End of Support for Version 1.x

The support for version 1.x of `ui5-webcomponents-react` has ended on **July 1, 2025**. We recommend migrating to version 2.x as soon as possible. For more information, please refer to our [Migration Guide](https://sap.github.io/ui5-webcomponents-react/v2/?path=/docs/migration-guide--docs).
Expand Down Expand Up @@ -135,7 +153,7 @@ You can find a curated list of project templates and examples on our [Project Te
2. Import the `ThemeProvider` component and wrap your root component with it:

```tsx
import { ThemeProvider } from '@ui5/webcomponents-react';
import { ThemeProvider } from '@ui5/webcomponents-react/ThemeProvider';
...
createRoot(document.getElementById('root') as HTMLElement).render(
<ThemeProvider>
Expand All @@ -148,7 +166,7 @@ You can find a curated list of project templates and examples on our [Project Te
For example, to use the `Button` component you need to import it:

```jsx
import { Button } from '@ui5/webcomponents-react'; // loads ui5-button wrapped in a ui5-webcomponents-react component
import { Button } from '@ui5/webcomponents-react/Button'; // loads ui5-button wrapped in a ui5-webcomponents-react component
```

4. Add the imported component to your JSX:
Expand Down
11 changes: 7 additions & 4 deletions docs/Welcome.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ npm install @ui5/webcomponents-react @ui5/webcomponents
In order to use `@ui5/webcomponents-react` you have to wrap your application's root component into the `ThemeProvider` component.

```tsx
import { ThemeProvider } from '@ui5/webcomponents-react';
import { ThemeProvider } from '@ui5/webcomponents-react/ThemeProvider';
...
createRoot(document.getElementById('root') as HTMLElement).render(
<ThemeProvider>
Expand All @@ -96,7 +96,7 @@ Then you are ready to use `@ui5/webcomponents-react` and you can import the desi
For example, to use the `Button` component you need to import it:

```jsx
import { Button } from '@ui5/webcomponents-react'; // loads ui5-button wrapped in a ui5-webcomponents-react component
import { Button } from '@ui5/webcomponents-react/Button'; // loads ui5-button wrapped in a ui5-webcomponents-react component
```

Then, you can use the Button in your app:
Expand Down Expand Up @@ -161,8 +161,11 @@ Small app with a popover opened by clicking a button including type declarations

```tsx
import { useState, useRef } from 'react';
import type { ButtonPropTypes, PopoverDomRef, PopoverPropTypes } from '@ui5/webcomponents-react';
import { ThemeProvider, Button, Popover } from '@ui5/webcomponents-react';
import type { ButtonPropTypes } from '@ui5/webcomponents-react/Button';
import type { PopoverDomRef, PopoverPropTypes } from '@ui5/webcomponents-react/Popover';
import { ThemeProvider } from '@ui5/webcomponents-react/ThemeProvider';
import { Button } from '@ui5/webcomponents-react/Button';
import { Popover } from '@ui5/webcomponents-react/Popover';

export default function App() {
const [open, setOpen] = useState<PopoverPropTypes['open']>(false);
Expand Down
2 changes: 1 addition & 1 deletion docs/knowledge-base/FAQ.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ setCustomElementsScopingSuffix('demo');
// app main file, e.g index.js, main.tsx, etc.
import './scoping-config.js';
// now, all other component imports - the scoping config import must be the first import of the app
import { Button } from '@ui5/webcomponents-react';
import { Button } from '@ui5/webcomponents-react/Button';
// ...
```

Expand Down
4 changes: 2 additions & 2 deletions docs/knowledge-base/Internationalization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ registerI18nLoader('myApp', 'de', async () => {

Add the following import statement to the component where you want to use translated texts:

```js
import { useI18nBundle } from '@ui5/webcomponents-react-base';
```ts
import { useI18nBundle } from '@ui5/webcomponents-react-base/hooks';
```

Now, you can use the `useI18nBundle` hook in your functional components in order to access the i18nBundle and get your
Expand Down
8 changes: 4 additions & 4 deletions docs/knowledge-base/Public-Utils.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The `@ui5/webcomponents-react-base` package is providing a couple of utils, whic

## Device

<Source code={`import { Device } from '@ui5/webcomponents-react-base';`} />
<Source code={`import * as Device from '@ui5/webcomponents-react-base/Device';`} />

The `Device` allows you to detect information about the environment where your app is running:

Expand Down Expand Up @@ -73,7 +73,7 @@ The `Device` allows you to detect information about the environment where your a

## Theming Parameters

<Source code={`import { ThemingParameters } from '@ui5/webcomponents-react-base';`} />
<Source code={`import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';`} />

By using the global `--sap...` CSS variables, you can define the look and feel of your application without the need to hard-code any
colors. The `ThemingParameters` is an JS object containing all available CSS variables mapped to their name.
Expand All @@ -98,14 +98,14 @@ Is equivalent to:

### `useI18nBundle`

<Source code={`import { useI18nBundle } from '@ui5/webcomponents-react-base';`} />
<Source code={`import { useI18nBundle } from '@ui5/webcomponents-react-base/hooks';`} />

The `useI18nBundle` hook can be used for adding internationalization to your application. Learn more about it in our
[Internationalization Guide](?path=/docs/knowledge-base-internationalization--docs).

### `useViewportRange`

<Source code={`import { useViewportRange } from '@ui5/webcomponents-react-base';`} />
<Source code={`import { useViewportRange } from '@ui5/webcomponents-react-base/hooks';`} />

The `useViewportRange` hook is a utility hook based on the `Device.getCurrentRange()` and `Device.attachMediaHandler` API.
It will always return a string with the name of the currently active range.
Expand Down
2 changes: 1 addition & 1 deletion docs/knowledge-base/Styling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ In order to reuse our central styling approach, you can import the `ThemingParam
You can then create a custom component by following this recipe:

```tsx
import { ThemingParameters } from '@ui5/webcomponents-react-base';
import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';
import './MyCustomElement.css';

export const MyCustomElement = () => {
Expand Down
7 changes: 2 additions & 5 deletions packages/base/src/hooks/useSyncRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ import { useCallback, useRef } from 'react';
* ```tsx
* const MyComponent = forwardRef<HTMLDivElement, PropTypes>((props, ref) => {
* const [componentRef, localRef] = useSyncRef<HTMLDivElement>(ref);
*
* useEffect(() => {
* // `localRef.current` is always the latest DOM node (or `null`)
* console.log('current node:', localRef.current);
* }, []);
* // `localRef.current` is always the latest DOM node (or `null`)
* console.log('current node:', localRef.current);
*
* return <div ref={componentRef}>Hello World!</div>;
* });
Expand Down
Loading
Loading