@ndla/ui 56.0.186-alpha.0 → 56.0.188-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.
- package/dist/panda.buildinfo.json +15 -5
- package/dist/styles.css +53 -13
- package/es/AudioPlayer/AudioElement.mjs +12 -0
- package/es/AudioPlayer/AudioElement.mjs.map +1 -0
- package/es/AudioPlayer/AudioPlayer.mjs +7 -2
- package/es/AudioPlayer/AudioPlayer.mjs.map +1 -1
- package/es/AudioPlayer/AudioProgress.mjs +54 -0
- package/es/AudioPlayer/AudioProgress.mjs.map +1 -0
- package/es/AudioPlayer/CompactAudioPlayer.mjs +111 -0
- package/es/AudioPlayer/CompactAudioPlayer.mjs.map +1 -0
- package/es/AudioPlayer/Controls.mjs +25 -110
- package/es/AudioPlayer/Controls.mjs.map +1 -1
- package/es/AudioPlayer/PlayButton.mjs +24 -0
- package/es/AudioPlayer/PlayButton.mjs.map +1 -0
- package/es/AudioPlayer/SpeechControl.mjs +5 -16
- package/es/AudioPlayer/SpeechControl.mjs.map +1 -1
- package/es/AudioPlayer/VolumeSlider.mjs +31 -0
- package/es/AudioPlayer/VolumeSlider.mjs.map +1 -0
- package/es/AudioPlayer/audioUtils.mjs +17 -0
- package/es/AudioPlayer/audioUtils.mjs.map +1 -0
- package/es/AudioPlayer/useAudioControls.mjs +55 -0
- package/es/AudioPlayer/useAudioControls.mjs.map +1 -0
- package/es/Embed/AudioEmbed.mjs +3 -6
- package/es/Embed/AudioEmbed.mjs.map +1 -1
- package/es/Gloss/Gloss.mjs +1 -2
- package/es/Gloss/Gloss.mjs.map +1 -1
- package/es/index.mjs +2 -1
- package/lib/AudioPlayer/AudioElement.d.ts +14 -0
- package/lib/AudioPlayer/AudioElement.js +13 -0
- package/lib/AudioPlayer/AudioElement.js.map +1 -0
- package/lib/AudioPlayer/AudioPlayer.d.ts +5 -4
- package/lib/AudioPlayer/AudioPlayer.js +7 -2
- package/lib/AudioPlayer/AudioPlayer.js.map +1 -1
- package/lib/AudioPlayer/AudioProgress.d.ts +16 -0
- package/lib/AudioPlayer/AudioProgress.js +55 -0
- package/lib/AudioPlayer/AudioProgress.js.map +1 -0
- package/lib/AudioPlayer/CompactAudioPlayer.d.ts +13 -0
- package/lib/AudioPlayer/CompactAudioPlayer.js +112 -0
- package/lib/AudioPlayer/CompactAudioPlayer.js.map +1 -0
- package/lib/AudioPlayer/Controls.d.ts +1 -0
- package/lib/AudioPlayer/Controls.js +25 -110
- package/lib/AudioPlayer/Controls.js.map +1 -1
- package/lib/AudioPlayer/PlayButton.d.ts +13 -0
- package/lib/AudioPlayer/PlayButton.js +25 -0
- package/lib/AudioPlayer/PlayButton.js.map +1 -0
- package/lib/AudioPlayer/SpeechControl.d.ts +1 -2
- package/lib/AudioPlayer/SpeechControl.js +5 -16
- package/lib/AudioPlayer/SpeechControl.js.map +1 -1
- package/lib/AudioPlayer/VolumeSlider.d.ts +14 -0
- package/lib/AudioPlayer/VolumeSlider.js +32 -0
- package/lib/AudioPlayer/VolumeSlider.js.map +1 -0
- package/lib/AudioPlayer/audioUtils.d.ts +8 -0
- package/lib/AudioPlayer/audioUtils.js +17 -0
- package/lib/AudioPlayer/audioUtils.js.map +1 -0
- package/lib/AudioPlayer/useAudioControls.d.ts +24 -0
- package/lib/AudioPlayer/useAudioControls.js +56 -0
- package/lib/AudioPlayer/useAudioControls.js.map +1 -0
- package/lib/Embed/AudioEmbed.js +3 -6
- package/lib/Embed/AudioEmbed.js.map +1 -1
- package/lib/Gloss/Gloss.js +1 -2
- package/lib/Gloss/Gloss.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/package.json +2 -2
- package/src/AudioPlayer/AudioElement.tsx +20 -0
- package/src/AudioPlayer/{AudiPlayer.stories.tsx → AudioPlayer.stories.tsx} +10 -1
- package/src/AudioPlayer/AudioPlayer.tsx +12 -5
- package/src/AudioPlayer/AudioProgress.tsx +92 -0
- package/src/AudioPlayer/CompactAudioPlayer.tsx +124 -0
- package/src/AudioPlayer/Controls.tsx +36 -149
- package/src/AudioPlayer/PlayButton.tsx +24 -0
- package/src/AudioPlayer/SpeechControl.tsx +6 -19
- package/src/AudioPlayer/VolumeSlider.tsx +56 -0
- package/src/AudioPlayer/audioUtils.ts +15 -0
- package/src/AudioPlayer/useAudioControls.ts +80 -0
- package/src/Embed/AudioEmbed.tsx +10 -9
- package/src/Gloss/Gloss.tsx +1 -1
- 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,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"}
|
package/lib/Embed/AudioEmbed.js
CHANGED
|
@@ -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
|
-
|
|
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,13 +34,14 @@ 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,
|
|
44
41
|
textVersion: data.manuscript?.manuscript.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: data.manuscript.manuscript } }) : void 0,
|
|
45
42
|
title: data.title.title,
|
|
46
43
|
subtitle
|
|
47
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_EmbedByline.EmbedByline, {
|
|
44
|
+
}), variant === "standard" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_EmbedByline.EmbedByline, {
|
|
48
45
|
error: false,
|
|
49
46
|
type: data.audioType === "standard" ? "audio" : "podcast",
|
|
50
47
|
copyright: embed.data.copyright
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioEmbed.js","names":["Figure","EmbedErrorPlaceholder","
|
|
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 {variant === \"standard\" && (\n <EmbedByline\n error={false}\n type={data.audioType === \"standard\" ? \"audio\" : \"podcast\"}\n copyright={embed.data.copyright}\n />\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,EACD,YAAY,cACX,iBAAA,GAAA,kBAAA,KAACC,oBAAAA,aAAD;GACE,OAAO;GACP,MAAM,KAAK,cAAc,aAAa,UAAU;GAChD,WAAW,MAAM,KAAK;GACtB,CAAA,CAES"}
|
package/lib/Gloss/Gloss.js
CHANGED
|
@@ -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",
|
package/lib/Gloss/Gloss.js.map
CHANGED
|
@@ -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}
|
|
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.
|
|
4
|
+
"version": "56.0.188-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": "
|
|
66
|
+
"gitHead": "2c15990e9a7e9dbe56eb4168449f10f64a2a30b5"
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
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
|
+
};
|