@nualang/nualang-ui-components 0.1.1409 → 0.1.1410

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 (48) hide show
  1. package/dist/Assignments/AssignmentBotSelection/AssignmentBotSelection.js +1 -1
  2. package/dist/Assignments/AssignmentExerciseSelection/AssignmentExerciseSelection.js +2 -1
  3. package/dist/Assignments/AssignmentPhraseListSelection/AssignmentPhraseListSelection.js +8 -4
  4. package/dist/Assignments/AssignmentRoleplaySelection/AssignmentRoleplaySelection.js +1 -1
  5. package/dist/Containers/App/App.js +12 -1
  6. package/dist/Dialogs/ImageBuilder/ImageBuilderControls.js +250 -0
  7. package/dist/Dialogs/ImageBuilder/ImageBuilderDialog.js +138 -0
  8. package/dist/Dialogs/ImageBuilder/ImageBuilderPreview.js +79 -0
  9. package/dist/Dialogs/ImageBuilder/iconPaths.js +117 -0
  10. package/dist/Dialogs/ImageBuilder/imageBuilderUtils.js +159 -0
  11. package/dist/Dialogs/QuickCreateClassroomDialog/QuickCreateClassroomDialog.js +21 -4
  12. package/dist/Dialogs/QuickCreateCourseDialog/QuickCreateCourseDialog.js +17 -3
  13. package/dist/Dialogs/QuickCreateTopicDialog/QuickCreateTopicDialog.js +31 -9
  14. package/dist/Dialogs/ResponsiveDialog/ResponsiveDialog.js +2 -1
  15. package/dist/Dialogs/UploadPhrases/UploadPhrases.js +23 -1
  16. package/dist/Editors/Bot/Editor/BotDesigner/VarList.js +2 -1
  17. package/dist/Exercises/PhraseGroup/PhraseGroup.js +3 -1
  18. package/dist/Exercises/PhraseGroup/PhraseGroup.test.js +60 -16
  19. package/dist/Exercises/PhraseGroup/PhraseGroupGameOptions/PhraseGroupGame/PhraseGroupGame.js +0 -1
  20. package/dist/Exercises/PhraseGroup/PhraseGroupGameOptions/PhraseGroupGames.js +30 -27
  21. package/dist/Forms/CreateClassroom/CreateClassroom.js +6 -3
  22. package/dist/Forms/CreateClassroom/Steps/Customization/Customization.js +31 -6
  23. package/dist/Forms/CreateCourse/CreateCourse.js +6 -2
  24. package/dist/Forms/CreateCourse/Steps/Customization/Customization.js +28 -3
  25. package/dist/Forms/CreateTopic/Steps/Customization/Customization.js +28 -3
  26. package/dist/Forms/ImageSelector/ImageSelector.js +116 -17
  27. package/dist/Forms/Settings/Tabs/General/General.js +44 -22
  28. package/dist/Forms/UpdateClassroom/UpdateClassroom.js +9 -1
  29. package/dist/Forms/UpdateCourse/UpdateCourse.js +9 -1
  30. package/dist/Forms/UpdatePhraseList/UpdatePhraseList.js +2 -1
  31. package/dist/Forms/UpdateTopic/UpdateTopic.js +9 -1
  32. package/dist/Lists/CourseOutline/CourseOutline.js +5 -1
  33. package/dist/Lists/Phrases/Phrases.js +172 -24
  34. package/dist/Misc/FeatureNudge/FeatureNudge.js +160 -0
  35. package/dist/Misc/FeatureNudge/FeatureNudge.test.js +113 -0
  36. package/dist/Misc/FeatureNudge/package.json +6 -0
  37. package/dist/Misc/HtmlTooltipComponent/HtmlTooltipComponent.js +10 -1
  38. package/dist/Misc/newGenerateCardBackground/Generate Card Background (standalone) (1).html +186 -0
  39. package/dist/Navigation/BottomBar/BottomBar.js +25 -3
  40. package/dist/Navigation/ResponsiveDrawer/ResponsiveDrawer.js +128 -112
  41. package/dist/img/bots_background.svg +62 -0
  42. package/dist/img/classroom_background.svg +43 -0
  43. package/dist/img/courses_background.svg +92 -0
  44. package/dist/img/nua-glyph.svg +6 -0
  45. package/dist/img/nuala-silhouette.svg +43 -0
  46. package/dist/img/roleplay_background.svg +62 -0
  47. package/dist/utils/placeholder-images/index.js +9 -10
  48. package/package.json +1 -1
@@ -54,7 +54,7 @@ function Bot({
54
54
  classroomId
55
55
  } = params;
56
56
  const [courseId, sectionId, topicId] = courseSectionTopicId ? courseSectionTopicId.split("|") : "";
57
- const [lasClickedCourseId, lasClickedSectionId, lasClickedTopicId, lastClickedAssignmentId, lastClickedBotId] = lastClickedExerciseId?.split("|") || [];
57
+ const [lastClickedAssignmentId, lastClickedBotId] = lastClickedExerciseId?.split("|").slice(-2) || [];
58
58
  const addSearchParams = () => {
59
59
  if (!classroomId || !courseId || !sectionId || !topicId || !bot?.botId) {
60
60
  console.error("Missing required parameters for navigation");
@@ -221,7 +221,8 @@ function Exercise({
221
221
  completionHelpers: completionHelpers,
222
222
  username: username,
223
223
  preferred_username: preferred_username,
224
- isCreator: isCreator
224
+ isCreator: isCreator,
225
+ lastClickedExerciseId: lastClickedExerciseId
225
226
  })
226
227
  })
227
228
  }), name.toLowerCase() === "roleplays" && /*#__PURE__*/_jsx(Collapse, {
@@ -110,7 +110,8 @@ function PhraseList({
110
110
  progressHelpers,
111
111
  completionHelpers,
112
112
  username,
113
- preferred_username
113
+ preferred_username,
114
+ lastClickedExerciseId
114
115
  }) {
115
116
  const navigate = useNavigate();
116
117
  const {
@@ -120,8 +121,9 @@ function PhraseList({
120
121
  const {
121
122
  classroomId
122
123
  } = params;
124
+ const [lastClickedAssignmentId, lastClickedPhraseListId] = lastClickedExerciseId?.split("|").slice(-2) || [];
123
125
  const [isSelected, setIsSelected] = useState(false);
124
- const [isPhraseListOpen, setIsPhraseListOpen] = useState(false);
126
+ const [isPhraseListOpen, setIsPhraseListOpen] = useState(lastClickedPhraseListId === phraseList.phraseListId && lastClickedAssignmentId === assignment.assignmentId);
125
127
  const [isSelectAllChecked, setIsSelectAllChecked] = useState(false);
126
128
  const [indeterminate, setIndeterminate] = useState(false);
127
129
  const activePhrases = phraseList?.phrases || [];
@@ -436,7 +438,8 @@ export default function AssignmentPhraseListSelection({
436
438
  completionHelpers,
437
439
  username,
438
440
  preferred_username,
439
- isCreator
441
+ isCreator,
442
+ lastClickedExerciseId
440
443
  }) {
441
444
  const selectedPhraseListIds = new Set(selectedExercises?.map(exercise => exercise.phraseListId) || []);
442
445
  const filteredPhraseLists = !viewOnly ? phraseLists : phraseLists.filter(phraseList => selectedPhraseListIds.has(phraseList.phraseListId));
@@ -458,7 +461,8 @@ export default function AssignmentPhraseListSelection({
458
461
  completionHelpers: completionHelpers,
459
462
  username: username,
460
463
  preferred_username: preferred_username,
461
- isCreator: isCreator
464
+ isCreator: isCreator,
465
+ lastClickedExerciseId: lastClickedExerciseId
462
466
  }, index)), orphanedPhraseListIds.map(id => /*#__PURE__*/_jsx(Box, {
463
467
  sx: {
464
468
  display: "flex",
@@ -97,7 +97,7 @@ function Roleplay({
97
97
  const [selectedGames, setSelectedGames] = useState([]);
98
98
  const [indeterminate, setIndeterminate] = useState(false);
99
99
  const [courseId, sectionId, topicId] = courseSectionTopicId ? courseSectionTopicId.split("|") : "";
100
- const [lastClickedAssignmentId, lastClickedRoleplayId] = lastClickedExerciseId?.split("|") || [];
100
+ const [lastClickedAssignmentId, lastClickedRoleplayId] = lastClickedExerciseId?.split("|").slice(-2) || [];
101
101
  const addSearchParams = exercise => {
102
102
  if (!classroomId || !courseId || !sectionId || !topicId || !roleplay?.roleplayId) {
103
103
  console.error("Missing required parameters for navigation");
@@ -513,7 +513,18 @@ function App({
513
513
  ...user
514
514
  }) : /*#__PURE__*/_jsx(BottomBar, {
515
515
  navigation: bottomNav,
516
- t: t
516
+ t: t,
517
+ nudges: [{
518
+ // Mobile counterpart of the sidebar profile nudge: the settings
519
+ // item is the one-tap route to the last-name field.
520
+ url: navToSettings,
521
+ active: !!(user && user.given_name && !user.family_name),
522
+ nudgeId: "add-family-name-bottom-nav",
523
+ tooltipTitle: t("nudge_add_family_name_title"),
524
+ tooltipDesc: t("nudge_add_family_name_profile_desc"),
525
+ placement: "top",
526
+ "data-cy": "nudge-add-family-name-bottom-nav"
527
+ }]
517
528
  }), /*#__PURE__*/_jsxs("main", {
518
529
  className: classes.main,
519
530
  children: [/*#__PURE__*/_jsx("div", {
@@ -0,0 +1,250 @@
1
+ import { makeStyles } from "tss-react/mui";
2
+ import { Typography, ButtonBase, Box, Slider } from "@mui/material";
3
+ import PaletteIcon from "@mui/icons-material/Palette";
4
+ import CheckIcon from "@mui/icons-material/Check";
5
+ import GroupsIcon from "@mui/icons-material/Groups";
6
+ import TranslateIcon from "@mui/icons-material/Translate";
7
+ import SchoolIcon from "@mui/icons-material/School";
8
+ import MenuBookIcon from "@mui/icons-material/MenuBook";
9
+ import AutoStoriesIcon from "@mui/icons-material/AutoStories";
10
+ import RecordVoiceOverIcon from "@mui/icons-material/RecordVoiceOver";
11
+ import ForumIcon from "@mui/icons-material/Forum";
12
+ import MicIcon from "@mui/icons-material/Mic";
13
+ import HeadphonesIcon from "@mui/icons-material/Headphones";
14
+ import EditIcon from "@mui/icons-material/Edit";
15
+ import QuizIcon from "@mui/icons-material/Quiz";
16
+ import LanguageIcon from "@mui/icons-material/Language";
17
+ import WavingHandIcon from "@mui/icons-material/WavingHand";
18
+ import LightbulbIcon from "@mui/icons-material/Lightbulb";
19
+ import FlagIcon from "@mui/icons-material/Flag";
20
+ import TravelExploreIcon from "@mui/icons-material/TravelExplore";
21
+ import { PRESET_COLORS, ICON_PATHS, ICON_KEYS } from "./iconPaths";
22
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
+ const ICON_COMPONENTS = {
24
+ groups: GroupsIcon,
25
+ translate: TranslateIcon,
26
+ school: SchoolIcon,
27
+ menu_book: MenuBookIcon,
28
+ auto_stories: AutoStoriesIcon,
29
+ record_voice_over: RecordVoiceOverIcon,
30
+ forum: ForumIcon,
31
+ mic: MicIcon,
32
+ headphones: HeadphonesIcon,
33
+ edit: EditIcon,
34
+ quiz: QuizIcon,
35
+ language: LanguageIcon,
36
+ waving_hand: WavingHandIcon,
37
+ lightbulb: LightbulbIcon,
38
+ flag: FlagIcon,
39
+ travel_explore: TravelExploreIcon
40
+ };
41
+
42
+ // Build mask-image URLs for SVG-based icons (computed once at module level)
43
+ const SVG_MASK_URLS = {};
44
+ for (const key of ICON_KEYS) {
45
+ if (!ICON_COMPONENTS[key]) {
46
+ const iconData = ICON_PATHS[key];
47
+ const vb = iconData.viewBox || {
48
+ width: 24,
49
+ height: 24
50
+ };
51
+ const pathsStr = iconData.paths.map(d => `<path d="${d}"/>`).join("");
52
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${vb.width} ${vb.height}" fill="black">${pathsStr}</svg>`;
53
+ SVG_MASK_URLS[key] = `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
54
+ }
55
+ }
56
+ const useStyles = makeStyles()(theme => ({
57
+ root: {
58
+ width: 440,
59
+ flexShrink: 0,
60
+ padding: "28px 32px",
61
+ borderRight: `1px solid ${theme.palette.divider}`,
62
+ overflowY: "auto",
63
+ [theme.breakpoints.down("md")]: {
64
+ width: "100%",
65
+ borderRight: "none",
66
+ borderBottom: `1px solid ${theme.palette.divider}`
67
+ }
68
+ },
69
+ sectionLabel: {
70
+ fontSize: 14,
71
+ fontWeight: 600,
72
+ marginBottom: 12
73
+ },
74
+ swatchRow: {
75
+ display: "flex",
76
+ flexWrap: "wrap",
77
+ gap: 12,
78
+ marginBottom: 14
79
+ },
80
+ swatch: {
81
+ width: 40,
82
+ height: 40,
83
+ borderRadius: 8,
84
+ border: "none",
85
+ cursor: "pointer",
86
+ padding: 0,
87
+ transition: "box-shadow 0.15s"
88
+ },
89
+ customSwatch: {
90
+ position: "relative",
91
+ display: "flex",
92
+ alignItems: "center",
93
+ justifyContent: "center",
94
+ width: 40,
95
+ height: 40,
96
+ borderRadius: 8,
97
+ cursor: "pointer",
98
+ overflow: "hidden"
99
+ },
100
+ colorInput: {
101
+ position: "absolute",
102
+ inset: 0,
103
+ opacity: 0,
104
+ cursor: "pointer",
105
+ width: "100%",
106
+ height: "100%",
107
+ border: "none"
108
+ },
109
+ iconGrid: {
110
+ display: "grid",
111
+ gridTemplateColumns: "repeat(6, 1fr)",
112
+ gap: 10,
113
+ marginBottom: 8
114
+ },
115
+ iconCell: {
116
+ display: "flex",
117
+ alignItems: "center",
118
+ justifyContent: "center",
119
+ aspectRatio: "1",
120
+ borderRadius: 8,
121
+ cursor: "pointer",
122
+ padding: 0,
123
+ transition: "background-color 0.15s, border-color 0.15s"
124
+ }
125
+ }));
126
+ function getSwatchSx(colorValue, isSelected) {
127
+ return {
128
+ backgroundColor: colorValue,
129
+ boxShadow: isSelected ? theme => `0 0 0 3px ${theme.palette.background.paper}, 0 0 0 5px ${theme.palette.primary.main}` : "inset 0 0 0 1px rgba(0,0,0,0.12)"
130
+ };
131
+ }
132
+ export default function ImageBuilderControls({
133
+ t,
134
+ color,
135
+ onColorChange,
136
+ customColor,
137
+ onCustomColorChange,
138
+ iconKey,
139
+ onIconChange,
140
+ iconOpacity,
141
+ onIconOpacityChange
142
+ }) {
143
+ const {
144
+ classes
145
+ } = useStyles();
146
+ const isPreset = PRESET_COLORS.some(c => c.value.toLowerCase() === color.toLowerCase());
147
+ return /*#__PURE__*/_jsxs(Box, {
148
+ className: classes.root,
149
+ children: [/*#__PURE__*/_jsx(Typography, {
150
+ className: classes.sectionLabel,
151
+ children: t("background_colour")
152
+ }), /*#__PURE__*/_jsxs("div", {
153
+ className: classes.swatchRow,
154
+ children: [PRESET_COLORS.map(c => {
155
+ const selected = c.value.toLowerCase() === color.toLowerCase();
156
+ return /*#__PURE__*/_jsx(ButtonBase, {
157
+ className: classes.swatch,
158
+ sx: getSwatchSx(c.value, selected),
159
+ onClick: () => onColorChange(c.value),
160
+ title: c.name,
161
+ "aria-label": c.name,
162
+ children: selected && /*#__PURE__*/_jsx(CheckIcon, {
163
+ sx: {
164
+ color: "#fff",
165
+ fontSize: 20
166
+ }
167
+ })
168
+ }, c.value);
169
+ }), /*#__PURE__*/_jsxs(ButtonBase, {
170
+ className: classes.customSwatch,
171
+ sx: getSwatchSx(customColor, !isPreset),
172
+ component: "label",
173
+ title: t("custom_colour"),
174
+ "aria-label": t("custom_colour"),
175
+ children: [/*#__PURE__*/_jsx(PaletteIcon, {
176
+ sx: {
177
+ color: "#fff",
178
+ fontSize: 20,
179
+ mixBlendMode: "difference"
180
+ }
181
+ }), /*#__PURE__*/_jsx("input", {
182
+ type: "color",
183
+ value: customColor,
184
+ onChange: e => onCustomColorChange(e.target.value),
185
+ className: classes.colorInput
186
+ })]
187
+ })]
188
+ }), /*#__PURE__*/_jsx(Typography, {
189
+ className: classes.sectionLabel,
190
+ sx: {
191
+ mt: 3
192
+ },
193
+ children: t("icon")
194
+ }), /*#__PURE__*/_jsx("div", {
195
+ className: classes.iconGrid,
196
+ children: ICON_KEYS.map(key => {
197
+ const IconComp = ICON_COMPONENTS[key];
198
+ const maskUrl = SVG_MASK_URLS[key];
199
+ const selected = key === iconKey;
200
+ return /*#__PURE__*/_jsx(ButtonBase, {
201
+ className: classes.iconCell,
202
+ sx: theme => ({
203
+ backgroundColor: selected ? theme.palette.primary.light + "30" : theme.palette.background.paper,
204
+ border: selected ? `2px solid ${theme.palette.primary.main}` : `1px solid ${theme.palette.divider}`
205
+ }),
206
+ onClick: () => onIconChange(key),
207
+ title: key.replace(/_/g, " "),
208
+ "aria-label": key.replace(/_/g, " "),
209
+ children: IconComp ? /*#__PURE__*/_jsx(IconComp, {
210
+ sx: theme => ({
211
+ color: selected ? theme.palette.primary.main : theme.palette.text.secondary,
212
+ fontSize: 24
213
+ })
214
+ }) : /*#__PURE__*/_jsx(Box, {
215
+ sx: theme => ({
216
+ width: 24,
217
+ height: 24,
218
+ backgroundColor: selected ? theme.palette.primary.main : theme.palette.text.secondary,
219
+ maskImage: maskUrl,
220
+ WebkitMaskImage: maskUrl,
221
+ maskSize: "contain",
222
+ WebkitMaskSize: "contain",
223
+ maskRepeat: "no-repeat",
224
+ WebkitMaskRepeat: "no-repeat",
225
+ maskPosition: "center",
226
+ WebkitMaskPosition: "center"
227
+ })
228
+ })
229
+ }, key);
230
+ })
231
+ }), /*#__PURE__*/_jsx(Typography, {
232
+ className: classes.sectionLabel,
233
+ sx: {
234
+ mt: 3
235
+ },
236
+ children: t("icon_opacity")
237
+ }), /*#__PURE__*/_jsx(Slider, {
238
+ value: iconOpacity,
239
+ min: 0.05,
240
+ max: 0.5,
241
+ step: 0.01,
242
+ onChange: (e, val) => onIconOpacityChange(val),
243
+ valueLabelDisplay: "auto",
244
+ valueLabelFormat: v => `${Math.round(v * 100)}%`,
245
+ sx: {
246
+ maxWidth: 360
247
+ }
248
+ })]
249
+ });
250
+ }
@@ -0,0 +1,138 @@
1
+ import { useState, useEffect, useMemo } from "react";
2
+ import { Box, Button } from "@mui/material";
3
+ import { makeStyles } from "tss-react/mui";
4
+ import CheckIcon from "@mui/icons-material/Check";
5
+ import CasinoIcon from "@mui/icons-material/Casino";
6
+ import ImageBuilderControls from "./ImageBuilderControls";
7
+ import ImageBuilderPreview from "./ImageBuilderPreview";
8
+ import ResponsiveDialog from "../ResponsiveDialog/ResponsiveDialog";
9
+ import { PRESET_COLORS, ICON_KEYS } from "./iconPaths";
10
+ import { generatePatternCanvas, getCenterCropDataUrl, getOptimalIconOpacity, shouldUseDarkIcons } from "./imageBuilderUtils";
11
+
12
+ // Matches placeholder SVG ratio (21144 x 4890), scaled down 10x
13
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
+ const BANNER_W = 2114;
15
+ const BANNER_H = 489;
16
+ const CARD_SIZE = 489;
17
+ const useStyles = makeStyles()(theme => ({
18
+ body: {
19
+ display: "flex",
20
+ [theme.breakpoints.down("md")]: {
21
+ flexDirection: "column"
22
+ }
23
+ }
24
+ }));
25
+ export default function ImageBuilderDialog({
26
+ t,
27
+ open,
28
+ onClose,
29
+ onConfirm,
30
+ initialColor,
31
+ initialIconKey
32
+ }) {
33
+ const {
34
+ classes
35
+ } = useStyles();
36
+ const defaultColor = initialColor || PRESET_COLORS[0].value;
37
+ const [color, setColor] = useState(defaultColor);
38
+ const [customColor, setCustomColor] = useState(defaultColor);
39
+ const [iconKey, setIconKey] = useState(initialIconKey || "school");
40
+ const [iconOpacity, setIconOpacity] = useState(() => getOptimalIconOpacity(defaultColor));
41
+ const darkIcons = shouldUseDarkIcons(color);
42
+
43
+ // Generate preview — only recalculates when inputs change
44
+ const {
45
+ bannerDataUrl,
46
+ cardDataUrl
47
+ } = useMemo(() => {
48
+ if (typeof document === "undefined") {
49
+ return {
50
+ bannerDataUrl: null,
51
+ cardDataUrl: null
52
+ };
53
+ }
54
+ const canvas = generatePatternCanvas({
55
+ color,
56
+ iconKey,
57
+ density: "dense",
58
+ iconOpacity,
59
+ darkIcons,
60
+ width: BANNER_W,
61
+ height: BANNER_H
62
+ });
63
+ return {
64
+ bannerDataUrl: canvas.toDataURL("image/png"),
65
+ cardDataUrl: getCenterCropDataUrl(canvas, CARD_SIZE)
66
+ };
67
+ }, [color, iconKey, iconOpacity, darkIcons]);
68
+
69
+ // Reset state when dialog opens
70
+ useEffect(() => {
71
+ if (open) {
72
+ const c = initialColor || PRESET_COLORS[0].value;
73
+ setColor(c);
74
+ setCustomColor(c);
75
+ setIconKey(initialIconKey || "school");
76
+ setIconOpacity(getOptimalIconOpacity(c));
77
+ }
78
+ }, [open, initialColor, initialIconKey]);
79
+ const handleColorChange = val => {
80
+ setColor(val);
81
+ setCustomColor(val);
82
+ setIconOpacity(getOptimalIconOpacity(val));
83
+ };
84
+ const handleCustomColorChange = val => {
85
+ setCustomColor(val);
86
+ setColor(val);
87
+ setIconOpacity(getOptimalIconOpacity(val));
88
+ };
89
+ const handleRandomize = () => {
90
+ const c = PRESET_COLORS[Math.floor(Math.random() * PRESET_COLORS.length)].value;
91
+ const ic = ICON_KEYS[Math.floor(Math.random() * ICON_KEYS.length)];
92
+ setColor(c);
93
+ setCustomColor(c);
94
+ setIconKey(ic);
95
+ setIconOpacity(getOptimalIconOpacity(c));
96
+ };
97
+ const handleConfirm = () => {
98
+ onConfirm(bannerDataUrl, {
99
+ color,
100
+ iconKey
101
+ });
102
+ };
103
+ return /*#__PURE__*/_jsx(ResponsiveDialog, {
104
+ open: open,
105
+ handleClose: onClose,
106
+ handleSubmit: handleConfirm,
107
+ dialogTitle: t("image_builder"),
108
+ submitText: t("use_image"),
109
+ closeText: t("cancel"),
110
+ maxWidth: "lg",
111
+ startIcon: /*#__PURE__*/_jsx(CheckIcon, {}),
112
+ t: t,
113
+ extraActions: /*#__PURE__*/_jsx(Button, {
114
+ variant: "outlined",
115
+ onClick: handleRandomize,
116
+ startIcon: /*#__PURE__*/_jsx(CasinoIcon, {}),
117
+ children: t("randomise")
118
+ }),
119
+ children: /*#__PURE__*/_jsxs(Box, {
120
+ className: classes.body,
121
+ children: [/*#__PURE__*/_jsx(ImageBuilderControls, {
122
+ t: t,
123
+ color: color,
124
+ onColorChange: handleColorChange,
125
+ customColor: customColor,
126
+ onCustomColorChange: handleCustomColorChange,
127
+ iconKey: iconKey,
128
+ onIconChange: setIconKey,
129
+ iconOpacity: iconOpacity,
130
+ onIconOpacityChange: setIconOpacity
131
+ }), /*#__PURE__*/_jsx(ImageBuilderPreview, {
132
+ t: t,
133
+ bannerDataUrl: bannerDataUrl,
134
+ cardDataUrl: cardDataUrl
135
+ })]
136
+ })
137
+ });
138
+ }
@@ -0,0 +1,79 @@
1
+ import { makeStyles } from "tss-react/mui";
2
+ import { Typography, Box } from "@mui/material";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ const useStyles = makeStyles()(theme => ({
5
+ root: {
6
+ flex: 1,
7
+ padding: "28px 32px",
8
+ backgroundColor: theme.palette.mode === "dark" ? theme.palette.background.default : "#ffffff",
9
+ [theme.breakpoints.down("md")]: {
10
+ padding: "20px 16px"
11
+ }
12
+ },
13
+ label: {
14
+ fontSize: 13,
15
+ fontWeight: 600,
16
+ color: theme.palette.text.secondary,
17
+ marginBottom: 10,
18
+ letterSpacing: "0.02em"
19
+ },
20
+ bannerImg: {
21
+ width: "100%",
22
+ aspectRatio: "2114 / 489",
23
+ borderRadius: 8,
24
+ overflow: "hidden",
25
+ border: `1px solid ${theme.palette.divider}`,
26
+ marginBottom: 24,
27
+ objectFit: "cover"
28
+ },
29
+ cardRow: {
30
+ display: "flex",
31
+ alignItems: "flex-end",
32
+ gap: 20
33
+ },
34
+ cardImg: {
35
+ width: 150,
36
+ height: 150,
37
+ flexShrink: 0,
38
+ borderRadius: 10,
39
+ border: `1px solid ${theme.palette.divider}`,
40
+ objectFit: "cover"
41
+ },
42
+ helperText: {
43
+ margin: 0,
44
+ fontSize: 13,
45
+ lineHeight: 1.5,
46
+ color: theme.palette.text.secondary,
47
+ maxWidth: 180
48
+ }
49
+ }));
50
+ export default function ImageBuilderPreview({
51
+ t,
52
+ bannerDataUrl,
53
+ cardDataUrl
54
+ }) {
55
+ const {
56
+ classes
57
+ } = useStyles();
58
+ return /*#__PURE__*/_jsxs(Box, {
59
+ className: classes.root,
60
+ children: [/*#__PURE__*/_jsx(Typography, {
61
+ className: classes.label,
62
+ children: t("banner")
63
+ }), bannerDataUrl && /*#__PURE__*/_jsx("img", {
64
+ src: bannerDataUrl,
65
+ alt: t("banner_preview"),
66
+ className: classes.bannerImg
67
+ }), /*#__PURE__*/_jsx(Typography, {
68
+ className: classes.label,
69
+ children: t("card")
70
+ }), /*#__PURE__*/_jsx("div", {
71
+ className: classes.cardRow,
72
+ children: cardDataUrl && /*#__PURE__*/_jsx("img", {
73
+ src: cardDataUrl,
74
+ alt: t("card_preview"),
75
+ className: classes.cardImg
76
+ })
77
+ })]
78
+ });
79
+ }