@ndla/ui 56.0.186-alpha.0 → 56.0.187-alpha.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.
Files changed (78) hide show
  1. package/dist/panda.buildinfo.json +15 -5
  2. package/dist/styles.css +53 -13
  3. package/es/AudioPlayer/AudioElement.mjs +12 -0
  4. package/es/AudioPlayer/AudioElement.mjs.map +1 -0
  5. package/es/AudioPlayer/AudioPlayer.mjs +7 -2
  6. package/es/AudioPlayer/AudioPlayer.mjs.map +1 -1
  7. package/es/AudioPlayer/AudioProgress.mjs +54 -0
  8. package/es/AudioPlayer/AudioProgress.mjs.map +1 -0
  9. package/es/AudioPlayer/CompactAudioPlayer.mjs +111 -0
  10. package/es/AudioPlayer/CompactAudioPlayer.mjs.map +1 -0
  11. package/es/AudioPlayer/Controls.mjs +25 -110
  12. package/es/AudioPlayer/Controls.mjs.map +1 -1
  13. package/es/AudioPlayer/PlayButton.mjs +24 -0
  14. package/es/AudioPlayer/PlayButton.mjs.map +1 -0
  15. package/es/AudioPlayer/SpeechControl.mjs +5 -16
  16. package/es/AudioPlayer/SpeechControl.mjs.map +1 -1
  17. package/es/AudioPlayer/VolumeSlider.mjs +31 -0
  18. package/es/AudioPlayer/VolumeSlider.mjs.map +1 -0
  19. package/es/AudioPlayer/audioUtils.mjs +17 -0
  20. package/es/AudioPlayer/audioUtils.mjs.map +1 -0
  21. package/es/AudioPlayer/useAudioControls.mjs +55 -0
  22. package/es/AudioPlayer/useAudioControls.mjs.map +1 -0
  23. package/es/Embed/AudioEmbed.mjs +2 -5
  24. package/es/Embed/AudioEmbed.mjs.map +1 -1
  25. package/es/Gloss/Gloss.mjs +1 -2
  26. package/es/Gloss/Gloss.mjs.map +1 -1
  27. package/es/index.mjs +2 -1
  28. package/lib/AudioPlayer/AudioElement.d.ts +14 -0
  29. package/lib/AudioPlayer/AudioElement.js +13 -0
  30. package/lib/AudioPlayer/AudioElement.js.map +1 -0
  31. package/lib/AudioPlayer/AudioPlayer.d.ts +5 -4
  32. package/lib/AudioPlayer/AudioPlayer.js +7 -2
  33. package/lib/AudioPlayer/AudioPlayer.js.map +1 -1
  34. package/lib/AudioPlayer/AudioProgress.d.ts +16 -0
  35. package/lib/AudioPlayer/AudioProgress.js +55 -0
  36. package/lib/AudioPlayer/AudioProgress.js.map +1 -0
  37. package/lib/AudioPlayer/CompactAudioPlayer.d.ts +13 -0
  38. package/lib/AudioPlayer/CompactAudioPlayer.js +112 -0
  39. package/lib/AudioPlayer/CompactAudioPlayer.js.map +1 -0
  40. package/lib/AudioPlayer/Controls.d.ts +1 -0
  41. package/lib/AudioPlayer/Controls.js +25 -110
  42. package/lib/AudioPlayer/Controls.js.map +1 -1
  43. package/lib/AudioPlayer/PlayButton.d.ts +13 -0
  44. package/lib/AudioPlayer/PlayButton.js +25 -0
  45. package/lib/AudioPlayer/PlayButton.js.map +1 -0
  46. package/lib/AudioPlayer/SpeechControl.d.ts +1 -2
  47. package/lib/AudioPlayer/SpeechControl.js +5 -16
  48. package/lib/AudioPlayer/SpeechControl.js.map +1 -1
  49. package/lib/AudioPlayer/VolumeSlider.d.ts +14 -0
  50. package/lib/AudioPlayer/VolumeSlider.js +32 -0
  51. package/lib/AudioPlayer/VolumeSlider.js.map +1 -0
  52. package/lib/AudioPlayer/audioUtils.d.ts +8 -0
  53. package/lib/AudioPlayer/audioUtils.js +17 -0
  54. package/lib/AudioPlayer/audioUtils.js.map +1 -0
  55. package/lib/AudioPlayer/useAudioControls.d.ts +24 -0
  56. package/lib/AudioPlayer/useAudioControls.js +56 -0
  57. package/lib/AudioPlayer/useAudioControls.js.map +1 -0
  58. package/lib/Embed/AudioEmbed.js +2 -5
  59. package/lib/Embed/AudioEmbed.js.map +1 -1
  60. package/lib/Gloss/Gloss.js +1 -2
  61. package/lib/Gloss/Gloss.js.map +1 -1
  62. package/lib/index.d.ts +2 -0
  63. package/lib/index.js +2 -0
  64. package/package.json +2 -2
  65. package/src/AudioPlayer/AudioElement.tsx +20 -0
  66. package/src/AudioPlayer/{AudiPlayer.stories.tsx → AudioPlayer.stories.tsx} +10 -1
  67. package/src/AudioPlayer/AudioPlayer.tsx +12 -5
  68. package/src/AudioPlayer/AudioProgress.tsx +92 -0
  69. package/src/AudioPlayer/CompactAudioPlayer.tsx +124 -0
  70. package/src/AudioPlayer/Controls.tsx +36 -149
  71. package/src/AudioPlayer/PlayButton.tsx +24 -0
  72. package/src/AudioPlayer/SpeechControl.tsx +6 -19
  73. package/src/AudioPlayer/VolumeSlider.tsx +56 -0
  74. package/src/AudioPlayer/audioUtils.ts +15 -0
  75. package/src/AudioPlayer/useAudioControls.ts +80 -0
  76. package/src/Embed/AudioEmbed.tsx +3 -4
  77. package/src/Gloss/Gloss.tsx +1 -1
  78. package/src/index.ts +2 -0
@@ -0,0 +1,32 @@
1
+ require("../_virtual/_rolldown/runtime.js");
2
+ let _ndla_primitives = require("@ndla/primitives");
3
+ let _ndla_styled_system_jsx = require("@ndla/styled-system/jsx");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let i18next = require("i18next");
6
+ //#region src/AudioPlayer/VolumeSlider.tsx
7
+ const StyledSliderControl = (0, _ndla_styled_system_jsx.styled)(_ndla_primitives.SliderControl, { base: {
8
+ height: "surface.3xsmall",
9
+ minWidth: "small"
10
+ } });
11
+ const VolumeSlider = ({ value, onValueChange }) => {
12
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_ndla_primitives.SliderRoot, {
13
+ orientation: "vertical",
14
+ value: [value],
15
+ min: 0,
16
+ max: 100,
17
+ defaultValue: [100],
18
+ step: 1,
19
+ onValueChange,
20
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ndla_primitives.SliderLabel, {
21
+ srOnly: true,
22
+ children: (0, i18next.t)("audio.controls.adjustVolume")
23
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StyledSliderControl, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ndla_primitives.SliderTrack, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ndla_primitives.SliderRange, {}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ndla_primitives.SliderThumb, {
24
+ index: 0,
25
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ndla_primitives.SliderHiddenInput, {})
26
+ })] })]
27
+ });
28
+ };
29
+ //#endregion
30
+ exports.VolumeSlider = VolumeSlider;
31
+
32
+ //# sourceMappingURL=VolumeSlider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VolumeSlider.js","names":["SliderControl","SliderRoot","SliderLabel","SliderTrack","SliderRange","SliderThumb","SliderHiddenInput"],"sources":["../../src/AudioPlayer/VolumeSlider.tsx"],"sourcesContent":["/**\n * Copyright (c) 2026-present, NDLA.\n *\n * This source code is licensed under the GPLv3 license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport type { SliderValueChangeDetails } from \"@ark-ui/react\";\nimport {\n SliderControl,\n SliderRoot,\n SliderLabel,\n SliderTrack,\n SliderRange,\n SliderHiddenInput,\n SliderThumb,\n} from \"@ndla/primitives\";\nimport { styled } from \"@ndla/styled-system/jsx\";\nimport { t } from \"i18next\";\n\nconst StyledSliderControl = styled(SliderControl, {\n base: {\n height: \"surface.3xsmall\",\n minWidth: \"small\",\n },\n});\n\ninterface Props {\n value: number;\n onValueChange: (value: SliderValueChangeDetails) => void;\n}\n\nexport const VolumeSlider = ({ value, onValueChange }: Props) => {\n return (\n <SliderRoot\n orientation=\"vertical\"\n value={[value]}\n min={0}\n max={100}\n defaultValue={[100]}\n step={1}\n onValueChange={onValueChange}\n >\n <SliderLabel srOnly>{t(\"audio.controls.adjustVolume\")}</SliderLabel>\n <StyledSliderControl>\n <SliderTrack>\n <SliderRange />\n </SliderTrack>\n <SliderThumb index={0}>\n <SliderHiddenInput />\n </SliderThumb>\n </StyledSliderControl>\n </SliderRoot>\n );\n};\n"],"mappings":";;;;;;AAqBA,MAAM,uBAAA,GAAA,wBAAA,QAA6BA,iBAAAA,eAAe,EAChD,MAAM;CACJ,QAAQ;CACR,UAAU;CACX,EACF,CAAC;AAOF,MAAa,gBAAgB,EAAE,OAAO,oBAA2B;AAC/D,QACE,iBAAA,GAAA,kBAAA,MAACC,iBAAAA,YAAD;EACE,aAAY;EACZ,OAAO,CAAC,MAAM;EACd,KAAK;EACL,KAAK;EACL,cAAc,CAAC,IAAI;EACnB,MAAM;EACS;YAPjB,CASE,iBAAA,GAAA,kBAAA,KAACC,iBAAAA,aAAD;GAAa,QAAA;4BAAU,8BAA8B;GAAe,CAAA,EACpE,iBAAA,GAAA,kBAAA,MAAC,qBAAD,EAAA,UAAA,CACE,iBAAA,GAAA,kBAAA,KAACC,iBAAAA,aAAD,EAAA,UACE,iBAAA,GAAA,kBAAA,KAACC,iBAAAA,aAAD,EAAe,CAAA,EACH,CAAA,EACd,iBAAA,GAAA,kBAAA,KAACC,iBAAAA,aAAD;GAAa,OAAO;aAClB,iBAAA,GAAA,kBAAA,KAACC,iBAAAA,mBAAD,EAAqB,CAAA;GACT,CAAA,CACM,EAAA,CAAA,CACX"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2026-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ export declare const formatTime: (seconds: number) => string;
@@ -0,0 +1,17 @@
1
+ //#region src/AudioPlayer/audioUtils.ts
2
+ /**
3
+ * Copyright (c) 2026-present, NDLA.
4
+ *
5
+ * This source code is licensed under the GPLv3 license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ */
9
+ const formatTime = (seconds) => {
10
+ const minutes = Math.floor(seconds / 60);
11
+ const currentSeconds = seconds % 60;
12
+ return `${minutes}:${currentSeconds < 10 ? `0${currentSeconds}` : currentSeconds}`;
13
+ };
14
+ //#endregion
15
+ exports.formatTime = formatTime;
16
+
17
+ //# sourceMappingURL=audioUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioUtils.js","names":[],"sources":["../../src/AudioPlayer/audioUtils.ts"],"sourcesContent":["/**\n * Copyright (c) 2026-present, NDLA.\n *\n * This source code is licensed under the GPLv3 license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nexport const formatTime = (seconds: number) => {\n const minutes = Math.floor(seconds / 60);\n const currentSeconds = seconds % 60;\n\n const formattedSeconds = currentSeconds < 10 ? `0${currentSeconds}` : currentSeconds;\n return `${minutes}:${formattedSeconds}`;\n};\n"],"mappings":";;;;;;;;AAQA,MAAa,cAAc,YAAoB;CAC7C,MAAM,UAAU,KAAK,MAAM,UAAU,GAAG;CACxC,MAAM,iBAAiB,UAAU;AAGjC,QAAO,GAAG,QAAQ,GADO,iBAAiB,KAAK,IAAI,mBAAmB"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Copyright (c) 2026-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import type { SliderValueChangeDetails } from "@ark-ui/react";
9
+ import { type ReactEventHandler } from "react";
10
+ export declare const useAudioControls: () => {
11
+ togglePlay: () => void;
12
+ onPlaybackRateChange: (rate: number) => void;
13
+ onSeekSeconds: (seconds: number) => void;
14
+ handleVolumeSliderChange: (details: SliderValueChangeDetails) => void;
15
+ handleSliderChange: (details: SliderValueChangeDetails) => void;
16
+ onEnded: () => void;
17
+ onHandleTime: ReactEventHandler<HTMLAudioElement>;
18
+ speedValue: number;
19
+ volumeValue: number;
20
+ currentTime: number;
21
+ duration: number;
22
+ playing: boolean;
23
+ audioRef: import("react").RefObject<HTMLAudioElement | null>;
24
+ };
@@ -0,0 +1,56 @@
1
+ require("../_virtual/_rolldown/runtime.js");
2
+ let react = require("react");
3
+ //#region src/AudioPlayer/useAudioControls.ts
4
+ const useAudioControls = () => {
5
+ const [speedValue, setSpeedValue] = (0, react.useState)(1);
6
+ const [volumeValue, setVolumeValue] = (0, react.useState)(100);
7
+ const [currentTime, setCurrentTime] = (0, react.useState)(0);
8
+ const [duration, setDuration] = (0, react.useState)(0);
9
+ const [playing, setPlaying] = (0, react.useState)(false);
10
+ const audioRef = (0, react.useRef)(null);
11
+ const togglePlay = (0, react.useCallback)(() => {
12
+ if (!audioRef.current) return;
13
+ if (audioRef.current.paused) audioRef.current.play();
14
+ else audioRef.current.pause();
15
+ setPlaying((p) => !p);
16
+ }, []);
17
+ const onPlaybackRateChange = (0, react.useCallback)((rate) => {
18
+ setSpeedValue(rate);
19
+ if (audioRef.current) audioRef.current.playbackRate = rate;
20
+ }, []);
21
+ const onSeekSeconds = (0, react.useCallback)((seconds) => {
22
+ if (audioRef.current) audioRef.current.currentTime += seconds;
23
+ }, []);
24
+ const handleSliderChange = (0, react.useCallback)((details) => {
25
+ const newValue = details.value[0];
26
+ if (audioRef.current && newValue != null && !isNaN(newValue)) audioRef.current.currentTime = details.value[0];
27
+ }, []);
28
+ return {
29
+ togglePlay,
30
+ onPlaybackRateChange,
31
+ onSeekSeconds,
32
+ handleVolumeSliderChange: (0, react.useCallback)((details) => {
33
+ if (audioRef.current) {
34
+ audioRef.current.volume = details.value[0] / 100;
35
+ setVolumeValue(details.value[0]);
36
+ }
37
+ }, []),
38
+ handleSliderChange,
39
+ onEnded: (0, react.useCallback)(() => setPlaying(false), []),
40
+ onHandleTime: (0, react.useCallback)((meta) => {
41
+ const target = meta.currentTarget;
42
+ setCurrentTime(Math.round(target.currentTime));
43
+ setDuration(Math.round(target.duration));
44
+ }, []),
45
+ speedValue,
46
+ volumeValue,
47
+ currentTime,
48
+ duration,
49
+ playing,
50
+ audioRef
51
+ };
52
+ };
53
+ //#endregion
54
+ exports.useAudioControls = useAudioControls;
55
+
56
+ //# sourceMappingURL=useAudioControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAudioControls.js","names":[],"sources":["../../src/AudioPlayer/useAudioControls.ts"],"sourcesContent":["/**\n * Copyright (c) 2026-present, NDLA.\n *\n * This source code is licensed under the GPLv3 license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport type { SliderValueChangeDetails } from \"@ark-ui/react\";\nimport { useCallback, useRef, useState, type ReactEventHandler } from \"react\";\n\nexport const useAudioControls = () => {\n const [speedValue, setSpeedValue] = useState(1);\n const [volumeValue, setVolumeValue] = useState(100);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [playing, setPlaying] = useState(false);\n const audioRef = useRef<HTMLAudioElement>(null);\n\n const togglePlay = useCallback(() => {\n if (!audioRef.current) return;\n if (audioRef.current.paused) {\n audioRef.current.play();\n } else {\n audioRef.current.pause();\n }\n setPlaying((p) => !p);\n }, []);\n\n const onPlaybackRateChange = useCallback((rate: number) => {\n setSpeedValue(rate);\n if (audioRef.current) {\n audioRef.current.playbackRate = rate;\n }\n }, []);\n\n const onSeekSeconds = useCallback((seconds: number) => {\n if (audioRef.current) {\n audioRef.current.currentTime += seconds;\n }\n }, []);\n\n const handleSliderChange = useCallback((details: SliderValueChangeDetails) => {\n const newValue = details.value[0];\n if (audioRef.current && newValue != null && !isNaN(newValue)) {\n audioRef.current.currentTime = details.value[0];\n }\n }, []);\n\n const handleVolumeSliderChange = useCallback((details: SliderValueChangeDetails) => {\n if (audioRef.current) {\n audioRef.current.volume = details.value[0] / 100;\n setVolumeValue(details.value[0]);\n }\n }, []);\n\n const onEnded = useCallback(() => setPlaying(false), []);\n\n const onHandleTime: ReactEventHandler<HTMLAudioElement> = useCallback((meta) => {\n const target = meta.currentTarget;\n setCurrentTime(Math.round(target.currentTime));\n setDuration(Math.round(target.duration));\n }, []);\n\n return {\n togglePlay,\n onPlaybackRateChange,\n onSeekSeconds,\n handleVolumeSliderChange,\n handleSliderChange,\n onEnded,\n onHandleTime,\n speedValue,\n volumeValue,\n currentTime,\n duration,\n playing,\n audioRef,\n };\n};\n"],"mappings":";;;AAWA,MAAa,yBAAyB;CACpC,MAAM,CAAC,YAAY,kBAAA,GAAA,MAAA,UAA0B,EAAE;CAC/C,MAAM,CAAC,aAAa,mBAAA,GAAA,MAAA,UAA2B,IAAI;CACnD,MAAM,CAAC,aAAa,mBAAA,GAAA,MAAA,UAA2B,EAAE;CACjD,MAAM,CAAC,UAAU,gBAAA,GAAA,MAAA,UAAwB,EAAE;CAC3C,MAAM,CAAC,SAAS,eAAA,GAAA,MAAA,UAAuB,MAAM;CAC7C,MAAM,YAAA,GAAA,MAAA,QAAoC,KAAK;CAE/C,MAAM,cAAA,GAAA,MAAA,mBAA+B;AACnC,MAAI,CAAC,SAAS,QAAS;AACvB,MAAI,SAAS,QAAQ,OACnB,UAAS,QAAQ,MAAM;MAEvB,UAAS,QAAQ,OAAO;AAE1B,cAAY,MAAM,CAAC,EAAE;IACpB,EAAE,CAAC;CAEN,MAAM,wBAAA,GAAA,MAAA,cAAoC,SAAiB;AACzD,gBAAc,KAAK;AACnB,MAAI,SAAS,QACX,UAAS,QAAQ,eAAe;IAEjC,EAAE,CAAC;CAEN,MAAM,iBAAA,GAAA,MAAA,cAA6B,YAAoB;AACrD,MAAI,SAAS,QACX,UAAS,QAAQ,eAAe;IAEjC,EAAE,CAAC;CAEN,MAAM,sBAAA,GAAA,MAAA,cAAkC,YAAsC;EAC5E,MAAM,WAAW,QAAQ,MAAM;AAC/B,MAAI,SAAS,WAAW,YAAY,QAAQ,CAAC,MAAM,SAAS,CAC1D,UAAS,QAAQ,cAAc,QAAQ,MAAM;IAE9C,EAAE,CAAC;AAiBN,QAAO;EACL;EACA;EACA;EACA,2BAAA,GAAA,MAAA,cAnB4C,YAAsC;AAClF,OAAI,SAAS,SAAS;AACpB,aAAS,QAAQ,SAAS,QAAQ,MAAM,KAAK;AAC7C,mBAAe,QAAQ,MAAM,GAAG;;KAEjC,EAAE,CAAC;EAeJ;EACA,UAAA,GAAA,MAAA,mBAdgC,WAAW,MAAM,EAAE,EAAE,CAAC;EAetD,eAAA,GAAA,MAAA,cAbqE,SAAS;GAC9E,MAAM,SAAS,KAAK;AACpB,kBAAe,KAAK,MAAM,OAAO,YAAY,CAAC;AAC9C,eAAY,KAAK,MAAM,OAAO,SAAS,CAAC;KACvC,EAAE,CAAC;EAUJ;EACA;EACA;EACA;EACA;EACA;EACD"}
@@ -19,11 +19,7 @@ const AudioEmbed = ({ embed, lang }) => {
19
19
  const type = embed.embedData.type === "standard" ? "audio" : "podcast";
20
20
  if (embed.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_EmbedErrorPlaceholder.EmbedErrorPlaceholder, { type });
21
21
  const { data, embedData } = embed;
22
- if (embedData.type === "minimal") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AudioPlayer.AudioPlayer, {
23
- speech: true,
24
- src: data.audioFile.url,
25
- title: data.title.title
26
- });
22
+ const variant = embedData.type === "podcast" ? "standard" : embedData.type;
27
23
  const subtitle = data.series ? {
28
24
  title: data.series.title.title,
29
25
  url: `/podkast/${data.series.id}`
@@ -38,6 +34,7 @@ const AudioEmbed = ({ embed, lang }) => {
38
34
  "data-embed-type": type,
39
35
  ...require_licenseAttributes.licenseAttributes(data.copyright.license.license, lang, embedData.url),
40
36
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AudioPlayer.AudioPlayer, {
37
+ variant,
41
38
  description: data.podcastMeta?.introduction ?? "",
42
39
  img,
43
40
  src: data.audioFile.url,
@@ -1 +1 @@
1
- {"version":3,"file":"AudioEmbed.js","names":["Figure","EmbedErrorPlaceholder","AudioPlayer","licenseAttributes","EmbedByline"],"sources":["../../src/Embed/AudioEmbed.tsx"],"sourcesContent":["/**\n * Copyright (c) 2023-present, NDLA.\n *\n * This source code is licensed under the GPLv3 license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport { Figure } from \"@ndla/primitives\";\nimport { styled } from \"@ndla/styled-system/jsx\";\nimport type { AudioMetaData } from \"@ndla/types-embed\";\nimport { AudioPlayer } from \"../AudioPlayer/AudioPlayer\";\nimport { EmbedByline } from \"../LicenseByline/EmbedByline\";\nimport { licenseAttributes } from \"../utils/licenseAttributes\";\nimport { EmbedErrorPlaceholder } from \"./EmbedErrorPlaceholder\";\nimport type { Author } from \"./ImageEmbed\";\n\nconst StyledFigure = styled(Figure, {\n base: {\n clear: \"both\",\n },\n});\n\ninterface Props {\n embed: AudioMetaData;\n lang?: string;\n}\n\nexport const getFirstNonEmptyLicenseCredits = (authors: {\n creators: Author[];\n rightsholders: Author[];\n processors: Author[];\n}) => Object.values(authors).find((i) => i.length > 0) ?? [];\n\nexport const AudioEmbed = ({ embed, lang }: Props) => {\n const type = embed.embedData.type === \"standard\" ? \"audio\" : \"podcast\";\n if (embed.status === \"error\") {\n return <EmbedErrorPlaceholder type={type} />;\n }\n\n const { data, embedData } = embed;\n\n if (embedData.type === \"minimal\") {\n return <AudioPlayer speech src={data.audioFile.url} title={data.title.title} />;\n }\n\n const subtitle = data.series ? { title: data.series.title.title, url: `/podkast/${data.series.id}` } : undefined;\n\n const coverPhoto = data.podcastMeta?.coverPhoto;\n\n const img = coverPhoto && { url: coverPhoto.url, alt: coverPhoto.altText };\n\n const licenseProps = licenseAttributes(data.copyright.license.license, lang, embedData.url);\n\n return (\n <StyledFigure lang={lang} data-embed-type={type} {...licenseProps}>\n <AudioPlayer\n description={data.podcastMeta?.introduction ?? \"\"}\n img={img}\n src={data.audioFile.url}\n textVersion={\n data.manuscript?.manuscript.length ? (\n <div dangerouslySetInnerHTML={{ __html: data.manuscript.manuscript }} />\n ) : undefined\n }\n title={data.title.title}\n subtitle={subtitle}\n />\n <EmbedByline\n error={false}\n type={data.audioType === \"standard\" ? \"audio\" : \"podcast\"}\n copyright={embed.data.copyright}\n />\n </StyledFigure>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAM,gBAAA,GAAA,wBAAA,QAAsBA,iBAAAA,QAAQ,EAClC,MAAM,EACJ,OAAO,QACR,EACF,CAAC;AAaF,MAAa,cAAc,EAAE,OAAO,WAAkB;CACpD,MAAM,OAAO,MAAM,UAAU,SAAS,aAAa,UAAU;AAC7D,KAAI,MAAM,WAAW,QACnB,QAAO,iBAAA,GAAA,kBAAA,KAACC,8BAAAA,uBAAD,EAA6B,MAAQ,CAAA;CAG9C,MAAM,EAAE,MAAM,cAAc;AAE5B,KAAI,UAAU,SAAS,UACrB,QAAO,iBAAA,GAAA,kBAAA,KAACC,oBAAAA,aAAD;EAAa,QAAA;EAAO,KAAK,KAAK,UAAU;EAAK,OAAO,KAAK,MAAM;EAAS,CAAA;CAGjF,MAAM,WAAW,KAAK,SAAS;EAAE,OAAO,KAAK,OAAO,MAAM;EAAO,KAAK,YAAY,KAAK,OAAO;EAAM,GAAG,KAAA;CAEvG,MAAM,aAAa,KAAK,aAAa;CAErC,MAAM,MAAM,cAAc;EAAE,KAAK,WAAW;EAAK,KAAK,WAAW;EAAS;AAI1E,QACE,iBAAA,GAAA,kBAAA,MAAC,cAAD;EAAoB;EAAM,mBAAiB;EAAM,GAH9BC,0BAAAA,kBAAkB,KAAK,UAAU,QAAQ,SAAS,MAAM,UAAU,IAAI;YAGzF,CACE,iBAAA,GAAA,kBAAA,KAACD,oBAAAA,aAAD;GACE,aAAa,KAAK,aAAa,gBAAgB;GAC1C;GACL,KAAK,KAAK,UAAU;GACpB,aACE,KAAK,YAAY,WAAW,SAC1B,iBAAA,GAAA,kBAAA,KAAC,OAAD,EAAK,yBAAyB,EAAE,QAAQ,KAAK,WAAW,YAAY,EAAI,CAAA,GACtE,KAAA;GAEN,OAAO,KAAK,MAAM;GACR;GACV,CAAA,EACF,iBAAA,GAAA,kBAAA,KAACE,oBAAAA,aAAD;GACE,OAAO;GACP,MAAM,KAAK,cAAc,aAAa,UAAU;GAChD,WAAW,MAAM,KAAK;GACtB,CAAA,CACW"}
1
+ {"version":3,"file":"AudioEmbed.js","names":["Figure","EmbedErrorPlaceholder","licenseAttributes","AudioPlayer","EmbedByline"],"sources":["../../src/Embed/AudioEmbed.tsx"],"sourcesContent":["/**\n * Copyright (c) 2023-present, NDLA.\n *\n * This source code is licensed under the GPLv3 license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport { Figure } from \"@ndla/primitives\";\nimport { styled } from \"@ndla/styled-system/jsx\";\nimport type { AudioMetaData } from \"@ndla/types-embed\";\nimport { AudioPlayer, type AudioPlayerVariant } from \"../AudioPlayer/AudioPlayer\";\nimport { EmbedByline } from \"../LicenseByline/EmbedByline\";\nimport { licenseAttributes } from \"../utils/licenseAttributes\";\nimport { EmbedErrorPlaceholder } from \"./EmbedErrorPlaceholder\";\nimport type { Author } from \"./ImageEmbed\";\n\nconst StyledFigure = styled(Figure, {\n base: {\n clear: \"both\",\n },\n});\n\ninterface Props {\n embed: AudioMetaData;\n lang?: string;\n}\n\nexport const getFirstNonEmptyLicenseCredits = (authors: {\n creators: Author[];\n rightsholders: Author[];\n processors: Author[];\n}) => Object.values(authors).find((i) => i.length > 0) ?? [];\n\nexport const AudioEmbed = ({ embed, lang }: Props) => {\n const type = embed.embedData.type === \"standard\" ? \"audio\" : \"podcast\";\n if (embed.status === \"error\") {\n return <EmbedErrorPlaceholder type={type} />;\n }\n\n const { data, embedData } = embed;\n\n const variant = embedData.type === \"podcast\" ? \"standard\" : (embedData.type as AudioPlayerVariant);\n\n const subtitle = data.series ? { title: data.series.title.title, url: `/podkast/${data.series.id}` } : undefined;\n\n const coverPhoto = data.podcastMeta?.coverPhoto;\n\n const img = coverPhoto && { url: coverPhoto.url, alt: coverPhoto.altText };\n\n const licenseProps = licenseAttributes(data.copyright.license.license, lang, embedData.url);\n\n return (\n <StyledFigure lang={lang} data-embed-type={type} {...licenseProps}>\n <AudioPlayer\n variant={variant}\n description={data.podcastMeta?.introduction ?? \"\"}\n img={img}\n src={data.audioFile.url}\n textVersion={\n data.manuscript?.manuscript.length ? (\n <div dangerouslySetInnerHTML={{ __html: data.manuscript.manuscript }} />\n ) : undefined\n }\n title={data.title.title}\n subtitle={subtitle}\n />\n <EmbedByline\n error={false}\n type={data.audioType === \"standard\" ? \"audio\" : \"podcast\"}\n copyright={embed.data.copyright}\n />\n </StyledFigure>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAM,gBAAA,GAAA,wBAAA,QAAsBA,iBAAAA,QAAQ,EAClC,MAAM,EACJ,OAAO,QACR,EACF,CAAC;AAaF,MAAa,cAAc,EAAE,OAAO,WAAkB;CACpD,MAAM,OAAO,MAAM,UAAU,SAAS,aAAa,UAAU;AAC7D,KAAI,MAAM,WAAW,QACnB,QAAO,iBAAA,GAAA,kBAAA,KAACC,8BAAAA,uBAAD,EAA6B,MAAQ,CAAA;CAG9C,MAAM,EAAE,MAAM,cAAc;CAE5B,MAAM,UAAU,UAAU,SAAS,YAAY,aAAc,UAAU;CAEvE,MAAM,WAAW,KAAK,SAAS;EAAE,OAAO,KAAK,OAAO,MAAM;EAAO,KAAK,YAAY,KAAK,OAAO;EAAM,GAAG,KAAA;CAEvG,MAAM,aAAa,KAAK,aAAa;CAErC,MAAM,MAAM,cAAc;EAAE,KAAK,WAAW;EAAK,KAAK,WAAW;EAAS;AAI1E,QACE,iBAAA,GAAA,kBAAA,MAAC,cAAD;EAAoB;EAAM,mBAAiB;EAAM,GAH9BC,0BAAAA,kBAAkB,KAAK,UAAU,QAAQ,SAAS,MAAM,UAAU,IAAI;YAGzF,CACE,iBAAA,GAAA,kBAAA,KAACC,oBAAAA,aAAD;GACW;GACT,aAAa,KAAK,aAAa,gBAAgB;GAC1C;GACL,KAAK,KAAK,UAAU;GACpB,aACE,KAAK,YAAY,WAAW,SAC1B,iBAAA,GAAA,kBAAA,KAAC,OAAD,EAAK,yBAAyB,EAAE,QAAQ,KAAK,WAAW,YAAY,EAAI,CAAA,GACtE,KAAA;GAEN,OAAO,KAAK,MAAM;GACR;GACV,CAAA,EACF,iBAAA,GAAA,kBAAA,KAACC,oBAAAA,aAAD;GACE,OAAO;GACP,MAAM,KAAK,cAAc,aAAa,UAAU;GAChD,WAAW,MAAM,KAAK;GACtB,CAAA,CACW"}
@@ -112,8 +112,7 @@ const Gloss = ({ title, glossData, audio, exampleIds, exampleLangs, variant }) =
112
112
  })
113
113
  ] }), !!audio?.src && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_SpeechControl.SpeechControl, {
114
114
  src: audio.src,
115
- title: audio.title,
116
- type: "gloss"
115
+ title: audio.title
117
116
  })] }),
118
117
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StyledContainer, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ndla_primitives.Text, {
119
118
  textStyle: "label.medium",
@@ -1 +1 @@
1
- {"version":3,"file":"Gloss.js","names":["AccordionItemContent","AccordionItem","AccordionRoot","Text","SpeechControl","AccordionItemTrigger","IconButton","AccordionItemIndicator","ArrowDownShortLine","GlossExample"],"sources":["../../src/Gloss/Gloss.tsx"],"sourcesContent":["/**\n * Copyright (c) 2023-present, NDLA.\n *\n * This source code is licensed under the GPLv3 license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport { AccordionItemTrigger } from \"@ark-ui/react\";\nimport { ArrowDownShortLine } from \"@ndla/icons\";\nimport {\n AccordionItem,\n AccordionItemContent,\n AccordionItemIndicator,\n AccordionRoot,\n IconButton,\n Text,\n} from \"@ndla/primitives\";\nimport { styled } from \"@ndla/styled-system/jsx\";\nimport type { StyledVariantProps } from \"@ndla/styled-system/types\";\nimport type { ConceptTitleDTO, GlossDataDTO, GlossExampleDTO } from \"@ndla/types-backend/concept-api\";\nimport parse from \"html-react-parser\";\nimport { useMemo } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { SpeechControl } from \"../AudioPlayer/SpeechControl\";\nimport { GlossExample } from \"./GlossExample\";\n\n// TODO: Figure out padding between bordered and simple variant.\n// The design says that the content above the accordion content should have enough padding to align with the accordion content.\n// When a gloss is bordered there's way too much padding.\n\nconst getFilteredExamples = (\n glossData: GlossDataDTO | undefined,\n exampleIds: string | undefined,\n exampleLangs: string | undefined,\n): GlossExampleDTO[][] => {\n if (exampleIds !== undefined || exampleLangs !== undefined) {\n const exampleIdsList = exampleIds?.toString()?.split(\",\") ?? [];\n const exampleLangsList = exampleLangs?.split(\",\") ?? [];\n\n const filteredExamples =\n glossData?.examples?.map((examples, i) => {\n if (exampleIdsList.includes(i.toString())) {\n return examples.filter((e) => exampleLangsList.includes(e.language));\n }\n return [];\n }) ?? [];\n const examplesWithoutEmpty = filteredExamples.filter((el) => !!el.length);\n return examplesWithoutEmpty;\n }\n return glossData?.examples ?? [];\n};\n\nconst Container = styled(\"div\", {\n base: {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n },\n});\n\nconst TextWrapper = styled(\"div\", {\n base: {\n display: \"flex\",\n gap: \"small\",\n },\n});\n\nconst StyledAccordionItemContent = styled(AccordionItemContent, {\n base: {\n paddingInline: \"0\",\n },\n});\n\nconst StyledContainer = styled(Container, {\n base: {\n marginBlockStart: \"3xsmall\",\n },\n});\n\nconst StyledAccordionItem = styled(AccordionItem, {\n base: {\n paddingBlock: \"small\",\n paddingInline: \"medium\",\n },\n defaultVariants: {\n variant: \"simple\",\n },\n variants: {\n variant: {\n simple: {},\n bordered: {\n border: \"1px solid\",\n borderColor: \"stroke.subtle\",\n borderRadius: \"xsmall\",\n },\n },\n },\n});\n\ntype GlossVariantProps = StyledVariantProps<typeof StyledAccordionItem>;\n\nexport interface Props {\n title: ConceptTitleDTO;\n glossData?: GlossDataDTO;\n audio?: {\n title: string;\n src?: string;\n };\n exampleIds?: string;\n exampleLangs?: string;\n}\n\nexport const Gloss = ({ title, glossData, audio, exampleIds, exampleLangs, variant }: Props & GlossVariantProps) => {\n const { t } = useTranslation();\n\n const parsedTitle = useMemo(() => parse(title.htmlTitle), [title.htmlTitle]);\n\n const filteredExamples = useMemo(\n () => getFilteredExamples(glossData, exampleIds, exampleLangs),\n [exampleIds, exampleLangs, glossData],\n );\n\n if (!glossData) return null;\n\n return (\n <AccordionRoot multiple variant=\"clean\">\n <StyledAccordionItem value=\"gloss\" variant={variant}>\n <Container>\n <TextWrapper>\n <Text textStyle=\"label.medium\" fontWeight=\"bold\" asChild consumeCss lang={glossData.originalLanguage}>\n <span>{glossData.gloss}</span>\n </Text>\n {!!glossData.transcriptions.traditional && (\n <Text textStyle=\"label.medium\" asChild consumeCss>\n <span\n key={t(\"gloss.transcriptions.traditional\")}\n aria-label={t(\"gloss.transcriptions.traditional\")}\n lang={glossData.originalLanguage}\n >\n {glossData.transcriptions.traditional}\n </span>\n </Text>\n )}\n {!!glossData.transcriptions.pinyin && (\n <Text textStyle=\"label.medium\" asChild consumeCss>\n <span\n data-pinyin=\"\"\n key={t(\"gloss.transcriptions.pinyin\")}\n aria-label={t(\"gloss.transcriptions.pinyin\")}\n lang={glossData.originalLanguage}\n >\n {glossData.transcriptions.pinyin}\n </span>\n </Text>\n )}\n {!!glossData.wordClass && (\n <Text textStyle=\"label.medium\" asChild consumeCss>\n <span aria-label={t(\"gloss.wordClass\")}>\n {glossData.wordClass.map((wc) => t(`wordClass.${wc}`).toLowerCase()).join(\" / \")}\n </span>\n </Text>\n )}\n </TextWrapper>\n {!!audio?.src && <SpeechControl src={audio.src} title={audio.title} type=\"gloss\" />}\n </Container>\n <StyledContainer>\n <Text textStyle=\"label.medium\" asChild consumeCss>\n <span lang={title.language}>{parsedTitle}</span>\n </Text>\n {!!filteredExamples.length && (\n <AccordionItemTrigger asChild>\n <IconButton variant=\"tertiary\" aria-label={t(\"gloss.showExamples\")} title={t(\"gloss.showExamples\")}>\n <AccordionItemIndicator asChild>\n <ArrowDownShortLine size=\"medium\" />\n </AccordionItemIndicator>\n </IconButton>\n </AccordionItemTrigger>\n )}\n </StyledContainer>\n <StyledAccordionItemContent>\n {filteredExamples.map((examples, index) => (\n <GlossExample\n key={`gloss-example-${index}`}\n examples={examples}\n originalLanguage={glossData.originalLanguage}\n />\n ))}\n </StyledAccordionItemContent>\n </StyledAccordionItem>\n </AccordionRoot>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Gloss.js","names":["AccordionItemContent","AccordionItem","AccordionRoot","Text","SpeechControl","AccordionItemTrigger","IconButton","AccordionItemIndicator","ArrowDownShortLine","GlossExample"],"sources":["../../src/Gloss/Gloss.tsx"],"sourcesContent":["/**\n * Copyright (c) 2023-present, NDLA.\n *\n * This source code is licensed under the GPLv3 license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport { AccordionItemTrigger } from \"@ark-ui/react\";\nimport { ArrowDownShortLine } from \"@ndla/icons\";\nimport {\n AccordionItem,\n AccordionItemContent,\n AccordionItemIndicator,\n AccordionRoot,\n IconButton,\n Text,\n} from \"@ndla/primitives\";\nimport { styled } from \"@ndla/styled-system/jsx\";\nimport type { StyledVariantProps } from \"@ndla/styled-system/types\";\nimport type { ConceptTitleDTO, GlossDataDTO, GlossExampleDTO } from \"@ndla/types-backend/concept-api\";\nimport parse from \"html-react-parser\";\nimport { useMemo } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { SpeechControl } from \"../AudioPlayer/SpeechControl\";\nimport { GlossExample } from \"./GlossExample\";\n\n// TODO: Figure out padding between bordered and simple variant.\n// The design says that the content above the accordion content should have enough padding to align with the accordion content.\n// When a gloss is bordered there's way too much padding.\n\nconst getFilteredExamples = (\n glossData: GlossDataDTO | undefined,\n exampleIds: string | undefined,\n exampleLangs: string | undefined,\n): GlossExampleDTO[][] => {\n if (exampleIds !== undefined || exampleLangs !== undefined) {\n const exampleIdsList = exampleIds?.toString()?.split(\",\") ?? [];\n const exampleLangsList = exampleLangs?.split(\",\") ?? [];\n\n const filteredExamples =\n glossData?.examples?.map((examples, i) => {\n if (exampleIdsList.includes(i.toString())) {\n return examples.filter((e) => exampleLangsList.includes(e.language));\n }\n return [];\n }) ?? [];\n const examplesWithoutEmpty = filteredExamples.filter((el) => !!el.length);\n return examplesWithoutEmpty;\n }\n return glossData?.examples ?? [];\n};\n\nconst Container = styled(\"div\", {\n base: {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n },\n});\n\nconst TextWrapper = styled(\"div\", {\n base: {\n display: \"flex\",\n gap: \"small\",\n },\n});\n\nconst StyledAccordionItemContent = styled(AccordionItemContent, {\n base: {\n paddingInline: \"0\",\n },\n});\n\nconst StyledContainer = styled(Container, {\n base: {\n marginBlockStart: \"3xsmall\",\n },\n});\n\nconst StyledAccordionItem = styled(AccordionItem, {\n base: {\n paddingBlock: \"small\",\n paddingInline: \"medium\",\n },\n defaultVariants: {\n variant: \"simple\",\n },\n variants: {\n variant: {\n simple: {},\n bordered: {\n border: \"1px solid\",\n borderColor: \"stroke.subtle\",\n borderRadius: \"xsmall\",\n },\n },\n },\n});\n\ntype GlossVariantProps = StyledVariantProps<typeof StyledAccordionItem>;\n\nexport interface Props {\n title: ConceptTitleDTO;\n glossData?: GlossDataDTO;\n audio?: {\n title: string;\n src?: string;\n };\n exampleIds?: string;\n exampleLangs?: string;\n}\n\nexport const Gloss = ({ title, glossData, audio, exampleIds, exampleLangs, variant }: Props & GlossVariantProps) => {\n const { t } = useTranslation();\n\n const parsedTitle = useMemo(() => parse(title.htmlTitle), [title.htmlTitle]);\n\n const filteredExamples = useMemo(\n () => getFilteredExamples(glossData, exampleIds, exampleLangs),\n [exampleIds, exampleLangs, glossData],\n );\n\n if (!glossData) return null;\n\n return (\n <AccordionRoot multiple variant=\"clean\">\n <StyledAccordionItem value=\"gloss\" variant={variant}>\n <Container>\n <TextWrapper>\n <Text textStyle=\"label.medium\" fontWeight=\"bold\" asChild consumeCss lang={glossData.originalLanguage}>\n <span>{glossData.gloss}</span>\n </Text>\n {!!glossData.transcriptions.traditional && (\n <Text textStyle=\"label.medium\" asChild consumeCss>\n <span\n key={t(\"gloss.transcriptions.traditional\")}\n aria-label={t(\"gloss.transcriptions.traditional\")}\n lang={glossData.originalLanguage}\n >\n {glossData.transcriptions.traditional}\n </span>\n </Text>\n )}\n {!!glossData.transcriptions.pinyin && (\n <Text textStyle=\"label.medium\" asChild consumeCss>\n <span\n data-pinyin=\"\"\n key={t(\"gloss.transcriptions.pinyin\")}\n aria-label={t(\"gloss.transcriptions.pinyin\")}\n lang={glossData.originalLanguage}\n >\n {glossData.transcriptions.pinyin}\n </span>\n </Text>\n )}\n {!!glossData.wordClass && (\n <Text textStyle=\"label.medium\" asChild consumeCss>\n <span aria-label={t(\"gloss.wordClass\")}>\n {glossData.wordClass.map((wc) => t(`wordClass.${wc}`).toLowerCase()).join(\" / \")}\n </span>\n </Text>\n )}\n </TextWrapper>\n {!!audio?.src && <SpeechControl src={audio.src} title={audio.title} />}\n </Container>\n <StyledContainer>\n <Text textStyle=\"label.medium\" asChild consumeCss>\n <span lang={title.language}>{parsedTitle}</span>\n </Text>\n {!!filteredExamples.length && (\n <AccordionItemTrigger asChild>\n <IconButton variant=\"tertiary\" aria-label={t(\"gloss.showExamples\")} title={t(\"gloss.showExamples\")}>\n <AccordionItemIndicator asChild>\n <ArrowDownShortLine size=\"medium\" />\n </AccordionItemIndicator>\n </IconButton>\n </AccordionItemTrigger>\n )}\n </StyledContainer>\n <StyledAccordionItemContent>\n {filteredExamples.map((examples, index) => (\n <GlossExample\n key={`gloss-example-${index}`}\n examples={examples}\n originalLanguage={glossData.originalLanguage}\n />\n ))}\n </StyledAccordionItemContent>\n </StyledAccordionItem>\n </AccordionRoot>\n );\n};\n"],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -39,6 +39,8 @@ export { PdfFile } from "./FileList/PdfFile";
39
39
  export { FactBox } from "./FactBox/FactBox";
40
40
  export { ResourceBox } from "./ResourceBox/ResourceBox";
41
41
  export { AudioPlayer } from "./AudioPlayer/AudioPlayer";
42
+ export type { AudioPlayerVariant } from "./AudioPlayer/AudioPlayer";
43
+ export { CompactAudioPlayer } from "./AudioPlayer/CompactAudioPlayer";
42
44
  export { constants } from "./model";
43
45
  export { contentTypes, contentTypeMapping, resourceEmbedTypeMapping } from "./model/ContentType";
44
46
  export { subjectTypes } from "./model/SubjectTypes";
package/lib/index.js CHANGED
@@ -11,6 +11,7 @@ const require_IframeEmbed = require("./Embed/IframeEmbed.js");
11
11
  const require_ImageEmbed = require("./Embed/ImageEmbed.js");
12
12
  const require_Concept = require("./Concept/Concept.js");
13
13
  const require_InlineTriggerButton = require("./Embed/InlineTriggerButton.js");
14
+ const require_CompactAudioPlayer = require("./AudioPlayer/CompactAudioPlayer.js");
14
15
  const require_AudioPlayer = require("./AudioPlayer/AudioPlayer.js");
15
16
  const require_AudioEmbed = require("./Embed/AudioEmbed.js");
16
17
  const require_FootnoteEmbed = require("./Embed/FootnoteEmbed.js");
@@ -75,6 +76,7 @@ exports.BrightcoveEmbed = require_BrightcoveEmbed.BrightcoveEmbed;
75
76
  exports.CampaignBlock = require_CampaignBlock.CampaignBlock;
76
77
  exports.CodeBlock = require_CodeBlock.CodeBlock;
77
78
  exports.CodeEmbed = require_CodeEmbed.CodeEmbed;
79
+ exports.CompactAudioPlayer = require_CompactAudioPlayer.CompactAudioPlayer;
78
80
  exports.Concept = require_Concept.Concept;
79
81
  exports.ConceptEmbed = require_ConceptEmbed.ConceptEmbed;
80
82
  exports.ConceptInlineTriggerButton = require_ConceptInlineTriggerButton.ConceptInlineTriggerButton;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ndla/ui",
3
3
  "type": "module",
4
- "version": "56.0.186-alpha.0",
4
+ "version": "56.0.187-alpha.0",
5
5
  "description": "UI component library for NDLA",
6
6
  "license": "GPL-3.0",
7
7
  "exports": {
@@ -63,5 +63,5 @@
63
63
  "publishConfig": {
64
64
  "access": "public"
65
65
  },
66
- "gitHead": "f8a73392a71c05f052f6cc69d55ca57c38e0db5b"
66
+ "gitHead": "6e0fe3687125dd1a02010543128cf67ad50dc986"
67
67
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copyright (c) 2026-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type { ComponentProps } from "react";
10
+
11
+ interface Props extends ComponentProps<"audio"> {
12
+ src: string;
13
+ title: string;
14
+ }
15
+
16
+ export const AudioElement = (props: Props) => {
17
+ // TODO: We should tie this up to the textual description somehow
18
+ // oxlint-disable-next-line jsx-a11y/media-has-caption
19
+ return <audio preload="metadata" {...props} />;
20
+ };
@@ -52,12 +52,21 @@ export const AudioPlayerStory: StoryObj<typeof AudioPlayer> = {
52
52
 
53
53
  AudioPlayerStory.storyName = "AudioPlayer";
54
54
 
55
+ export const SpeechVariant: StoryObj<typeof AudioPlayer> = {
56
+ args: {
57
+ src: "https://api.staging.ndla.no/audio/files/Alltid_Nyheter_nrk128kps.mp3",
58
+ title: "Den gode lydhistoria",
59
+ textVersion: TextVersion,
60
+ variant: "minimal",
61
+ },
62
+ };
63
+
55
64
  export const SimpleVariant: StoryObj<typeof AudioPlayer> = {
56
65
  args: {
57
66
  src: "https://api.staging.ndla.no/audio/files/Alltid_Nyheter_nrk128kps.mp3",
58
67
  title: "Den gode lydhistoria",
59
68
  textVersion: TextVersion,
60
- speech: true,
69
+ variant: "compact",
61
70
  },
62
71
  };
63
72
 
@@ -11,6 +11,7 @@ import { SafeLink } from "@ndla/safelink";
11
11
  import { styled } from "@ndla/styled-system/jsx";
12
12
  import { type ReactNode, useId, useMemo, useState } from "react";
13
13
  import { useTranslation } from "react-i18next";
14
+ import { CompactAudioPlayer } from "./CompactAudioPlayer";
14
15
  import { Controls } from "./Controls";
15
16
  import { SpeechControl } from "./SpeechControl";
16
17
 
@@ -140,33 +141,39 @@ const ShowMoreButton = styled(Button, {
140
141
 
141
142
  const DESCRIPTION_MAX_LENGTH = 200;
142
143
 
143
- type Props = {
144
+ export type AudioPlayerVariant = "standard" | "minimal" | "compact";
145
+
146
+ interface Props {
144
147
  src: string;
145
148
  title: string;
146
149
  subtitle?: {
147
150
  title: string;
148
151
  url?: string;
149
152
  };
150
- speech?: boolean;
153
+ variant?: AudioPlayerVariant;
151
154
  description?: string;
152
155
  textVersion?: ReactNode;
153
156
  img?: {
154
157
  url: string;
155
158
  alt: string;
156
159
  };
157
- };
160
+ }
158
161
 
159
- export const AudioPlayer = ({ src, title, subtitle, speech, description, img, textVersion }: Props) => {
162
+ export const AudioPlayer = ({ src, title, subtitle, variant = "standard", description, img, textVersion }: Props) => {
160
163
  const { t } = useTranslation();
161
164
  const [showTextVersion, setShowTextVersion] = useState(false);
162
165
  const [showFullDescription, setShowFullDescription] = useState(false);
163
166
  const truncatedDescription = useMemo(() => description?.slice(0, DESCRIPTION_MAX_LENGTH), [description]);
164
167
  const textDescriptionId = useId();
165
168
 
166
- if (speech) {
169
+ if (variant === "minimal") {
167
170
  return <SpeechControl src={src} title={title} />;
168
171
  }
169
172
 
173
+ if (variant === "compact") {
174
+ return <CompactAudioPlayer src={src} title={title} />;
175
+ }
176
+
170
177
  const toggleTextVersion = () => {
171
178
  setShowTextVersion((curr) => !curr);
172
179
  };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Copyright (c) 2026-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type { SliderValueChangeDetails } from "@ark-ui/react";
10
+ import {
11
+ SliderRoot,
12
+ SliderLabel,
13
+ SliderControl,
14
+ SliderTrack,
15
+ SliderRange,
16
+ SliderThumb,
17
+ SliderHiddenInput,
18
+ } from "@ndla/primitives";
19
+ import { styled } from "@ndla/styled-system/jsx";
20
+ import { useTranslation } from "react-i18next";
21
+ import { formatTime } from "./audioUtils";
22
+
23
+ interface Props {
24
+ currentTime: number;
25
+ duration: number;
26
+ onValueChange: (details: SliderValueChangeDetails) => void;
27
+ variant?: "simple" | "standard";
28
+ }
29
+
30
+ const StyledSliderThumb = styled(SliderThumb, {
31
+ variants: {
32
+ variant: {
33
+ standard: {},
34
+ simple: {
35
+ borderRadius: "0",
36
+ width: "4xsmall",
37
+ height: "4xsmall",
38
+ },
39
+ },
40
+ },
41
+ });
42
+
43
+ const StyledSliderTrack = styled(SliderTrack, {
44
+ variants: {
45
+ variant: {
46
+ standard: {},
47
+ simple: {
48
+ background: "unset",
49
+ },
50
+ },
51
+ },
52
+ });
53
+
54
+ const StyledSliderControl = styled(SliderControl, {
55
+ variants: {
56
+ variant: {
57
+ standard: {},
58
+ simple: {
59
+ height: "unset",
60
+ },
61
+ },
62
+ },
63
+ });
64
+
65
+ export const AudioProgress = ({ currentTime, duration, onValueChange, variant }: Props) => {
66
+ const { t } = useTranslation();
67
+ return (
68
+ <SliderRoot
69
+ value={[currentTime]}
70
+ defaultValue={[0]}
71
+ step={1}
72
+ max={duration}
73
+ onValueChange={onValueChange}
74
+ getAriaValueText={(value) =>
75
+ t("audio.valueText", {
76
+ start: formatTime(Math.round(value.value)),
77
+ end: formatTime(Math.round(duration)),
78
+ })
79
+ }
80
+ >
81
+ <SliderLabel srOnly>{t("audio.progressBar")}</SliderLabel>
82
+ <StyledSliderControl variant={variant}>
83
+ <StyledSliderTrack variant={variant}>
84
+ <SliderRange />
85
+ </StyledSliderTrack>
86
+ <StyledSliderThumb index={0} variant={variant}>
87
+ <SliderHiddenInput />
88
+ </StyledSliderThumb>
89
+ </StyledSliderControl>
90
+ </SliderRoot>
91
+ );
92
+ };
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Copyright (c) 2026-present, NDLA.
3
+ *
4
+ * This source code is licensed under the GPLv3 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import { VolumeUpFill } from "@ndla/icons";
10
+ import { PopoverRoot, PopoverTrigger, IconButton, PopoverContent, Text, PopoverTitle } from "@ndla/primitives";
11
+ import { styled } from "@ndla/styled-system/jsx";
12
+ import { useTranslation } from "react-i18next";
13
+ import { AudioElement } from "./AudioElement";
14
+ import { AudioProgress } from "./AudioProgress";
15
+ import { formatTime } from "./audioUtils";
16
+ import { PlayButton } from "./PlayButton";
17
+ import { useAudioControls } from "./useAudioControls";
18
+ import { VolumeSlider } from "./VolumeSlider";
19
+
20
+ const AudioContainer = styled("div", {
21
+ base: {
22
+ display: "flex",
23
+ gap: "xxsmall",
24
+ flexDirection: "column",
25
+ padding: "xsmall",
26
+ paddingBlockEnd: "0",
27
+ borderRadius: "xsmall",
28
+ boxShadow: "xsmall",
29
+ background: "surface.brand.1.subtle",
30
+ },
31
+ });
32
+
33
+ const ControlsContainer = styled("div", {
34
+ base: {
35
+ display: "flex",
36
+ gap: "xsmall",
37
+ alignItems: "center",
38
+ },
39
+ });
40
+
41
+ interface Props {
42
+ src: string;
43
+ title: string;
44
+ }
45
+
46
+ const StyledText = styled(Text, {
47
+ base: {
48
+ minWidth: "4xlarge",
49
+ flexShrink: "0",
50
+ textAlign: "center",
51
+ },
52
+ });
53
+
54
+ const StyledIconButton = styled(IconButton, {
55
+ base: {
56
+ marginInlineStart: "auto",
57
+ },
58
+ });
59
+
60
+ const EllipsedText = styled(Text, {
61
+ base: {
62
+ overflow: "hidden",
63
+ textOverflow: "ellipsis",
64
+ whiteSpace: "nowrap",
65
+ },
66
+ });
67
+
68
+ export const CompactAudioPlayer = ({ src, title }: Props) => {
69
+ const { t } = useTranslation();
70
+ const {
71
+ audioRef,
72
+ playing,
73
+ togglePlay,
74
+ currentTime,
75
+ duration,
76
+ handleSliderChange,
77
+ volumeValue,
78
+ handleVolumeSliderChange,
79
+ onEnded,
80
+ onHandleTime,
81
+ } = useAudioControls();
82
+ return (
83
+ <AudioContainer>
84
+ <AudioElement
85
+ ref={audioRef}
86
+ src={src}
87
+ title={title}
88
+ onEnded={onEnded}
89
+ onLoadedMetadata={onHandleTime}
90
+ onTimeUpdate={onHandleTime}
91
+ />
92
+ <ControlsContainer>
93
+ <PlayButton playing={playing} onClick={togglePlay} />
94
+ <StyledText>
95
+ <Text textStyle="label.medium" asChild consumeCss>
96
+ <span>{formatTime(currentTime)}</span>
97
+ </Text>
98
+ {"/ "}
99
+ <Text textStyle="label.medium" color="text.subtle" asChild consumeCss>
100
+ <span>{formatTime(duration)}</span>
101
+ </Text>
102
+ </StyledText>
103
+ <EllipsedText textStyle="title.medium">{title}</EllipsedText>
104
+ <PopoverRoot positioning={{ placement: "top" }}>
105
+ <PopoverTrigger asChild>
106
+ <StyledIconButton variant="tertiary">
107
+ <VolumeUpFill />
108
+ </StyledIconButton>
109
+ </PopoverTrigger>
110
+ <PopoverContent>
111
+ <PopoverTitle srOnly>{t("audio.controls.adjustVolume")}</PopoverTitle>
112
+ <VolumeSlider value={volumeValue} onValueChange={handleVolumeSliderChange} />
113
+ </PopoverContent>
114
+ </PopoverRoot>
115
+ </ControlsContainer>
116
+ <AudioProgress
117
+ currentTime={currentTime}
118
+ duration={duration}
119
+ onValueChange={handleSliderChange}
120
+ variant="simple"
121
+ />
122
+ </AudioContainer>
123
+ );
124
+ };