@prokodo/ui 0.0.56 β†’ 0.1.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.
package/README.md CHANGED
@@ -32,6 +32,8 @@
32
32
  [![License: BUSL-1.1](https://img.shields.io/badge/license-BUSL--1.1-blue.svg)](LICENSE)
33
33
  [![Storybook](https://img.shields.io/badge/storybook-ui.prokodo.com-ff4785?logo=storybook&logoColor=white)](https://ui.prokodo.com)
34
34
  [![bundlephobia](https://img.shields.io/bundlephobia/minzip/@prokodo/ui?label=bundle%20size&style=flat&color=blue)](https://bundlephobia.com/result?p=@prokodo/ui)
35
+ [![Next.js](https://img.shields.io/badge/Next.js-13–16-black)](#)
36
+ [![Turbopack](https://img.shields.io/badge/Works%20with-Turbopack-000)](#)
35
37
 
36
38
  ---
37
39
 
@@ -44,7 +46,9 @@
44
46
  - πŸ§ͺ **Reliable**: Fully tested with Jest and Testing Library
45
47
  - πŸ“š **Storybook**: Explore the components at [ui.prokodo.com](https://ui.prokodo.com)
46
48
  - πŸ“¦ **Ready-to-install**: Distributed via npm for non-production use under the BUSL-1.1 license
47
- - 🧱 **Optimized for SSR**: Works great with Next.js and React Server Components
49
+ - πŸš€ **Optimized for Next.js 13–16 out of the box** (App Router, React Server Components)
50
+ - ⚑ **Turbopack compatible** (no config required)
51
+ - πŸ”— **Framework adapters** via `UIRuntimeProvider` for `next/link` & `next/image`
48
52
 
49
53
  ## ⚑ Lightweight by Design
50
54
 
@@ -173,7 +177,7 @@ export default function GalleryPage() {
173
177
  | Grid/GridRow | βœ… | – |
174
178
  | Headline | βœ… | - |
175
179
  | Icon | βœ… | – |
176
- | Image | βœ… | – |
180
+ | Image | βœ… | βœ… |
177
181
  | ImageText | βœ… | - |
178
182
  | Input | βœ… | βœ… |
179
183
  | Label | βœ… | – |
@@ -199,6 +203,80 @@ export default function GalleryPage() {
199
203
  | Table | βœ… | – |
200
204
  | Teaser | βœ… | - |
201
205
 
206
+ ## Since Next.js 16
207
+
208
+ - Link/Image runtime provider (required for <Link>/<Image> adapters)
209
+ - For Next.js apps, provide your framework components (next/link, next/image) via a small client provider.
210
+ - Do not pass linkComponent / imageComponent props from pagesβ€”use the provider instead.
211
+
212
+ ### 1. Create a client provider
213
+
214
+ ```tsx
215
+ // app/providers/ProkodoUiNextProvider.tsx
216
+ "use client"
217
+
218
+ import NextLink from "next/link"
219
+ import NextImage from "next/image"
220
+ import { UIRuntimeProvider } from "@prokodo/ui/runtime"
221
+
222
+ export function ProkodoUiNextProvider({
223
+ children,
224
+ }: {
225
+ children: React.ReactNode
226
+ }) {
227
+ return (
228
+ <UIRuntimeProvider
229
+ value={{ linkComponent: NextLink, imageComponent: NextImage }}
230
+ >
231
+ {children}
232
+ </UIRuntimeProvider>
233
+ )
234
+ }
235
+ ```
236
+
237
+ ### 2. Wrap your root layout
238
+
239
+ ```tsx
240
+ // app/layout.tsx (server component)
241
+ import { ProkodoUiNextProvider } from "./providers/ProkodoUiNextProvider"
242
+
243
+ export default function RootLayout({
244
+ children,
245
+ }: {
246
+ children: React.ReactNode
247
+ }) {
248
+ return (
249
+ <html lang="en">
250
+ <body>
251
+ <ProkodoUiNextProvider>{children}</ProkodoUiNextProvider>
252
+ </body>
253
+ </html>
254
+ )
255
+ }
256
+ ```
257
+
258
+ ### 3. Use components normally (no extra props needed)
259
+
260
+ ```tsx
261
+ import { Link } from "@prokodo/ui/link"
262
+ import { Image } from "@prokodo/ui/image"
263
+
264
+ export default function Page() {
265
+ return (
266
+ <>
267
+ <Link href="/about">About</Link>
268
+ <Image src="/hero.jpg" alt="Hero" />
269
+ </>
270
+ )
271
+ }
272
+ ```
273
+
274
+ **Notes**:
275
+
276
+ - The provider file must be a "use client" module.
277
+ - Remove any linkComponent / imageComponent props you previously passed from server code.
278
+ - Plain React (non-Next) apps don’t need this; just use <a> / <img> or pass your own adapters inside client components.
279
+
202
280
  ## How to create my own Island Component?
203
281
 
204
282
  ### 1. Create your island component (Navbar.tsx):
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ import { jsxs, jsx } from "react/jsx-runtime";
5
+ import { create } from "../../helpers/bem.js";
6
+ import { useUIRuntime } from "../../helpers/runtime.client.js";
7
+ import { isString } from "../../helpers/validations.js";
8
+ import styles from "./Image.module.scss.js";
9
+ const bem = create(styles, "Image");
10
+ const ImageClient = /* @__PURE__ */ __name(({
11
+ alt,
12
+ caption,
13
+ containerClassName,
14
+ captionClassName,
15
+ className,
16
+ imageComponent,
17
+ ...props
18
+ }) => {
19
+ const { imageComponent: ctxImage } = useUIRuntime();
20
+ const CustomImage = imageComponent ?? ctxImage ?? "img";
21
+ const renderImage = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx(
22
+ CustomImage,
23
+ {
24
+ alt: alt ?? "",
25
+ className: bem("image", void 0, className),
26
+ ...props
27
+ }
28
+ ), "renderImage");
29
+ if (isString(caption)) {
30
+ return /* @__PURE__ */ jsxs("figure", { className: bem(void 0, void 0, containerClassName), children: [
31
+ renderImage(),
32
+ /* @__PURE__ */ jsx("figcaption", { className: bem("caption", void 0, captionClassName), children: caption })
33
+ ] });
34
+ }
35
+ return renderImage();
36
+ }, "ImageClient");
37
+ export {
38
+ ImageClient as default
39
+ };
@@ -1,35 +1,13 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
- import { jsxs, jsx } from "react/jsx-runtime";
4
- import { create } from "../../helpers/bem.js";
5
- import { isString } from "../../helpers/validations.js";
6
- import styles from "./Image.module.scss.js";
7
- const bem = create(styles, "Image");
8
- const Image = /* @__PURE__ */ __name(({
9
- alt,
10
- caption,
11
- containerClassName,
12
- captionClassName,
13
- className,
14
- imageComponent: CustomImage = "img",
15
- ...props
16
- }) => {
17
- const renderImage = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx(
18
- CustomImage,
19
- {
20
- alt: alt ?? "",
21
- className: bem("image", void 0, className),
22
- ...props
23
- }
24
- ), "renderImage");
25
- if (isString(caption)) {
26
- return /* @__PURE__ */ jsxs("figure", { className: bem(void 0, void 0, containerClassName), children: [
27
- renderImage(),
28
- /* @__PURE__ */ jsx("figcaption", { className: bem("caption", void 0, captionClassName), children: caption })
29
- ] });
30
- }
31
- return renderImage();
32
- }, "Image");
3
+ import { createIsland } from "../../helpers/createIsland.js";
4
+ import ImageServer from "./Image.server.js";
5
+ const Image = createIsland({
6
+ name: "Image",
7
+ Server: ImageServer,
8
+ loadLazy: /* @__PURE__ */ __name(() => import("./Image.lazy.js"), "loadLazy"),
9
+ isInteractive: /* @__PURE__ */ __name((p) => typeof p.imageComponent === "function", "isInteractive")
10
+ });
33
11
  export {
34
12
  Image
35
13
  };
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ import { createLazyWrapper } from "../../helpers/createLazyWrapper.js";
5
+ import ImageClient from "./Image.client.js";
6
+ import ImageServer from "./Image.server.js";
7
+ const Image_lazy = createLazyWrapper({
8
+ name: "Image",
9
+ Client: ImageClient,
10
+ Server: ImageServer,
11
+ // treat as interactive if a custom image component is a function (e.g. NextImage)
12
+ isInteractive: /* @__PURE__ */ __name((p) => typeof p.imageComponent === "function", "isInteractive")
13
+ });
14
+ export {
15
+ Image_lazy as default
16
+ };
@@ -0,0 +1,88 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ import { jsxs, jsx } from "react/jsx-runtime";
4
+ import { create } from "../../helpers/bem.js";
5
+ import { isString } from "../../helpers/validations.js";
6
+ import styles from "./Image.module.scss.js";
7
+ const bem = create(styles, "Image");
8
+ function toImgOnlyProps(p) {
9
+ const {
10
+ // Strip next/image-only props so they don't leak onto <img>
11
+ _fill,
12
+ // intentionally unused (name the same to show intent)
13
+ _loader,
14
+ _placeholder,
15
+ _blurDataURL,
16
+ _priority,
17
+ _quality,
18
+ // Note: sizes *is* valid on <img> when it's a string, so we won't strip it
19
+ _onLoadingComplete,
20
+ // our runtime wiring we never want on <img>
21
+ _imageComponent,
22
+ ...rest
23
+ } = p;
24
+ let src;
25
+ const rawSrc = rest == null ? void 0 : rest.src;
26
+ if (typeof rawSrc === "string") {
27
+ src = rawSrc;
28
+ } else if (typeof rawSrc === "object" && rawSrc !== null && // explicit property check to satisfy strict-boolean-expressions
29
+ Object.prototype.hasOwnProperty.call(
30
+ rawSrc,
31
+ "src"
32
+ ) && typeof rawSrc.src === "string") {
33
+ const rs = rawSrc;
34
+ ({ src } = rs);
35
+ } else {
36
+ src = void 0;
37
+ }
38
+ const { width, height } = rest;
39
+ const imgProps = {
40
+ ...rest,
41
+ src,
42
+ width,
43
+ height
44
+ };
45
+ return imgProps;
46
+ }
47
+ __name(toImgOnlyProps, "toImgOnlyProps");
48
+ const ImageServer = /* @__PURE__ */ __name(({
49
+ alt,
50
+ caption,
51
+ containerClassName,
52
+ captionClassName,
53
+ className,
54
+ ...rawProps
55
+ }) => {
56
+ if (process.env.NODE_ENV !== "production" && typeof rawProps.imageComponent === "function") {
57
+ console.error(
58
+ "[UI] Do not pass function props (imageComponent) to <Image> on the server. Use UIRuntimeProvider in the parent app instead.",
59
+ rawProps
60
+ );
61
+ }
62
+ const {
63
+ imageComponent: _dropImageComponent,
64
+ onClick: _dropClick,
65
+ onKeyDown: _dropKey,
66
+ ...rest
67
+ } = rawProps;
68
+ const CustomImage = "img";
69
+ const imgProps = toImgOnlyProps(rest);
70
+ const renderImage = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx(
71
+ CustomImage,
72
+ {
73
+ alt: alt ?? "",
74
+ className: bem("image", void 0, className),
75
+ ...imgProps
76
+ }
77
+ ), "renderImage");
78
+ if (isString(caption)) {
79
+ return /* @__PURE__ */ jsxs("figure", { className: bem(void 0, void 0, containerClassName), children: [
80
+ renderImage(),
81
+ /* @__PURE__ */ jsx("figcaption", { className: bem("caption", void 0, captionClassName), children: caption })
82
+ ] });
83
+ }
84
+ return renderImage();
85
+ }, "ImageServer");
86
+ export {
87
+ ImageServer as default
88
+ };
@@ -1,10 +1,12 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { memo } from "react";
4
+ import { useUIRuntime } from "../../helpers/runtime.client.js";
4
5
  import BaseLinkServer from "../base-link/BaseLink.server.js";
5
6
  import { LinkView } from "./Link.view.js";
6
7
  const LinkClient = memo((props) => {
7
8
  const { href, onClick } = props;
9
+ const { linkComponent: ctxLink } = useUIRuntime();
8
10
  const linkTag = onClick && !href ? "span" : "a";
9
11
  const hasHandlers = Boolean(onClick) || Boolean(props.onKeyDown);
10
12
  return /* @__PURE__ */ jsx(
@@ -13,7 +15,8 @@ const LinkClient = memo((props) => {
13
15
  ...props,
14
16
  BaseLinkComponent: BaseLinkServer,
15
17
  hasHandlers,
16
- LinkTag: linkTag
18
+ LinkTag: linkTag,
19
+ ...ctxLink ? { linkComponent: ctxLink } : null
17
20
  }
18
21
  );
19
22
  });
@@ -3,9 +3,21 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import BaseLinkServer from "../base-link/BaseLink.server.js";
5
5
  import { LinkView } from "./Link.view.js";
6
- function LinkServer(props) {
6
+ function LinkServer(rawProps) {
7
+ if (process.env.NODE_ENV !== "production" && typeof (rawProps == null ? void 0 : rawProps.linkComponent) === "function") {
8
+ console.error(
9
+ "[UI] Do not pass function props (linkComponent) to <Link> on the server. Use the UIRuntimeProvider in the parent app instead.",
10
+ rawProps
11
+ );
12
+ }
13
+ const {
14
+ onClick,
15
+ onKeyDown: _onKeyDown,
16
+ linkComponent: _drop,
17
+ ...props
18
+ } = rawProps;
7
19
  const hasHandlers = false;
8
- const linkTag = props.onClick && !props.href ? "span" : "a";
20
+ const linkTag = onClick && !(props == null ? void 0 : props.href) ? "span" : "a";
9
21
  return /* @__PURE__ */ jsx(
10
22
  LinkView,
11
23
  {
@@ -1,4 +1,4 @@
1
- const PROKODO_UI_VERSION = "0.0.56";
1
+ const PROKODO_UI_VERSION = "0.1.0";
2
2
  export {
3
3
  PROKODO_UI_VERSION
4
4
  };
@@ -2,6 +2,12 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { lazy, Suspense, cloneElement } from "react";
5
+ function stripFnProps(p) {
6
+ return Object.fromEntries(
7
+ Object.entries(p).filter(([, v]) => typeof v !== "function")
8
+ );
9
+ }
10
+ __name(stripFnProps, "stripFnProps");
5
11
  function createIsland({
6
12
  name,
7
13
  Server,
@@ -13,40 +19,25 @@ function createIsland({
13
19
  void loadLazy();
14
20
  }
15
21
  function withIslandAttr(el, priority) {
16
- var _a;
17
22
  const islandName = name.toLowerCase();
18
- if (typeof process !== "undefined" && typeof ((_a = process == null ? void 0 : process.env) == null ? void 0 : _a.PK_ENABLE_DEBUG_LOGS) === "string") {
19
- console.debug(
20
- `[hydrate] createIsland β€œ${name}” rendering as interactive=${Boolean(
21
- priority
22
- )}`
23
- );
24
- }
25
23
  const extra = {
26
24
  "data-island": islandName
27
25
  };
28
- if (Boolean(priority)) {
29
- extra.priority = priority;
30
- }
26
+ if (Boolean(priority)) extra.priority = true;
31
27
  return cloneElement(el, extra);
32
28
  }
33
29
  __name(withIslandAttr, "withIslandAttr");
34
- const Island = /* @__PURE__ */ __name(({
35
- priority = false,
36
- ...raw
37
- }) => {
30
+ const Island = /* @__PURE__ */ __name(({ ...raw }) => {
38
31
  const props = raw;
39
32
  const autoInteractive = Object.entries(props).some(
40
33
  ([k, v]) => k.startsWith("on") && typeof v === "function"
41
34
  ) || props.redirect !== void 0;
42
35
  const interactive = customInteractive ? customInteractive(props) || autoInteractive : autoInteractive;
43
- if (interactive && priority) {
44
- return withIslandAttr(/* @__PURE__ */ jsx(LazyComp, { ...props }));
45
- }
36
+ const serverSafe = stripFnProps(props);
46
37
  if (!interactive) {
47
- return withIslandAttr(/* @__PURE__ */ jsx(Server, { ...props }));
38
+ return withIslandAttr(/* @__PURE__ */ jsx(Server, { ...serverSafe }));
48
39
  }
49
- const fallback = withIslandAttr(/* @__PURE__ */ jsx(Server, { ...props }));
40
+ const fallback = withIslandAttr(/* @__PURE__ */ jsx(Server, { ...serverSafe }));
50
41
  return /* @__PURE__ */ jsx(Suspense, { fallback, children: withIslandAttr(/* @__PURE__ */ jsx(LazyComp, { ...props })) });
51
42
  }, "Island");
52
43
  Island.displayName = `${name}Island`;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { useContext, createContext } from "react";
6
+ const UIRuntimeContext = createContext({});
7
+ const UIRuntimeProvider = /* @__PURE__ */ __name(({
8
+ value,
9
+ children
10
+ }) => /* @__PURE__ */ jsx(UIRuntimeContext.Provider, { value, children }), "UIRuntimeProvider");
11
+ const useUIRuntime = /* @__PURE__ */ __name(() => useContext(UIRuntimeContext), "useUIRuntime");
12
+ export {
13
+ UIRuntimeProvider,
14
+ useUIRuntime
15
+ };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { UIRuntimeProvider, useUIRuntime } from "./helpers/runtime.client.js";
1
2
  import { Accordion } from "./components/accordion/Accordion.js";
2
3
  import { Animated } from "./components/animated/Animated.js";
3
4
  import { AnimatedText } from "./components/animatedText/AnimatedText.js";
@@ -90,5 +91,7 @@ export {
90
91
  Stepper,
91
92
  Switch,
92
93
  Table,
93
- Teaser
94
+ Teaser,
95
+ UIRuntimeProvider,
96
+ useUIRuntime
94
97
  };