@intlayer/design-system 9.5.1 → 9.5.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.
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useItemSelector } from "../../../hooks/useItemSelector.mjs";
4
3
  import { InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "../../Input/OTPInput.mjs";
4
+ import { useItemSelector } from "../../../hooks/useItemSelector.mjs";
5
5
  import { FormField, useFormField } from "../FormField.mjs";
6
6
  import { FormItemLayout } from "../layout/FormItemLayout.mjs";
7
7
  import { useEffect, useRef } from "react";
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { usePersistedStore } from "../../hooks/usePersistedStore.mjs";
4
3
  import { Button } from "../Button/Button.mjs";
5
4
  import { Container } from "../Container/index.mjs";
6
5
  import { DropDown } from "../DropDown/index.mjs";
7
6
  import { Input } from "../Input/Input.mjs";
8
7
  import { SwitchSelector } from "../SwitchSelector/SwitchSelector.mjs";
8
+ import { usePersistedStore } from "../../hooks/usePersistedStore.mjs";
9
9
  import { useLocaleSwitcherContent } from "./LocaleSwitcherContentContext.mjs";
10
10
  import { useMemo, useRef, useState } from "react";
11
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -5,16 +5,58 @@ import { Button } from "../Button/Button.mjs";
5
5
  import { Link } from "../Link/Link.mjs";
6
6
  import { Container } from "../Container/index.mjs";
7
7
  import { Modal } from "../Modal/Modal.mjs";
8
+ import { getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl } from "./youtubeUrl.mjs";
8
9
  import { useEffect, useRef, useState } from "react";
9
10
  import { jsx, jsxs } from "react/jsx-runtime";
10
- import { MoveDiagonal, Play } from "lucide-react";
11
+ import { MoveDiagonal } from "lucide-react";
11
12
 
12
13
  //#region src/components/MarkDownRender/MarkDownIframe.tsx
13
- function embedLinkMeta(src) {
14
+ /**
15
+ * The features the YouTube player asks for, as shipped by YouTube's own embed code.
16
+ */
17
+ const YOUTUBE_PLAYER_ALLOW = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share";
18
+ /**
19
+ * Turns whatever the markdown handed over into what the frame is rendered with.
20
+ *
21
+ * A YouTube address, in any of its forms, becomes the player embed carrying
22
+ * the attributes YouTube's own embed code ships with. Query parameters are
23
+ * dropped on purpose: docs written for one origin (`origin=…`) break the
24
+ * player on every other one.
25
+ *
26
+ * `referrerPolicy` is the attribute that matters: the player refuses to start
27
+ * ("Video player configuration error", error 153) unless the request carries
28
+ * a referrer, and a page served with `Referrer-Policy: same-origin` sends none
29
+ * to a cross-origin frame. The attribute overrides the page policy for this
30
+ * frame only.
31
+ */
32
+ const resolveFrame = (src, frameProps) => {
33
+ const youtubeVideoId = getYoutubeVideoId(src);
34
+ if (!youtubeVideoId) return {
35
+ src,
36
+ frameProps,
37
+ isYoutubeVideo: false
38
+ };
39
+ return {
40
+ isYoutubeVideo: true,
41
+ src: getYoutubeEmbedUrl(youtubeVideoId),
42
+ frameProps: {
43
+ ...frameProps,
44
+ allow: YOUTUBE_PLAYER_ALLOW,
45
+ allowFullScreen: true,
46
+ referrerPolicy: "strict-origin-when-cross-origin"
47
+ }
48
+ };
49
+ };
50
+ const getEmbedLinkMeta = (src) => {
14
51
  if (!src) return {
15
52
  href: "",
16
53
  label: ""
17
54
  };
55
+ const youtubeVideoId = getYoutubeVideoId(src);
56
+ if (youtubeVideoId) return {
57
+ href: getYoutubeWatchUrl(youtubeVideoId),
58
+ label: "youtube.com"
59
+ };
18
60
  if (/^https?:\/\//i.test(src)) try {
19
61
  const url = new URL(src);
20
62
  return {
@@ -31,21 +73,6 @@ function embedLinkMeta(src) {
31
73
  href: src,
32
74
  label: src
33
75
  };
34
- }
35
- /**
36
- * Reads the video id out of a YouTube embed address.
37
- *
38
- * @returns The video id, or `null` when the address is not a YouTube embed.
39
- */
40
- const getYoutubeVideoId = (src) => {
41
- if (!src || !/^https?:\/\//i.test(src)) return null;
42
- try {
43
- const url = new URL(src);
44
- if (!/(^|\.)youtube(-nocookie)?\.com$/.test(url.hostname)) return null;
45
- return url.pathname.replace(/^\/embed\//, "").split("/")[0] || null;
46
- } catch {
47
- return null;
48
- }
49
76
  };
50
77
  /**
51
78
  * Tracks whether an element has come close enough to the viewport to be worth
@@ -85,63 +112,28 @@ const useHasBecomeVisible = () => {
85
112
  };
86
113
  };
87
114
  /**
88
- * The embed itself: a poster for YouTube, deferred loading for anything else.
89
- *
90
- * A YouTube player pulls well over a megabyte of script before it can show a
91
- * single frame. Standing in for it with its own thumbnail until the reader
92
- * presses play keeps that cost on the readers who actually watch the video.
115
+ * The embed itself, mounted once it comes close to the viewport.
93
116
  */
94
117
  const EmbeddedFrame = ({ src, title, className, frameProps }) => {
95
- const [isPlayRequested, setIsPlayRequested] = useState(false);
96
118
  const { elementRef, hasBecomeVisible } = useHasBecomeVisible();
97
- const youtubeVideoId = getYoutubeVideoId(src);
98
- if (youtubeVideoId && !isPlayRequested) return /* @__PURE__ */ jsxs("button", {
99
- type: "button",
100
- "aria-label": title ?? "Play video",
101
- onClick: () => setIsPlayRequested(true),
102
- className: cn("group relative block cursor-pointer border-0 bg-card p-0", className),
103
- children: [/* @__PURE__ */ jsxs("picture", { children: [/* @__PURE__ */ jsx("source", {
104
- srcSet: `https://i.ytimg.com/vi_webp/${youtubeVideoId}/hqdefault.webp`,
105
- type: "image/webp"
106
- }), /* @__PURE__ */ jsx("img", {
107
- src: `https://i.ytimg.com/vi/${youtubeVideoId}/hqdefault.jpg`,
108
- alt: "",
109
- width: 480,
110
- height: 360,
111
- fetchPriority: "high",
112
- decoding: "async",
113
- className: "size-full object-cover"
114
- })] }), /* @__PURE__ */ jsx("span", {
115
- className: "absolute inset-0 flex items-center justify-center",
116
- children: /* @__PURE__ */ jsx("span", {
117
- className: "flex size-14 items-center justify-center rounded-full bg-text/70 text-text-opposite transition-transform group-hover:scale-110",
118
- children: /* @__PURE__ */ jsx(Play, { className: "size-6 translate-x-0.5 fill-current" })
119
- })
120
- })]
121
- });
122
- if (!hasBecomeVisible && !isPlayRequested) return /* @__PURE__ */ jsx("div", {
119
+ if (!hasBecomeVisible) return /* @__PURE__ */ jsx("div", {
123
120
  ref: elementRef,
124
121
  "aria-hidden": true,
125
122
  className
126
123
  });
127
- const frameSrc = (() => {
128
- if (!isPlayRequested || !youtubeVideoId || !src) return src;
129
- const playbackSrc = new URL(src);
130
- playbackSrc.searchParams.set("autoplay", "1");
131
- return playbackSrc.href;
132
- })();
133
124
  return /* @__PURE__ */ jsx("iframe", {
134
125
  ...frameProps,
135
- src: frameSrc,
126
+ src,
136
127
  title,
137
128
  loading: "lazy",
138
129
  className
139
130
  });
140
131
  };
141
132
  const MarkDownIframe = (props) => {
142
- const { src, className, title, ...rest } = props;
133
+ const { src: rawSrc, className, title, ...rest } = props;
143
134
  const [isModalOpen, setIsModalOpen] = useState(false);
144
- const { href, label } = embedLinkMeta(src);
135
+ const { src, frameProps, isYoutubeVideo } = resolveFrame(rawSrc, rest);
136
+ const { href, label } = getEmbedLinkMeta(rawSrc);
145
137
  return /* @__PURE__ */ jsxs(Container, {
146
138
  roundedSize: "2xl",
147
139
  border: true,
@@ -152,8 +144,8 @@ const MarkDownIframe = (props) => {
152
144
  /* @__PURE__ */ jsx(EmbeddedFrame, {
153
145
  src,
154
146
  title,
155
- frameProps: rest,
156
- className: cn("block max-h-[80vh] min-h-[12rem] w-full border-0", className)
147
+ frameProps,
148
+ className: cn("block max-h-[80vh] min-h-[12rem] w-full border-0", isYoutubeVideo && "aspect-video", className)
157
149
  }),
158
150
  /* @__PURE__ */ jsxs("div", {
159
151
  className: "flex items-center justify-between gap-3 px-3 py-1",
@@ -190,7 +182,7 @@ const MarkDownIframe = (props) => {
190
182
  className: "overflow-hidden p-0",
191
183
  gap: "none",
192
184
  children: /* @__PURE__ */ jsx("iframe", {
193
- ...rest,
185
+ ...frameProps,
194
186
  src,
195
187
  title: title ?? "Embedded content",
196
188
  allowFullScreen: true,
@@ -1 +1 @@
1
- {"version":3,"file":"MarkDownIframe.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownIframe.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@components/Button';\nimport { Container } from '@components/Container';\nimport { Link } from '@components/Link';\nimport { Modal } from '@components/Modal';\nimport { cn } from '@utils/cn';\nimport { MoveDiagonal, Play } from 'lucide-react';\nimport {\n type ComponentProps,\n type FC,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nfunction embedLinkMeta(src: string | undefined): {\n href: string;\n label: string;\n} {\n if (!src) return { href: '', label: '' };\n if (/^https?:\\/\\//i.test(src)) {\n try {\n const url = new URL(src);\n return { href: url.href, label: url.host };\n } catch {\n return { href: src, label: src };\n }\n }\n return { href: src, label: src };\n}\n\n/**\n * Reads the video id out of a YouTube embed address.\n *\n * @returns The video id, or `null` when the address is not a YouTube embed.\n */\nconst getYoutubeVideoId = (src: string | undefined): string | null => {\n if (!src || !/^https?:\\/\\//i.test(src)) return null;\n\n try {\n const url = new URL(src);\n if (!/(^|\\.)youtube(-nocookie)?\\.com$/.test(url.hostname)) return null;\n\n const videoId = url.pathname.replace(/^\\/embed\\//, '').split('/')[0];\n\n return videoId || null;\n } catch {\n return null;\n }\n};\n\n/**\n * Tracks whether an element has come close enough to the viewport to be worth\n * loading.\n *\n * `loading=\"lazy\"` is not enough for embeds inside tabs or carousels: the\n * browser only defers frames that are far down the page, so a frame parked\n * off-screen by a transform — an inactive tab panel — still loads immediately.\n * A live-app embed costs upwards of a megabyte and seconds of main thread,\n * which is the whole page's performance budget spent on something nobody is\n * looking at.\n *\n * @returns A ref to attach to the placeholder, and whether it became visible.\n */\nconst useHasBecomeVisible = <ElementType extends HTMLElement>() => {\n const elementRef = useRef<ElementType>(null);\n const [hasBecomeVisible, setHasBecomeVisible] = useState(false);\n\n useEffect(() => {\n const element = elementRef.current;\n if (!element || hasBecomeVisible) return;\n\n if (typeof IntersectionObserver === 'undefined') {\n setHasBecomeVisible(true);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setHasBecomeVisible(true);\n observer.disconnect();\n }\n },\n { rootMargin: '200px' }\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n }, [hasBecomeVisible]);\n\n return { elementRef, hasBecomeVisible };\n};\n\ntype EmbeddedFrameProps = {\n src: string | undefined;\n title: string | undefined;\n className: string;\n frameProps: Omit<ComponentProps<'iframe'>, 'src' | 'title' | 'className'>;\n};\n\n/**\n * The embed itself: a poster for YouTube, deferred loading for anything else.\n *\n * A YouTube player pulls well over a megabyte of script before it can show a\n * single frame. Standing in for it with its own thumbnail until the reader\n * presses play keeps that cost on the readers who actually watch the video.\n */\nconst EmbeddedFrame: FC<EmbeddedFrameProps> = ({\n src,\n title,\n className,\n frameProps,\n}) => {\n const [isPlayRequested, setIsPlayRequested] = useState(false);\n const { elementRef, hasBecomeVisible } =\n useHasBecomeVisible<HTMLDivElement>();\n const youtubeVideoId = getYoutubeVideoId(src);\n\n if (youtubeVideoId && !isPlayRequested) {\n return (\n <button\n type=\"button\"\n aria-label={title ?? 'Play video'}\n onClick={() => setIsPlayRequested(true)}\n className={cn(\n 'group relative block cursor-pointer border-0 bg-card p-0',\n className\n )}\n >\n {/*\n * The poster stands in for the player, so on a page that opens with a\n * video it is the largest element painted — deferring it would defer\n * the page's LCP. It is the one image here worth fetching eagerly.\n */}\n <picture>\n {/*\n * YouTube serves the same poster as WebP under `vi_webp`, at roughly\n * a third of the JPEG's weight. The `img` below stays as the\n * fallback for the browsers that cannot decode it.\n */}\n <source\n srcSet={`https://i.ytimg.com/vi_webp/${youtubeVideoId}/hqdefault.webp`}\n type=\"image/webp\"\n />\n <img\n src={`https://i.ytimg.com/vi/${youtubeVideoId}/hqdefault.jpg`}\n alt=\"\"\n width={480}\n height={360}\n fetchPriority=\"high\"\n decoding=\"async\"\n className=\"size-full object-cover\"\n />\n </picture>\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"flex size-14 items-center justify-center rounded-full bg-text/70 text-text-opposite transition-transform group-hover:scale-110\">\n <Play className=\"size-6 translate-x-0.5 fill-current\" />\n </span>\n </span>\n </button>\n );\n }\n\n if (!hasBecomeVisible && !isPlayRequested) {\n // Same box as the frame it stands in for, so nothing shifts once it loads.\n return <div ref={elementRef} aria-hidden className={className} />;\n }\n\n const frameSrc = (() => {\n if (!isPlayRequested || !youtubeVideoId || !src) return src;\n\n const playbackSrc = new URL(src);\n playbackSrc.searchParams.set('autoplay', '1');\n\n return playbackSrc.href;\n })();\n\n return (\n <iframe\n {...frameProps}\n src={frameSrc}\n title={title}\n loading=\"lazy\"\n className={className}\n />\n );\n};\n\nexport const MarkDownIframe: FC<ComponentProps<'iframe'>> = (props) => {\n const { src, className, title, ...rest } = props;\n const [isModalOpen, setIsModalOpen] = useState(false);\n const { href, label } = embedLinkMeta(src);\n\n return (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <EmbeddedFrame\n src={src}\n title={title}\n frameProps={rest}\n className={cn(\n 'block max-h-[80vh] min-h-[12rem] w-full border-0',\n className\n )}\n />\n <div className=\"flex items-center justify-between gap-3 px-3 py-1\">\n {href ? (\n <Link\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n label=\"\"\n color=\"neutral\"\n className=\"inline-flex min-w-0 max-w-[calc(100%-3rem)] items-center gap-2 text-muted-foreground text-xs underline-offset-2 hover:text-foreground hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <span className=\"text-neutral text-sm\">Embedded frame</span>\n )}\n <Button\n variant=\"hoverable\"\n size=\"icon-md\"\n onClick={() => setIsModalOpen(true)}\n label=\"Open embedded page in fullscreen\"\n Icon={MoveDiagonal}\n />\n </div>\n\n <Modal\n isOpen={isModalOpen}\n onClose={() => setIsModalOpen(false)}\n size=\"unset\"\n hasCloseButton\n isScrollable\n padding=\"sm\"\n >\n {isModalOpen && src ? (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <iframe\n {...rest}\n src={src}\n title={title ?? 'Embedded content'}\n allowFullScreen\n className=\"block min-h-[82vh] w-full border-0\"\n />\n </Container>\n ) : null}\n </Modal>\n </Container>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"MarkDownIframe.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownIframe.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@components/Button';\nimport { Container } from '@components/Container';\nimport { Link } from '@components/Link';\nimport { Modal } from '@components/Modal';\nimport { cn } from '@utils/cn';\nimport { MoveDiagonal } from 'lucide-react';\nimport {\n type ComponentProps,\n type FC,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport {\n getYoutubeEmbedUrl,\n getYoutubeVideoId,\n getYoutubeWatchUrl,\n} from './youtubeUrl';\n\ntype FrameProps = Omit<ComponentProps<'iframe'>, 'src' | 'title' | 'className'>;\n\n/**\n * The features the YouTube player asks for, as shipped by YouTube's own embed code.\n */\nconst YOUTUBE_PLAYER_ALLOW =\n 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share';\n\ntype ResolvedFrame = {\n src: string | undefined;\n frameProps: FrameProps;\n isYoutubeVideo: boolean;\n};\n\n/**\n * Turns whatever the markdown handed over into what the frame is rendered with.\n *\n * A YouTube address, in any of its forms, becomes the player embed carrying\n * the attributes YouTube's own embed code ships with. Query parameters are\n * dropped on purpose: docs written for one origin (`origin=…`) break the\n * player on every other one.\n *\n * `referrerPolicy` is the attribute that matters: the player refuses to start\n * (\"Video player configuration error\", error 153) unless the request carries\n * a referrer, and a page served with `Referrer-Policy: same-origin` sends none\n * to a cross-origin frame. The attribute overrides the page policy for this\n * frame only.\n */\nconst resolveFrame = (\n src: string | undefined,\n frameProps: FrameProps\n): ResolvedFrame => {\n const youtubeVideoId = getYoutubeVideoId(src);\n\n if (!youtubeVideoId) return { src, frameProps, isYoutubeVideo: false };\n\n return {\n isYoutubeVideo: true,\n src: getYoutubeEmbedUrl(youtubeVideoId),\n frameProps: {\n ...frameProps,\n allow: YOUTUBE_PLAYER_ALLOW,\n allowFullScreen: true,\n referrerPolicy: 'strict-origin-when-cross-origin',\n },\n };\n};\n\nconst getEmbedLinkMeta = (\n src: string | undefined\n): { href: string; label: string } => {\n if (!src) return { href: '', label: '' };\n\n const youtubeVideoId = getYoutubeVideoId(src);\n if (youtubeVideoId) {\n return {\n href: getYoutubeWatchUrl(youtubeVideoId),\n label: 'youtube.com',\n };\n }\n\n if (/^https?:\\/\\//i.test(src)) {\n try {\n const url = new URL(src);\n return { href: url.href, label: url.host };\n } catch {\n return { href: src, label: src };\n }\n }\n\n return { href: src, label: src };\n};\n\n/**\n * Tracks whether an element has come close enough to the viewport to be worth\n * loading.\n *\n * `loading=\"lazy\"` is not enough for embeds inside tabs or carousels: the\n * browser only defers frames that are far down the page, so a frame parked\n * off-screen by a transform — an inactive tab panel — still loads immediately.\n * A live-app embed costs upwards of a megabyte and seconds of main thread,\n * which is the whole page's performance budget spent on something nobody is\n * looking at.\n *\n * @returns A ref to attach to the placeholder, and whether it became visible.\n */\nconst useHasBecomeVisible = <ElementType extends HTMLElement>() => {\n const elementRef = useRef<ElementType>(null);\n const [hasBecomeVisible, setHasBecomeVisible] = useState(false);\n\n useEffect(() => {\n const element = elementRef.current;\n if (!element || hasBecomeVisible) return;\n\n if (typeof IntersectionObserver === 'undefined') {\n setHasBecomeVisible(true);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setHasBecomeVisible(true);\n observer.disconnect();\n }\n },\n { rootMargin: '200px' }\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n }, [hasBecomeVisible]);\n\n return { elementRef, hasBecomeVisible };\n};\n\ntype EmbeddedFrameProps = Pick<ResolvedFrame, 'src' | 'frameProps'> & {\n title: string | undefined;\n className: string;\n};\n\n/**\n * The embed itself, mounted once it comes close to the viewport.\n */\nconst EmbeddedFrame: FC<EmbeddedFrameProps> = ({\n src,\n title,\n className,\n frameProps,\n}) => {\n const { elementRef, hasBecomeVisible } =\n useHasBecomeVisible<HTMLDivElement>();\n\n if (!hasBecomeVisible) {\n // Same box as the frame it stands in for, so nothing shifts once it loads.\n return <div ref={elementRef} aria-hidden className={className} />;\n }\n\n return (\n <iframe\n {...frameProps}\n src={src}\n title={title}\n loading=\"lazy\"\n className={className}\n />\n );\n};\n\nexport const MarkDownIframe: FC<ComponentProps<'iframe'>> = (props) => {\n const { src: rawSrc, className, title, ...rest } = props;\n const [isModalOpen, setIsModalOpen] = useState(false);\n const { src, frameProps, isYoutubeVideo } = resolveFrame(rawSrc, rest);\n const { href, label } = getEmbedLinkMeta(rawSrc);\n\n return (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <EmbeddedFrame\n src={src}\n title={title}\n frameProps={frameProps}\n className={cn(\n 'block max-h-[80vh] min-h-[12rem] w-full border-0',\n isYoutubeVideo && 'aspect-video',\n className\n )}\n />\n <div className=\"flex items-center justify-between gap-3 px-3 py-1\">\n {href ? (\n <Link\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n label=\"\"\n color=\"neutral\"\n className=\"inline-flex min-w-0 max-w-[calc(100%-3rem)] items-center gap-2 text-muted-foreground text-xs underline-offset-2 hover:text-foreground hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <span className=\"text-neutral text-sm\">Embedded frame</span>\n )}\n <Button\n variant=\"hoverable\"\n size=\"icon-md\"\n onClick={() => setIsModalOpen(true)}\n label=\"Open embedded page in fullscreen\"\n Icon={MoveDiagonal}\n />\n </div>\n\n <Modal\n isOpen={isModalOpen}\n onClose={() => setIsModalOpen(false)}\n size=\"unset\"\n hasCloseButton\n isScrollable\n padding=\"sm\"\n >\n {isModalOpen && src ? (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <iframe\n {...frameProps}\n src={src}\n title={title ?? 'Embedded content'}\n allowFullScreen\n className=\"block min-h-[82vh] w-full border-0\"\n />\n </Container>\n ) : null}\n </Modal>\n </Container>\n );\n};\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
+ import { getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl } from "./youtubeUrl.mjs";
1
2
  import { MarkDownIframe } from "./MarkDownIframe.mjs";
2
3
  import { MarkdownRenderer, baseMarkdownComponents, getIntlayerMarkdownOptions } from "./MarkDownRender.mjs";
3
4
 
4
- export { MarkDownIframe, MarkdownRenderer, baseMarkdownComponents, getIntlayerMarkdownOptions };
5
+ export { MarkDownIframe, MarkdownRenderer, baseMarkdownComponents, getIntlayerMarkdownOptions, getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl };
@@ -0,0 +1,22 @@
1
+ //#region src/components/MarkDownRender/youtubeUrl.ts
2
+ /**
3
+ * Reads the video id out of a YouTube address (embed, watch, youtu.be, shorts, etc.).
4
+ *
5
+ * @returns The video id, or `null` when the address is not a YouTube URL.
6
+ */
7
+ const getYoutubeVideoId = (src) => {
8
+ if (!src) return null;
9
+ return src.match(/(?:youtu\.be\/|youtube(?:-nocookie)?\.com\/(?:embed\/|v\/|watch\?v=|shorts\/))([\w-]{11})/i)?.[1] ?? null;
10
+ };
11
+ /**
12
+ * The address the YouTube player is loaded from for a given video.
13
+ */
14
+ const getYoutubeEmbedUrl = (videoId) => `https://www.youtube.com/embed/${videoId}`;
15
+ /**
16
+ * The address of the video on youtube.com itself.
17
+ */
18
+ const getYoutubeWatchUrl = (videoId) => `https://www.youtube.com/watch?v=${videoId}`;
19
+
20
+ //#endregion
21
+ export { getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl };
22
+ //# sourceMappingURL=youtubeUrl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"youtubeUrl.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/youtubeUrl.ts"],"sourcesContent":["/**\n * Reads the video id out of a YouTube address (embed, watch, youtu.be, shorts, etc.).\n *\n * @returns The video id, or `null` when the address is not a YouTube URL.\n */\nexport const getYoutubeVideoId = (src: string | undefined): string | null => {\n if (!src) return null;\n\n const match = src.match(\n /(?:youtu\\.be\\/|youtube(?:-nocookie)?\\.com\\/(?:embed\\/|v\\/|watch\\?v=|shorts\\/))([\\w-]{11})/i\n );\n\n return match?.[1] ?? null;\n};\n\n/**\n * The address the YouTube player is loaded from for a given video.\n */\nexport const getYoutubeEmbedUrl = (videoId: string): string =>\n `https://www.youtube.com/embed/${videoId}`;\n\n/**\n * The address of the video on youtube.com itself.\n */\nexport const getYoutubeWatchUrl = (videoId: string): string =>\n `https://www.youtube.com/watch?v=${videoId}`;\n"],"mappings":";;;;;;AAKA,MAAa,qBAAqB,QAA2C;CAC3E,IAAI,CAAC,KAAK,OAAO;CAMjB,OAJc,IAAI,MAChB,4FAGS,CAAC,GAAG,MAAM;AACvB;;;;AAKA,MAAa,sBAAsB,YACjC,iCAAiC;;;;AAKnC,MAAa,sBAAsB,YACjC,mCAAmC"}
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
- import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
5
- import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
6
4
  import { Button } from "../Button/Button.mjs";
7
5
  import { Container } from "../Container/index.mjs";
6
+ import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
7
+ import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
8
8
  import { H3 } from "../Headers/index.mjs";
9
9
  import { useEffect } from "react";
10
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
+ import { MaxHeightSmoother } from "../MaxHeightSmoother/index.mjs";
4
5
  import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
5
6
  import { useScrollDetection } from "../../hooks/useScrollDetection.mjs";
6
- import { MaxHeightSmoother } from "../MaxHeightSmoother/index.mjs";
7
7
  import { Burger } from "./Burger.mjs";
8
8
  import { useEffect, useRef, useState } from "react";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
- import { useItemSelector } from "../../hooks/useItemSelector.mjs";
5
4
  import { Button } from "../Button/Button.mjs";
5
+ import { useItemSelector } from "../../hooks/useItemSelector.mjs";
6
6
  import { useEffect, useRef } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { cva } from "class-variance-authority";
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
4
- import { useDevice } from "../../hooks/useDevice.mjs";
5
- import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
6
3
  import { Button } from "../Button/Button.mjs";
7
4
  import { Container } from "../Container/index.mjs";
5
+ import { useDevice } from "../../hooks/useDevice.mjs";
8
6
  import { KeyboardShortcut } from "../KeyboardShortcut/KeyboardShortcut.mjs";
9
7
  import { Popover } from "../Popover/dynamic.mjs";
8
+ import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
9
+ import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
10
10
  import { MaxWidthSmoother } from "../MaxWidthSmoother/index.mjs";
11
11
  import { isElementAtTopAndNotCovered } from "./isElementAtTopAndNotCovered.mjs";
12
12
  import { useIsRightDrawerOpen, useRightDrawerActions } from "./useRightDrawer.mjs";
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
- import { useHorizontalSwipe } from "../../hooks/useHorizontalSwipe.mjs";
5
4
  import { TabSelector } from "../TabSelector/TabSelector.mjs";
5
+ import { useHorizontalSwipe } from "../../hooks/useHorizontalSwipe.mjs";
6
6
  import { useTabContext } from "./TabContext.mjs";
7
7
  import { Children, createContext, isValidElement, useState } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -115,6 +115,7 @@ import { KeyboardScreenAdapter } from "./KeyboardScreenAdapter/index.mjs";
115
115
  import { LanguageBackground } from "./LanguageBackground/LanguageBackground.mjs";
116
116
  import { LanguageSection, LocalCardList } from "./LanguageBackground/LanguageSection.mjs";
117
117
  import { LocaleSwitcher } from "./LocaleSwitcherDropDown/LocaleSwitcher.mjs";
118
+ import { getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl } from "./MarkDownRender/youtubeUrl.mjs";
118
119
  import { MarkDownIframe } from "./MarkDownRender/MarkDownIframe.mjs";
119
120
  import { Tab } from "./Tab/Tab.mjs";
120
121
  import { Table } from "./Table/Table.mjs";
@@ -142,4 +143,4 @@ import { Terminal } from "./Terminal/Terminal.mjs";
142
143
  import { DesktopThemeSwitcher } from "./ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs";
143
144
  import { MobileThemeSwitcher } from "./ThemeSwitcherDropDown/MobileThemeSwitcher.mjs";
144
145
 
145
- export { Accordion, AutoCompleteTextarea, AutoSizedTextArea, Avatar, Badge, BitbucketLogo, Breadcrumb, Browser, Burger, Button, Carousel, Checkbox, ClickOutsideDiv, Code, CodeBlock, CodeDefault, CodeHighlighted, CollapsibleTable, Command, CommandRoot, Container, ContentEditableTextArea, ContentEditor, ContentEditorInput, ContentEditorTextArea, ContentSelector, CopyButton, CopyToClipboard, DesktopNavbar, DesktopThemeSwitcher, Detail, DictionaryCreationForm, DictionaryEditor, DictionaryFieldEditor, DiscordLogo, DotPattern, DropDown, EditableFieldInput, EditableFieldTextArea, ExpandCollapse, FacebookLogo, FileList, Flag, flags_exports as Flags, Footer, Form, AutoSizedTextAreaElement as FormAutoSizedTextArea, Button as FormButton, CheckboxElement as FormCheckbox, FormControl, FormDescription, EditableFieldInputElement as FormEditableFieldInput, EditableFieldTextAreaElement as FormEditableFieldTextArea, FormElement, FormField, InputElement as FormInput, InputPasswordElement as FormInputPassword, FormItem, FormLabelLayout as FormLabel, FormMessage, MultiSelectElement as FormMultiSelect, OTPElement as FormOTP, SearchInputElement as FormSearchInput, SelectElement as FormSelect, SwitchSelectorElement as FormSwitchSelector, TextAreaElement as FormTextArea, GitHubLogo, GitLabLogo, GoogleLogo, GridDistortionPattern, GridPattern, H1, H2, H3, H4, H5, H6, HTMLRenderer, HeightResizer, HideShow, Hr, IDE, InformationTag, Input, InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, InputPassword, InstagramLogo, KeyPathBreadcrumb, KeyboardScreenAdapter, KeyboardShortcut, Label, LanguageBackground, LanguageSection, Link, LinkedInLogo, Loader, LocalCardList, LocaleSwitcher, LocaleSwitcherContent, LocaleSwitcherContentProvider, Logo, LogoTextOnly, LogoWithText, LogoWithTextBelow, MarkDownIframe, MarkdownEditor, MarkdownRenderer, MaxHeightSmoother, MaxWidthSmoother, MobileNavbar, MobileThemeSwitcher, Modal, MultiSelect, Navbar, NodeEditor, NumberItemsSelector, OTPInput, OTPInputContext, Pagination, Popover, PopoverStatic, PressableSpan, ProductHuntLogo, Radio, RightDrawer, SaveForm, SearchInput, Select, SelectContent, SelectLabel, SelectSeparator, ShowingResultsNumberItems, SmartTable, SocialNetworks, Spotlight, Step, Steps, SwitchSelector, Tab, TabSelector, Table, Tag, Td, TechLogo, techLogos_exports as TechLogos, Terminal, TextArea, TextEditor, TextEditorContainer, Th, TiktokLogo, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Tr, VersionSwitcher, VersionSwitcherProvider, VerticalSwitchSelector, WithResizer, XLogo, YoutubeLogo, badgeVariants, baseMarkdownComponents, buttonVariants, checkIsExternalLink, checkboxVariants, choiceVariant, containerVariants, createEditorUploadFn, defaultChoices, drawerManager, getCapitals, getIntlayerHTMLOptions, getIntlayerMarkdownOptions, indicatorVariant, inputSlotVariants, inputVariants, isTextChildren, linkVariants, paginationVariants, radioVariants, reducer, shikiWrapperClassName, switchSelectorVariant, toast, toastVariants, traceKeys, useContentEditable, useCopyToClipboard, useForm, useFormField, useIsRightDrawerOpen, useLocaleSwitcherContent, usePasswordManagerBadge, usePopoverIds, usePrevious, useRightDrawer, useRightDrawerActions, useToast, useVersionSwitcher };
146
+ export { Accordion, AutoCompleteTextarea, AutoSizedTextArea, Avatar, Badge, BitbucketLogo, Breadcrumb, Browser, Burger, Button, Carousel, Checkbox, ClickOutsideDiv, Code, CodeBlock, CodeDefault, CodeHighlighted, CollapsibleTable, Command, CommandRoot, Container, ContentEditableTextArea, ContentEditor, ContentEditorInput, ContentEditorTextArea, ContentSelector, CopyButton, CopyToClipboard, DesktopNavbar, DesktopThemeSwitcher, Detail, DictionaryCreationForm, DictionaryEditor, DictionaryFieldEditor, DiscordLogo, DotPattern, DropDown, EditableFieldInput, EditableFieldTextArea, ExpandCollapse, FacebookLogo, FileList, Flag, flags_exports as Flags, Footer, Form, AutoSizedTextAreaElement as FormAutoSizedTextArea, Button as FormButton, CheckboxElement as FormCheckbox, FormControl, FormDescription, EditableFieldInputElement as FormEditableFieldInput, EditableFieldTextAreaElement as FormEditableFieldTextArea, FormElement, FormField, InputElement as FormInput, InputPasswordElement as FormInputPassword, FormItem, FormLabelLayout as FormLabel, FormMessage, MultiSelectElement as FormMultiSelect, OTPElement as FormOTP, SearchInputElement as FormSearchInput, SelectElement as FormSelect, SwitchSelectorElement as FormSwitchSelector, TextAreaElement as FormTextArea, GitHubLogo, GitLabLogo, GoogleLogo, GridDistortionPattern, GridPattern, H1, H2, H3, H4, H5, H6, HTMLRenderer, HeightResizer, HideShow, Hr, IDE, InformationTag, Input, InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, InputPassword, InstagramLogo, KeyPathBreadcrumb, KeyboardScreenAdapter, KeyboardShortcut, Label, LanguageBackground, LanguageSection, Link, LinkedInLogo, Loader, LocalCardList, LocaleSwitcher, LocaleSwitcherContent, LocaleSwitcherContentProvider, Logo, LogoTextOnly, LogoWithText, LogoWithTextBelow, MarkDownIframe, MarkdownEditor, MarkdownRenderer, MaxHeightSmoother, MaxWidthSmoother, MobileNavbar, MobileThemeSwitcher, Modal, MultiSelect, Navbar, NodeEditor, NumberItemsSelector, OTPInput, OTPInputContext, Pagination, Popover, PopoverStatic, PressableSpan, ProductHuntLogo, Radio, RightDrawer, SaveForm, SearchInput, Select, SelectContent, SelectLabel, SelectSeparator, ShowingResultsNumberItems, SmartTable, SocialNetworks, Spotlight, Step, Steps, SwitchSelector, Tab, TabSelector, Table, Tag, Td, TechLogo, techLogos_exports as TechLogos, Terminal, TextArea, TextEditor, TextEditorContainer, Th, TiktokLogo, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Tr, VersionSwitcher, VersionSwitcherProvider, VerticalSwitchSelector, WithResizer, XLogo, YoutubeLogo, badgeVariants, baseMarkdownComponents, buttonVariants, checkIsExternalLink, checkboxVariants, choiceVariant, containerVariants, createEditorUploadFn, defaultChoices, drawerManager, getCapitals, getIntlayerHTMLOptions, getIntlayerMarkdownOptions, getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl, indicatorVariant, inputSlotVariants, inputVariants, isTextChildren, linkVariants, paginationVariants, radioVariants, reducer, shikiWrapperClassName, switchSelectorVariant, toast, toastVariants, traceKeys, useContentEditable, useCopyToClipboard, useForm, useFormField, useIsRightDrawerOpen, useLocaleSwitcherContent, usePasswordManagerBadge, usePopoverIds, usePrevious, useRightDrawer, useRightDrawerActions, useToast, useVersionSwitcher };
@@ -1,17 +1,17 @@
1
- import { useKeyboardDetector } from "./useKeyboardDetector.mjs";
2
- import { useGetElementOrWindow } from "./useGetElementOrWindow.mjs";
3
- import { useScrollY } from "./useScrollY.mjs";
1
+ import { calculateIsMobile, checkIsIOS, checkIsIphoneOrSafariDevice, checkIsMac, checkIsMobileScreen, checkIsMobileUserAgent, getBreakpointFromSize, useDevice } from "./useDevice.mjs";
2
+ import { useItemSelector } from "./useItemSelector.mjs";
4
3
  import { usePersistedStore } from "./usePersistedStore.mjs";
4
+ import { useGetElementById } from "./useGetElementById.mjs";
5
+ import { useGetElementOrWindow } from "./useGetElementOrWindow.mjs";
5
6
  import { useHorizontalSwipe } from "./useHorizontalSwipe.mjs";
6
- import { useItemSelector } from "./useItemSelector.mjs";
7
- import { NO_HIGHLIGHTED_INDEX, useListKeyboardNavigation } from "./useListKeyboardNavigation.mjs";
8
- import { calculateIsMobile, checkIsIOS, checkIsIphoneOrSafariDevice, checkIsMac, checkIsMobileScreen, checkIsMobileUserAgent, getBreakpointFromSize, useDevice } from "./useDevice.mjs";
9
- import { useSearch } from "./useSearch.mjs";
10
7
  import { useIsMounted } from "./useIsMounted.mjs";
11
- import { useGetElementById } from "./useGetElementById.mjs";
8
+ import { useKeyboardDetector } from "./useKeyboardDetector.mjs";
9
+ import { NO_HIGHLIGHTED_INDEX, useListKeyboardNavigation } from "./useListKeyboardNavigation.mjs";
12
10
  import { useScreenWidth } from "./useScreenWidth.mjs";
13
11
  import { useScrollBlockage } from "./useScrollBlockage/index.mjs";
14
12
  import { useScrollDetection } from "./useScrollDetection.mjs";
13
+ import { useScrollY } from "./useScrollY.mjs";
14
+ import { useSearch } from "./useSearch.mjs";
15
15
  import { useStickToBottom } from "./useStickToBottom.mjs";
16
16
 
17
17
  export { NO_HIGHLIGHTED_INDEX, calculateIsMobile, checkIsIOS, checkIsIphoneOrSafariDevice, checkIsMac, checkIsMobileScreen, checkIsMobileUserAgent, getBreakpointFromSize, useDevice, useGetElementById, useGetElementOrWindow, useHorizontalSwipe, useIsMounted, useItemSelector, useKeyboardDetector, useListKeyboardNavigation, usePersistedStore, useScreenWidth, useScrollBlockage, useScrollDetection, useScrollY, useSearch, useStickToBottom };
@@ -1 +1 @@
1
- {"version":3,"file":"MarkDownIframe.d.ts","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownIframe.tsx"],"mappings":";;qBA+La,gBAAgB,GAAG"}
1
+ {"version":3,"file":"MarkDownIframe.d.ts","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownIframe.tsx"],"mappings":";;qBA2Ka,gBAAgB,GAAG"}
@@ -1,3 +1,4 @@
1
1
  import { MarkDownIframe } from "./MarkDownIframe.js";
2
+ import { getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl } from "./youtubeUrl.js";
2
3
  import { MarkdownRenderer, ParsedMarkdown, baseMarkdownComponents, getIntlayerMarkdownOptions } from "./MarkDownRender.js";
3
- export { MarkDownIframe, MarkdownRenderer, type ParsedMarkdown, baseMarkdownComponents, getIntlayerMarkdownOptions };
4
+ export { MarkDownIframe, MarkdownRenderer, type ParsedMarkdown, baseMarkdownComponents, getIntlayerMarkdownOptions, getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl };
@@ -0,0 +1,17 @@
1
+ //#region src/components/MarkDownRender/youtubeUrl.d.ts
2
+ /**
3
+ * Reads the video id out of a YouTube address (embed, watch, youtu.be, shorts, etc.).
4
+ *
5
+ * @returns The video id, or `null` when the address is not a YouTube URL.
6
+ */
7
+ export declare const getYoutubeVideoId: (src: string | undefined) => string | null;
8
+ /**
9
+ * The address the YouTube player is loaded from for a given video.
10
+ */
11
+ export declare const getYoutubeEmbedUrl: (videoId: string) => string;
12
+ /**
13
+ * The address of the video on youtube.com itself.
14
+ */
15
+ export declare const getYoutubeWatchUrl: (videoId: string) => string;
16
+ //#endregion
17
+ //# sourceMappingURL=youtubeUrl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"youtubeUrl.d.ts","names":[],"sources":["../../../../src/components/MarkDownRender/youtubeUrl.ts"],"mappings":";;;;;;qBAKa,oBAAiB;;;;qBAajB,qBAAkB;;;;qBAMlB,qBAAkB"}
@@ -114,6 +114,7 @@ import { LogoWithText } from "./Logo/LogoWithText.js";
114
114
  import { LogoWithTextBelow } from "./Logo/LogoWithTextBelow.js";
115
115
  import "./Logo/index.js";
116
116
  import { MarkDownIframe } from "./MarkDownRender/MarkDownIframe.js";
117
+ import { getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl } from "./MarkDownRender/youtubeUrl.js";
117
118
  import "./MarkDownRender/index.js";
118
119
  import { CreateEditorUploadFnOptions, UploadImageFn, createEditorUploadFn } from "./MarkdownEditor/imageUpload.js";
119
120
  import { MarkdownEditor, MarkdownEditorProps } from "./MarkdownEditor/MarkdownEditor.js";
@@ -183,4 +184,4 @@ import { reducer, toast, useToast } from "./Toaster/useToast.js";
183
184
  import "./Toaster/index.js";
184
185
  import { WithResizer } from "./WithResizer/index.js";
185
186
  import { MarkdownRenderer, ParsedMarkdown, baseMarkdownComponents, getIntlayerMarkdownOptions } from "./MarkDownRender/MarkDownRender.js";
186
- export { Accordion, AccordionProps, AutoCompleteTextarea, AutoSizedTextArea, AutoSizedTextAreaProps, AutocompleteTextAreaProps, Avatar, AvatarProps, Badge, BadgeColor, BadgeProps, BadgeSize, BadgeVariant, BadgeVariantProps, BitbucketLogo, Breadcrumb, BreadcrumbLink, BreadcrumbProps, Browser, BrowserProps, Burger, Button, ButtonColor, ButtonProps, ButtonSize, ButtonTextAlign, ButtonVariant, Carousel, Checkbox, CheckboxColor, CheckboxProps, CheckboxSize, ClickOutsideDiv, ClickOutsideDivProps, Code, CodeBlock, CodeBlockProps, CodeCompAttributes, CodeDefault, CodeHighlighted, type CodeLanguage, CollapsibleTable, CollapsibleTableProps, Command, CommandRoot, Container, ContainerBackground, ContainerBorderColor, ContainerGap, ContainerPadding, ContainerProps, ContainerRoundedSize, ContainerSeparator, ContainerTransparency, ContentEditableTextArea, ContentEditableTextAreaHandle, ContentEditableTextAreaProps, ContentEditor, ContentEditorInput, ContentEditorInputProps, ContentEditorProps, ContentEditorTextArea, ContentEditorTextAreaProps, ContentSelector, CopyButton, CopyToClipboard, CopyToClipboardProps, type CreateEditorUploadFnOptions, DesktopNavbar, DesktopThemeSwitcher, Detail, DetailProps, DictionaryCreationForm, DictionaryEditor, DictionaryFieldEditor, DiscordLogo, DotPattern, DropDown, DropDownAlign, DropDownProps, DropDownType, DropDownYAlign, EditableFieldInput, EditableFieldTextArea, ExpandCollapse, ExpandCollapseProps, FacebookLogo, FileList, FileListProps, Flag, flags_d_exports as Flags, Footer, FooterLink, Form, AutoSizedTextAreaElement as FormAutoSizedTextArea, Button as FormButton, CheckboxElement as FormCheckbox, FormControl, FormDescription, EditableFieldInputElement as FormEditableFieldInput, EditableFieldTextAreaElement as FormEditableFieldTextArea, FormElement, FormField, InputElement as FormInput, InputPasswordElement as FormInputPassword, FormItem, FormLabelLayout as FormLabel, FormMessage, MultiSelectElement as FormMultiSelect, OTPElement as FormOTP, SearchInputElement as FormSearchInput, SelectElement as FormSelect, SwitchSelectorElement as FormSwitchSelector, TextAreaElement as FormTextArea, GitHubLogo, GitLabLogo, GoogleLogo, GridDistortionPattern, GridPattern, H1, H2, H3, H4, H5, H6, HTMLRenderer, HeadingProps, HeightResizer, HideShow, HideShowProps, Hr, IDE, IDEProps, InformationTag, Input, InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, InputPassword, InputProps, InputSize, InputVariant, InstagramLogo, KeyList, KeyPathBreadcrumb, KeyboardScreenAdapter, KeyboardShortcut, KeyboardShortcutProps, KeyboardShortcutType, Label, LabelProps, LanguageBackground, LanguageSection, Link, LinkColor, LinkGroup, LinkProps, LinkRoundedSize, LinkSize, LinkUnderlined, LinkVariant, LinkedInLogo, Loader, LoaderProps, LocalCardList, LocaleSwitcher, LocaleSwitcherContent, LocaleSwitcherContentProps, LocaleSwitcherContentProvider, LocaleSwitcherProps, Logo, LogoTextOnly, LogoWithText, LogoWithTextBelow, MarkDownIframe, MarkdownEditor, type MarkdownEditorProps, MarkdownRenderer, MaxHeightSmoother, MaxWidthSmoother, MobileNavbar, MobileThemeSwitcher, Modal, ModalSize, Modes, MultiSelect, Navbar, NodeEditor, NodeEditorProps, NumberItemsSelector, NumberItemsSelectorProps, OTPInput, OTPInputContext, OTPInputProps, Pagination, PaginationProps, PaginationSize, PaginationVariant, PanelProps, type ParsedMarkdown, Popover, PopoverProps, PopoverStatic, PopoverType, PopoverXAlign, PopoverYAlign, PressableSpan, ProductHuntLogo, Radio, RadioColor, RadioProps, RadioSize, RenderProps, RightDrawer, SaveForm, SearchInput, Select, SelectContent, SelectContentPosition, SelectLabel, SelectSeparator, ShowingResultsNumberItems, ShowingResultsNumberItemsProps, SlotProps, SmartTable, SocialNetworks, Spotlight, Step, Steps, SwitchSelector, SwitchSelectorBaseProps, SwitchSelectorChoice, SwitchSelectorChoices, SwitchSelectorColor, SwitchSelectorProps, SwitchSelectorSize, Tab, TabItemProps, TabProps, TabSelector, TabSelectorColor, TabSelectorItemProps, TabSelectorProps, Table, TableProps, Tag, TagBackground, TagBorder, TagColor, TagRoundedSize, TagSize, Td, TechLogo, type TechLogoName, techLogos_d_exports as TechLogos, Terminal, TerminalProps, TextArea, TextAreaProps, TextEditor, TextEditorContainer, TextEditorProps, Th, TiktokLogo, Toast, ToastAction, ToastActionElement, ToastClose, ToastDescription, ToastProps, ToastProvider, ToastTitle, ToastViewport, Toaster, Tr, TriggerProps, type UploadImageFn, VersionSwitcher, VersionSwitcherProps, VersionSwitcherProvider, VerticalSwitchSelector, VerticalSwitchSelectorProps, WithResizer, XLogo, YoutubeLogo, badgeVariants, baseMarkdownComponents, buttonVariants, checkIsExternalLink, checkboxVariants, choiceVariant, containerVariants, createEditorUploadFn, defaultChoices, drawerManager, getCapitals, getIntlayerHTMLOptions, getIntlayerMarkdownOptions, indicatorVariant, inputSlotVariants, inputVariants, isTextChildren, linkVariants, paginationVariants, radioVariants, reducer, shikiWrapperClassName, switchSelectorVariant, toast, toastVariants, traceKeys, useContentEditable, useCopyToClipboard, useForm, useFormField, useIsRightDrawerOpen, useLocaleSwitcherContent, usePasswordManagerBadge, usePopoverIds, usePrevious, useRightDrawer, useRightDrawerActions, useToast, useVersionSwitcher };
187
+ export { Accordion, AccordionProps, AutoCompleteTextarea, AutoSizedTextArea, AutoSizedTextAreaProps, AutocompleteTextAreaProps, Avatar, AvatarProps, Badge, BadgeColor, BadgeProps, BadgeSize, BadgeVariant, BadgeVariantProps, BitbucketLogo, Breadcrumb, BreadcrumbLink, BreadcrumbProps, Browser, BrowserProps, Burger, Button, ButtonColor, ButtonProps, ButtonSize, ButtonTextAlign, ButtonVariant, Carousel, Checkbox, CheckboxColor, CheckboxProps, CheckboxSize, ClickOutsideDiv, ClickOutsideDivProps, Code, CodeBlock, CodeBlockProps, CodeCompAttributes, CodeDefault, CodeHighlighted, type CodeLanguage, CollapsibleTable, CollapsibleTableProps, Command, CommandRoot, Container, ContainerBackground, ContainerBorderColor, ContainerGap, ContainerPadding, ContainerProps, ContainerRoundedSize, ContainerSeparator, ContainerTransparency, ContentEditableTextArea, ContentEditableTextAreaHandle, ContentEditableTextAreaProps, ContentEditor, ContentEditorInput, ContentEditorInputProps, ContentEditorProps, ContentEditorTextArea, ContentEditorTextAreaProps, ContentSelector, CopyButton, CopyToClipboard, CopyToClipboardProps, type CreateEditorUploadFnOptions, DesktopNavbar, DesktopThemeSwitcher, Detail, DetailProps, DictionaryCreationForm, DictionaryEditor, DictionaryFieldEditor, DiscordLogo, DotPattern, DropDown, DropDownAlign, DropDownProps, DropDownType, DropDownYAlign, EditableFieldInput, EditableFieldTextArea, ExpandCollapse, ExpandCollapseProps, FacebookLogo, FileList, FileListProps, Flag, flags_d_exports as Flags, Footer, FooterLink, Form, AutoSizedTextAreaElement as FormAutoSizedTextArea, Button as FormButton, CheckboxElement as FormCheckbox, FormControl, FormDescription, EditableFieldInputElement as FormEditableFieldInput, EditableFieldTextAreaElement as FormEditableFieldTextArea, FormElement, FormField, InputElement as FormInput, InputPasswordElement as FormInputPassword, FormItem, FormLabelLayout as FormLabel, FormMessage, MultiSelectElement as FormMultiSelect, OTPElement as FormOTP, SearchInputElement as FormSearchInput, SelectElement as FormSelect, SwitchSelectorElement as FormSwitchSelector, TextAreaElement as FormTextArea, GitHubLogo, GitLabLogo, GoogleLogo, GridDistortionPattern, GridPattern, H1, H2, H3, H4, H5, H6, HTMLRenderer, HeadingProps, HeightResizer, HideShow, HideShowProps, Hr, IDE, IDEProps, InformationTag, Input, InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, InputPassword, InputProps, InputSize, InputVariant, InstagramLogo, KeyList, KeyPathBreadcrumb, KeyboardScreenAdapter, KeyboardShortcut, KeyboardShortcutProps, KeyboardShortcutType, Label, LabelProps, LanguageBackground, LanguageSection, Link, LinkColor, LinkGroup, LinkProps, LinkRoundedSize, LinkSize, LinkUnderlined, LinkVariant, LinkedInLogo, Loader, LoaderProps, LocalCardList, LocaleSwitcher, LocaleSwitcherContent, LocaleSwitcherContentProps, LocaleSwitcherContentProvider, LocaleSwitcherProps, Logo, LogoTextOnly, LogoWithText, LogoWithTextBelow, MarkDownIframe, MarkdownEditor, type MarkdownEditorProps, MarkdownRenderer, MaxHeightSmoother, MaxWidthSmoother, MobileNavbar, MobileThemeSwitcher, Modal, ModalSize, Modes, MultiSelect, Navbar, NodeEditor, NodeEditorProps, NumberItemsSelector, NumberItemsSelectorProps, OTPInput, OTPInputContext, OTPInputProps, Pagination, PaginationProps, PaginationSize, PaginationVariant, PanelProps, type ParsedMarkdown, Popover, PopoverProps, PopoverStatic, PopoverType, PopoverXAlign, PopoverYAlign, PressableSpan, ProductHuntLogo, Radio, RadioColor, RadioProps, RadioSize, RenderProps, RightDrawer, SaveForm, SearchInput, Select, SelectContent, SelectContentPosition, SelectLabel, SelectSeparator, ShowingResultsNumberItems, ShowingResultsNumberItemsProps, SlotProps, SmartTable, SocialNetworks, Spotlight, Step, Steps, SwitchSelector, SwitchSelectorBaseProps, SwitchSelectorChoice, SwitchSelectorChoices, SwitchSelectorColor, SwitchSelectorProps, SwitchSelectorSize, Tab, TabItemProps, TabProps, TabSelector, TabSelectorColor, TabSelectorItemProps, TabSelectorProps, Table, TableProps, Tag, TagBackground, TagBorder, TagColor, TagRoundedSize, TagSize, Td, TechLogo, type TechLogoName, techLogos_d_exports as TechLogos, Terminal, TerminalProps, TextArea, TextAreaProps, TextEditor, TextEditorContainer, TextEditorProps, Th, TiktokLogo, Toast, ToastAction, ToastActionElement, ToastClose, ToastDescription, ToastProps, ToastProvider, ToastTitle, ToastViewport, Toaster, Tr, TriggerProps, type UploadImageFn, VersionSwitcher, VersionSwitcherProps, VersionSwitcherProvider, VerticalSwitchSelector, VerticalSwitchSelectorProps, WithResizer, XLogo, YoutubeLogo, badgeVariants, baseMarkdownComponents, buttonVariants, checkIsExternalLink, checkboxVariants, choiceVariant, containerVariants, createEditorUploadFn, defaultChoices, drawerManager, getCapitals, getIntlayerHTMLOptions, getIntlayerMarkdownOptions, getYoutubeEmbedUrl, getYoutubeVideoId, getYoutubeWatchUrl, indicatorVariant, inputSlotVariants, inputVariants, isTextChildren, linkVariants, paginationVariants, radioVariants, reducer, shikiWrapperClassName, switchSelectorVariant, toast, toastVariants, traceKeys, useContentEditable, useCopyToClipboard, useForm, useFormField, useIsRightDrawerOpen, useLocaleSwitcherContent, usePasswordManagerBadge, usePopoverIds, usePrevious, useRightDrawer, useRightDrawerActions, useToast, useVersionSwitcher };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlayer/design-system",
3
- "version": "9.5.1",
3
+ "version": "9.5.2",
4
4
  "private": false,
5
5
  "description": "Intlayer design system, including UI components used in the Intlayer editor, website, and visual editor/CMS.",
6
6
  "keywords": [
@@ -455,15 +455,15 @@
455
455
  "typecheck_": "tsc --noEmit --project tsconfig.types.json"
456
456
  },
457
457
  "dependencies": {
458
- "@better-auth/passkey": "1.7.3",
459
- "@better-auth/sso": "1.7.3",
460
- "@intlayer/api": "9.5.1",
461
- "@intlayer/config": "9.5.1",
462
- "@intlayer/core": "9.5.1",
463
- "@intlayer/dictionaries-entry": "9.5.1",
464
- "@intlayer/editor-react": "9.5.1",
465
- "@intlayer/engine": "9.5.1",
466
- "@intlayer/types": "9.5.1",
458
+ "@better-auth/passkey": "1.7.4",
459
+ "@better-auth/sso": "1.7.4",
460
+ "@intlayer/api": "9.5.2",
461
+ "@intlayer/config": "9.5.2",
462
+ "@intlayer/core": "9.5.2",
463
+ "@intlayer/dictionaries-entry": "9.5.2",
464
+ "@intlayer/editor-react": "9.5.2",
465
+ "@intlayer/engine": "9.5.2",
466
+ "@intlayer/types": "9.5.2",
467
467
  "@radix-ui/react-dialog": "1.1.23",
468
468
  "@radix-ui/react-select": "2.3.7",
469
469
  "@radix-ui/react-slot": "1.3.3",
@@ -471,7 +471,7 @@
471
471
  "@tailwindcss/aspect-ratio": "0.4.2",
472
472
  "@tanstack/query-async-storage-persister": "5.102.8",
473
473
  "@tanstack/react-query-persist-client": "5.102.8",
474
- "@tanstack/react-virtual": "3.14.11",
474
+ "@tanstack/react-virtual": "3.14.12",
475
475
  "@tiptap/core": "3.31.3",
476
476
  "@tiptap/extension-character-count": "3.31.3",
477
477
  "@tiptap/extension-code-block": "3.31.3",
@@ -490,22 +490,22 @@
490
490
  "@tiptap/react": "3.31.3",
491
491
  "@tiptap/starter-kit": "3.31.3",
492
492
  "@tiptap/suggestion": "3.31.3",
493
- "better-auth": "1.7.3",
493
+ "better-auth": "1.7.4",
494
494
  "class-variance-authority": "0.7.1",
495
495
  "cmdk": "1.1.1",
496
496
  "cnfast": "0.2.0",
497
497
  "defu": "6.1.7",
498
498
  "dompurify": "3.4.15",
499
499
  "isomorphic-dompurify": "4.1.0",
500
- "react-intlayer": "9.5.1",
500
+ "react-intlayer": "9.5.2",
501
501
  "rollup-preserve-directives": "1.1.3",
502
502
  "tiptap-extension-global-drag-handle": "^0.1.18",
503
503
  "tiptap-markdown": "^0.9.0",
504
504
  "tunnel-rat": "^0.1.2",
505
- "zod": "4.5.4"
505
+ "zod": "4.6.2"
506
506
  },
507
507
  "devDependencies": {
508
- "@intlayer/backend": "9.5.1",
508
+ "@intlayer/backend": "9.5.2",
509
509
  "@shikijs/core": "4.4.3",
510
510
  "@shikijs/engine-oniguruma": "4.4.3",
511
511
  "@shikijs/transformers": "4.4.3",
@@ -526,27 +526,27 @@
526
526
  "@testing-library/user-event": "14.6.7",
527
527
  "@types/dompurify": "3.2.0",
528
528
  "@types/espree": "11.1.0",
529
- "@types/node": "26.5.0",
530
- "@types/react": "19.2.18",
531
- "@types/react-dom": "19.2.7",
529
+ "@types/node": "^22",
530
+ "@types/react": "19.3.0",
531
+ "@types/react-dom": "19.3.0",
532
532
  "@utils/ts-config": "1.0.4",
533
533
  "@utils/ts-config-types": "1.0.4",
534
534
  "fast-glob": "3.3.3",
535
- "intlayer": "9.5.1",
535
+ "intlayer": "9.5.2",
536
536
  "rimraf": "6.1.3",
537
537
  "shiki": "4.4.3",
538
538
  "storybook": "10.6.0",
539
539
  "tsdown": "0.23.0",
540
540
  "typescript": "7.0.2",
541
- "vite": "8.2.2",
542
- "vite-intlayer": "9.5.1",
541
+ "vite": "8.3.0",
542
+ "vite-intlayer": "9.5.2",
543
543
  "vite-plugin-dts": "5.1.0",
544
544
  "vitest": "5.0.0"
545
545
  },
546
546
  "peerDependencies": {
547
547
  "@better-fetch/fetch": "1.3.1",
548
548
  "@hookform/resolvers": "5.9.1",
549
- "@intlayer/backend": "9.5.1",
549
+ "@intlayer/backend": "9.5.2",
550
550
  "@monaco-editor/react": "4.7.0",
551
551
  "@shikijs/core": "4.4.3",
552
552
  "@shikijs/engine-oniguruma": "4.4.3",
@@ -555,12 +555,12 @@
555
555
  "@tanstack/react-query-devtools": "5.102.8",
556
556
  "framer-motion": "13.2.0",
557
557
  "fuse.js": "7.5.0",
558
- "intlayer": "9.5.1",
559
- "lucide-react": "1.43.0",
558
+ "intlayer": "9.5.2",
559
+ "lucide-react": "1.45.0",
560
560
  "react": ">=16.0.0",
561
561
  "react-dom": ">=16.0.0",
562
562
  "react-hook-form": "7.87.0",
563
- "react-intlayer": "9.5.1",
563
+ "react-intlayer": "9.5.2",
564
564
  "shiki": "4.4.3",
565
565
  "tailwindcss": "4.3.3"
566
566
  },