dirk-cfx-react 1.1.72 → 1.1.75

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.
@@ -678,6 +678,7 @@ type ButtonProps = {
678
678
  icon: string;
679
679
  onClick?: () => void;
680
680
  };
681
+ type TitleSize = "xs" | "sm" | "md" | "lg" | "xl";
681
682
  type TitleProps = {
682
683
  title: string;
683
684
  description: string;
@@ -688,6 +689,12 @@ type TitleProps = {
688
689
  removeBorder?: boolean;
689
690
  borderColor?: string;
690
691
  p?: string;
692
+ /**
693
+ * Scale of the whole block — icon, title text, description text, and inner
694
+ * spacing all follow this single value. Defaults to "md" to preserve the
695
+ * historical look so existing consumers don't shift.
696
+ */
697
+ size?: TitleSize;
691
698
  rightSection?: React.ReactNode;
692
699
  };
693
700
  declare function Title(props: TitleProps): react_jsx_runtime.JSX.Element;
@@ -993,6 +1000,7 @@ interface TestBedItem {
993
1000
  active: boolean;
994
1001
  onToggle: (next: boolean) => void;
995
1002
  }
1003
+ type TestBedPlacement = "top-left" | "top-center" | "top-right";
996
1004
  interface TestBedProps {
997
1005
  items: TestBedItem[];
998
1006
  /** localStorage key used to persist toggled state across reloads. Default: "testbed:open-state". */
@@ -1001,7 +1009,29 @@ interface TestBedProps {
1001
1009
  disablePersistence?: boolean;
1002
1010
  /** Header label shown in the collapsed pill. Default: "TestBed". */
1003
1011
  title?: string;
1012
+ /** Where to anchor the floating panel. Default: "top-left". */
1013
+ placement?: TestBedPlacement;
1004
1014
  }
1005
- declare function TestBed({ items, storageKey, disablePersistence, title, }: TestBedProps): react_jsx_runtime.JSX.Element | null;
1015
+ declare function TestBed({ items, storageKey, disablePersistence, title, placement, }: TestBedProps): react_jsx_runtime.JSX.Element | null;
1016
+
1017
+ type ThemeOverrideValue = {
1018
+ useOverride: boolean;
1019
+ primaryColor: string;
1020
+ primaryShade: number;
1021
+ customTheme: string[];
1022
+ };
1023
+ type ThemeOverrideSectionProps = {
1024
+ /**
1025
+ * Schema path the consumer stores its override block under. Defaults to
1026
+ * "theme" — matches the recommended schema key.
1027
+ */
1028
+ schemaKey?: string;
1029
+ /**
1030
+ * Optional title for the AdminPageTitle. Defaults to the localized
1031
+ * "Theme" string with a fallback.
1032
+ */
1033
+ title?: string;
1034
+ };
1035
+ declare function ThemeOverrideSection({ schemaKey, title, }: ThemeOverrideSectionProps): react_jsx_runtime.JSX.Element;
1006
1036
 
1007
- export { AdminPageTitle, type AdminPageTitleProps, AsyncSaveButton, BlipColorSelect, type BlipColorSelectProps, BlipDisplaySelect, type BlipDisplaySelectProps, BlipIconSelect, type BlipIconSelectProps, BorderedIcon, type BorderedIconProps, type ButtonProps, ConfigPanel, type ConfigPanelProps, ConfirmModal, type ConfirmModalProps, ControlMultiSelect, type ControlMultiSelectProps, ControlSelect, type ControlSelectProps, Counter, type FiveMControls, FiveMKeyBindInput, FloatingParticles, type FloatingParticlesProps, GroupName, type GroupNameProps, GroupRank, type GroupRankProps, GroupSelect, type GroupSelectProps, type GroupType, type GroupValue, InfoBox, type InfoBoxProps, InputContainer, type InputContainerProps, LevelBanner, LevelPanel, MissingItemsBanner, Modal, ModalContext, type ModalProps, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, type NavItem, NavigationContext, NavigationProvider, type NavigationStore, type ParticleState, PositionPicker, type PositionPickerProps, type ProgressProps, type Prompt, type PromptButton, PromptModal, type SegmentProps, SegmentedControl, type SegmentedControlProps, SegmentedProgress, SelectItem, type SelectItemProps, type StoreModalProps, TestBed, type TestBedItem, type TestBedProps, Title, type TitleProps, Vector4DeleteButton, Vector4Display, type Vector4Value, WorldPositionGotoButton, WorldPositionSetButton, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore };
1037
+ export { AdminPageTitle, type AdminPageTitleProps, AsyncSaveButton, BlipColorSelect, type BlipColorSelectProps, BlipDisplaySelect, type BlipDisplaySelectProps, BlipIconSelect, type BlipIconSelectProps, BorderedIcon, type BorderedIconProps, type ButtonProps, ConfigPanel, type ConfigPanelProps, ConfirmModal, type ConfirmModalProps, ControlMultiSelect, type ControlMultiSelectProps, ControlSelect, type ControlSelectProps, Counter, type FiveMControls, FiveMKeyBindInput, FloatingParticles, type FloatingParticlesProps, GroupName, type GroupNameProps, GroupRank, type GroupRankProps, GroupSelect, type GroupSelectProps, type GroupType, type GroupValue, InfoBox, type InfoBoxProps, InputContainer, type InputContainerProps, LevelBanner, LevelPanel, MissingItemsBanner, Modal, ModalContext, type ModalProps, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, type NavItem, NavigationContext, NavigationProvider, type NavigationStore, type ParticleState, PositionPicker, type PositionPickerProps, type ProgressProps, type Prompt, type PromptButton, PromptModal, type SegmentProps, SegmentedControl, type SegmentedControlProps, SegmentedProgress, SelectItem, type SelectItemProps, type StoreModalProps, TestBed, type TestBedItem, type TestBedPlacement, type TestBedProps, ThemeOverrideSection, type ThemeOverrideSectionProps, type ThemeOverrideValue, Title, type TitleProps, type TitleSize, Vector4DeleteButton, Vector4Display, type Vector4Value, WorldPositionGotoButton, WorldPositionSetButton, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore };
@@ -678,6 +678,7 @@ type ButtonProps = {
678
678
  icon: string;
679
679
  onClick?: () => void;
680
680
  };
681
+ type TitleSize = "xs" | "sm" | "md" | "lg" | "xl";
681
682
  type TitleProps = {
682
683
  title: string;
683
684
  description: string;
@@ -688,6 +689,12 @@ type TitleProps = {
688
689
  removeBorder?: boolean;
689
690
  borderColor?: string;
690
691
  p?: string;
692
+ /**
693
+ * Scale of the whole block — icon, title text, description text, and inner
694
+ * spacing all follow this single value. Defaults to "md" to preserve the
695
+ * historical look so existing consumers don't shift.
696
+ */
697
+ size?: TitleSize;
691
698
  rightSection?: React.ReactNode;
692
699
  };
693
700
  declare function Title(props: TitleProps): react_jsx_runtime.JSX.Element;
@@ -993,6 +1000,7 @@ interface TestBedItem {
993
1000
  active: boolean;
994
1001
  onToggle: (next: boolean) => void;
995
1002
  }
1003
+ type TestBedPlacement = "top-left" | "top-center" | "top-right";
996
1004
  interface TestBedProps {
997
1005
  items: TestBedItem[];
998
1006
  /** localStorage key used to persist toggled state across reloads. Default: "testbed:open-state". */
@@ -1001,7 +1009,29 @@ interface TestBedProps {
1001
1009
  disablePersistence?: boolean;
1002
1010
  /** Header label shown in the collapsed pill. Default: "TestBed". */
1003
1011
  title?: string;
1012
+ /** Where to anchor the floating panel. Default: "top-left". */
1013
+ placement?: TestBedPlacement;
1004
1014
  }
1005
- declare function TestBed({ items, storageKey, disablePersistence, title, }: TestBedProps): react_jsx_runtime.JSX.Element | null;
1015
+ declare function TestBed({ items, storageKey, disablePersistence, title, placement, }: TestBedProps): react_jsx_runtime.JSX.Element | null;
1016
+
1017
+ type ThemeOverrideValue = {
1018
+ useOverride: boolean;
1019
+ primaryColor: string;
1020
+ primaryShade: number;
1021
+ customTheme: string[];
1022
+ };
1023
+ type ThemeOverrideSectionProps = {
1024
+ /**
1025
+ * Schema path the consumer stores its override block under. Defaults to
1026
+ * "theme" — matches the recommended schema key.
1027
+ */
1028
+ schemaKey?: string;
1029
+ /**
1030
+ * Optional title for the AdminPageTitle. Defaults to the localized
1031
+ * "Theme" string with a fallback.
1032
+ */
1033
+ title?: string;
1034
+ };
1035
+ declare function ThemeOverrideSection({ schemaKey, title, }: ThemeOverrideSectionProps): react_jsx_runtime.JSX.Element;
1006
1036
 
1007
- export { AdminPageTitle, type AdminPageTitleProps, AsyncSaveButton, BlipColorSelect, type BlipColorSelectProps, BlipDisplaySelect, type BlipDisplaySelectProps, BlipIconSelect, type BlipIconSelectProps, BorderedIcon, type BorderedIconProps, type ButtonProps, ConfigPanel, type ConfigPanelProps, ConfirmModal, type ConfirmModalProps, ControlMultiSelect, type ControlMultiSelectProps, ControlSelect, type ControlSelectProps, Counter, type FiveMControls, FiveMKeyBindInput, FloatingParticles, type FloatingParticlesProps, GroupName, type GroupNameProps, GroupRank, type GroupRankProps, GroupSelect, type GroupSelectProps, type GroupType, type GroupValue, InfoBox, type InfoBoxProps, InputContainer, type InputContainerProps, LevelBanner, LevelPanel, MissingItemsBanner, Modal, ModalContext, type ModalProps, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, type NavItem, NavigationContext, NavigationProvider, type NavigationStore, type ParticleState, PositionPicker, type PositionPickerProps, type ProgressProps, type Prompt, type PromptButton, PromptModal, type SegmentProps, SegmentedControl, type SegmentedControlProps, SegmentedProgress, SelectItem, type SelectItemProps, type StoreModalProps, TestBed, type TestBedItem, type TestBedProps, Title, type TitleProps, Vector4DeleteButton, Vector4Display, type Vector4Value, WorldPositionGotoButton, WorldPositionSetButton, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore };
1037
+ export { AdminPageTitle, type AdminPageTitleProps, AsyncSaveButton, BlipColorSelect, type BlipColorSelectProps, BlipDisplaySelect, type BlipDisplaySelectProps, BlipIconSelect, type BlipIconSelectProps, BorderedIcon, type BorderedIconProps, type ButtonProps, ConfigPanel, type ConfigPanelProps, ConfirmModal, type ConfirmModalProps, ControlMultiSelect, type ControlMultiSelectProps, ControlSelect, type ControlSelectProps, Counter, type FiveMControls, FiveMKeyBindInput, FloatingParticles, type FloatingParticlesProps, GroupName, type GroupNameProps, GroupRank, type GroupRankProps, GroupSelect, type GroupSelectProps, type GroupType, type GroupValue, InfoBox, type InfoBoxProps, InputContainer, type InputContainerProps, LevelBanner, LevelPanel, MissingItemsBanner, Modal, ModalContext, type ModalProps, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, type NavItem, NavigationContext, NavigationProvider, type NavigationStore, type ParticleState, PositionPicker, type PositionPickerProps, type ProgressProps, type Prompt, type PromptButton, PromptModal, type SegmentProps, SegmentedControl, type SegmentedControlProps, SegmentedProgress, SelectItem, type SelectItemProps, type StoreModalProps, TestBed, type TestBedItem, type TestBedPlacement, type TestBedProps, ThemeOverrideSection, type ThemeOverrideSectionProps, type ThemeOverrideValue, Title, type TitleProps, type TitleSize, Vector4DeleteButton, Vector4Display, type Vector4Value, WorldPositionGotoButton, WorldPositionSetButton, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore };
@@ -1,14 +1,15 @@
1
- import { Flex, Text, Image, TextInput, Select, Box, useMantineTheme, Tooltip, alpha, Progress, RingProgress, Portal, Button, NumberInput, MultiSelect, Loader, ActionIcon, Stack, Group, JsonInput } from '@mantine/core';
1
+ import { Flex, Text, Image, TextInput, Select, Box, useMantineTheme, Tooltip, alpha, Progress, RingProgress, Portal, Button, NumberInput, MultiSelect, Loader, ActionIcon, Stack, Switch, ColorInput, Popover, Group, JsonInput } from '@mantine/core';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { createContext, useContext, useRef, useState, useEffect, useCallback, useMemo } from 'react';
4
4
  import { create, useStore, createStore } from 'zustand';
5
5
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6
6
  import { motion, AnimatePresence, useMotionValue } from 'framer-motion';
7
- import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
7
+ import { Info, X, AlertTriangle, Trash2, RefreshCw, ChevronDown, Check, Copy, MapPin, Crosshair, EyeOff, Eye, RotateCcw, FlaskConical, ChevronUp, Palette, ArrowLeft, Undo2, Redo2, Save, History, XCircle, Code2, Search, Filter, User } from 'lucide-react';
8
8
  import clickSoundUrl from '../click_sound-PNCRRTM4.mp3';
9
9
  import hoverSoundUrl from '../hover_sound-NBUA222C.mp3';
10
10
  import { notifications } from '@mantine/notifications';
11
11
  import { QueryClient, QueryClientProvider, useInfiniteQuery } from '@tanstack/react-query';
12
+ import { generateColors } from '@mantine/colors-generator';
12
13
 
13
14
  // src/components/BlipSelect.tsx
14
15
  var BLIP_ENTRIES = [
@@ -1292,7 +1293,11 @@ async function fetchNui(eventName, data, mockData) {
1292
1293
  return {};
1293
1294
  }
1294
1295
  const overrideResourceName = useSettings.getState().overideResourceName;
1295
- const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName ? overrideResourceName : "dirk-cfx-react";
1296
+ const hasResourceContext = typeof window.GetParentResourceName === "function" || !!overrideResourceName;
1297
+ if (!hasResourceContext) {
1298
+ return mockData ?? {};
1299
+ }
1300
+ const resourceName = window.GetParentResourceName ? window.GetParentResourceName() : overrideResourceName;
1296
1301
  try {
1297
1302
  const resp = await fetch(`https://${resourceName}/${eventName}`, options);
1298
1303
  return await resp.json();
@@ -2465,9 +2470,25 @@ function SegmentedProgress(props) {
2465
2470
  }
2466
2471
  );
2467
2472
  }
2473
+ function getSizePreset(size, themeMdFontSize) {
2474
+ switch (size) {
2475
+ case "xs":
2476
+ return { iconFontSize: "1.2vh", iconPadding: "xxs", titleSize: "xxs", titleLineHeight: "1.2vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
2477
+ case "sm":
2478
+ return { iconFontSize: "1.6vh", iconPadding: "xxs", titleSize: "xs", titleLineHeight: "1.6vh", descriptionSize: "xxs", innerGap: "xs", bottomPad: "xs" };
2479
+ case "lg":
2480
+ return { iconFontSize: "2.6vh", iconPadding: "sm", titleSize: "md", titleLineHeight: "2.6vh", descriptionSize: "sm", innerGap: "sm", bottomPad: "sm" };
2481
+ case "xl":
2482
+ return { iconFontSize: "3.2vh", iconPadding: "sm", titleSize: "lg", titleLineHeight: "3.2vh", descriptionSize: "md", innerGap: "md", bottomPad: "md" };
2483
+ case "md":
2484
+ default:
2485
+ return { iconFontSize: themeMdFontSize, iconPadding: "xs", titleSize: "sm", titleLineHeight: themeMdFontSize, descriptionSize: "xs", innerGap: "sm", bottomPad: "sm" };
2486
+ }
2487
+ }
2468
2488
  function Title(props) {
2469
2489
  const game = useSettings((state) => state.game);
2470
2490
  const theme = useMantineTheme();
2491
+ const preset = getSizePreset(props.size ?? "md", theme.fontSizes.md);
2471
2492
  return /* @__PURE__ */ jsx(
2472
2493
  Flex,
2473
2494
  {
@@ -2476,71 +2497,50 @@ function Title(props) {
2476
2497
  gap: "xs",
2477
2498
  w: props.w || "100%",
2478
2499
  p: props.p || "unset",
2479
- pb: !props.p ? "sm" : props.p,
2500
+ pb: !props.p ? preset.bottomPad : props.p,
2480
2501
  style: {
2481
2502
  userSelect: "none",
2482
2503
  borderBottom: props.removeBorder ? "none" : `0.3vh solid ${props.borderColor || colorWithAlpha(theme.colors[theme.primaryColor][9], 0.5)}`
2483
2504
  },
2484
- children: /* @__PURE__ */ jsxs(
2485
- Flex,
2486
- {
2487
- align: "center",
2488
- justify: "center",
2489
- children: [
2490
- /* @__PURE__ */ jsxs(
2491
- Flex,
2505
+ children: /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "center", children: [
2506
+ /* @__PURE__ */ jsxs(Flex, { align: "center", gap: preset.innerGap, pr: "xs", children: [
2507
+ /* @__PURE__ */ jsx(
2508
+ BorderedIcon,
2509
+ {
2510
+ icon: props.icon,
2511
+ fontSize: preset.iconFontSize,
2512
+ color: props.iconColor,
2513
+ p: preset.iconPadding
2514
+ }
2515
+ ),
2516
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "0.25vh", children: [
2517
+ /* @__PURE__ */ jsx(
2518
+ Text,
2492
2519
  {
2493
- align: "center",
2494
- gap: "sm",
2495
- pr: "xs",
2496
- children: [
2497
- /* @__PURE__ */ jsx(
2498
- BorderedIcon,
2499
- {
2500
- icon: props.icon,
2501
- fontSize: theme.fontSizes.md,
2502
- color: props.iconColor
2503
- }
2504
- ),
2505
- /* @__PURE__ */ jsxs(
2506
- Flex,
2507
- {
2508
- direction: "column",
2509
- gap: "0.25vh",
2510
- children: [
2511
- /* @__PURE__ */ jsx(Text, { p: "0", size: "sm", style: {
2512
- lineHeight: theme.fontSizes.md,
2513
- fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
2514
- letterSpacing: "0.05em",
2515
- textTransform: "uppercase"
2516
- }, children: props.title }),
2517
- /* @__PURE__ */ jsx(
2518
- Text,
2519
- {
2520
- size: "xs",
2521
- c: "grey",
2522
- style: { whiteSpace: "normal", wordWrap: "break-word" },
2523
- children: props.description
2524
- }
2525
- )
2526
- ]
2527
- }
2528
- )
2529
- ]
2520
+ p: "0",
2521
+ size: preset.titleSize,
2522
+ style: {
2523
+ lineHeight: preset.titleLineHeight,
2524
+ fontFamily: game == "fivem" ? "Akrobat Bold" : "Red Dead",
2525
+ letterSpacing: "0.05em",
2526
+ textTransform: "uppercase"
2527
+ },
2528
+ children: props.title
2530
2529
  }
2531
2530
  ),
2532
2531
  /* @__PURE__ */ jsx(
2533
- Flex,
2532
+ Text,
2534
2533
  {
2535
- ml: "auto",
2536
- align: "center",
2537
- gap: "xs",
2538
- children: props.rightSection
2534
+ size: preset.descriptionSize,
2535
+ c: "grey",
2536
+ style: { whiteSpace: "normal", wordWrap: "break-word" },
2537
+ children: props.description
2539
2538
  }
2540
2539
  )
2541
- ]
2542
- }
2543
- )
2540
+ ] })
2541
+ ] }),
2542
+ /* @__PURE__ */ jsx(Flex, { ml: "auto", align: "center", gap: "xs", children: props.rightSection })
2543
+ ] })
2544
2544
  }
2545
2545
  );
2546
2546
  }
@@ -3707,6 +3707,13 @@ function useForm() {
3707
3707
  }, [state.values, state.initialValues]);
3708
3708
  return { ...state, changedFields, changedCount: changedFields.length };
3709
3709
  }
3710
+ function useFormField(path) {
3711
+ const store = useContext(FormContext);
3712
+ if (!store) {
3713
+ throw new Error("useFormField must be used inside <FormProvider>");
3714
+ }
3715
+ return useStore(store, (s) => getNested(s.values, path));
3716
+ }
3710
3717
  function useFormActions() {
3711
3718
  const store = useContext(FormContext);
3712
3719
  if (!store) {
@@ -4093,7 +4100,22 @@ function ConfigPanelInner({
4093
4100
  if (result?.success) {
4094
4101
  const { store } = getScriptConfigInstance();
4095
4102
  form.reinitialize(cloneConfig(store.getState()));
4103
+ notifications.show({
4104
+ color: "green",
4105
+ title: locale("ConfigResetSuccessTitle"),
4106
+ message: locale("ConfigResetSuccessBody"),
4107
+ autoClose: 3e3
4108
+ });
4109
+ return;
4096
4110
  }
4111
+ const err = result?._error || "Unknown";
4112
+ console.warn(`[ConfigPanel] config reset failed: ${err}`);
4113
+ notifications.show({
4114
+ color: "red",
4115
+ title: locale("ConfigResetFailedTitle"),
4116
+ message: locale("ConfigResetFailedBody", err),
4117
+ autoClose: 6e3
4118
+ });
4097
4119
  },
4098
4120
  onClose: () => setResetOpen(false),
4099
4121
  zIndex: 300
@@ -4161,8 +4183,8 @@ function ConfigPanelInner({
4161
4183
  children: /* @__PURE__ */ jsx(ArrowLeft, { size: "1.4vh", color })
4162
4184
  }
4163
4185
  ),
4164
- /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { minWidth: 0, lineHeight: 1 }, children: [
4165
- /* @__PURE__ */ jsx(Text, { size: "lg", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4186
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { minWidth: 0, flex: 1, lineHeight: 1, overflow: "hidden" }, children: [
4187
+ /* @__PURE__ */ jsx(Text, { size: "md", ff: "Akrobat Bold", tt: "uppercase", lts: "0.04em", truncate: true, children: title }),
4166
4188
  subtitle && /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.08em", c: color, truncate: true, children: subtitle })
4167
4189
  ] })
4168
4190
  ] }),
@@ -5103,6 +5125,17 @@ function AdminPageTitle(props) {
5103
5125
  /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", tt: "uppercase", lts: "0.1em", size: "sm", c: "rgba(255,255,255,0.6)", children: locale(props.title) })
5104
5126
  ] });
5105
5127
  }
5128
+ var placementStyle = (placement) => {
5129
+ switch (placement) {
5130
+ case "top-center":
5131
+ return { top: "1vh", left: "50%", transform: "translateX(-50%)" };
5132
+ case "top-right":
5133
+ return { top: "1vh", right: "1vh" };
5134
+ case "top-left":
5135
+ default:
5136
+ return { top: "1vh", left: "1vh" };
5137
+ }
5138
+ };
5106
5139
  var loadPersistedState = (storageKey) => {
5107
5140
  try {
5108
5141
  const raw = localStorage.getItem(storageKey);
@@ -5121,7 +5154,8 @@ function TestBed({
5121
5154
  items,
5122
5155
  storageKey = "testbed:open-state",
5123
5156
  disablePersistence = false,
5124
- title = "TestBed"
5157
+ title = "TestBed",
5158
+ placement = "top-left"
5125
5159
  }) {
5126
5160
  const [open, setOpen] = useState(false);
5127
5161
  const itemsRef = useRef(items);
@@ -5151,8 +5185,7 @@ function TestBed({
5151
5185
  {
5152
5186
  style: {
5153
5187
  position: "fixed",
5154
- top: "1vh",
5155
- left: "1vh",
5188
+ ...placementStyle(placement),
5156
5189
  zIndex: 2147483647,
5157
5190
  pointerEvents: "auto",
5158
5191
  fontSize: "1.4vh"
@@ -5257,7 +5290,282 @@ function TestBed({
5257
5290
  }
5258
5291
  );
5259
5292
  }
5293
+ var MANTINE_COLOR_OPTIONS = [
5294
+ "dirk",
5295
+ "red",
5296
+ "pink",
5297
+ "grape",
5298
+ "violet",
5299
+ "indigo",
5300
+ "blue",
5301
+ "cyan",
5302
+ "teal",
5303
+ "green",
5304
+ "lime",
5305
+ "yellow",
5306
+ "orange"
5307
+ ].map((value) => ({ value, label: value }));
5308
+ var DEFAULT_PALETTE = [
5309
+ "#f0f4ff",
5310
+ "#d9e3ff",
5311
+ "#bfcfff",
5312
+ "#a6bbff",
5313
+ "#8ca7ff",
5314
+ "#7393ff",
5315
+ "#5a7fff",
5316
+ "#406bff",
5317
+ "#2547ff",
5318
+ "#0b33ff"
5319
+ ];
5320
+ var DEFAULT_VALUE = {
5321
+ useOverride: false,
5322
+ primaryColor: "dirk",
5323
+ primaryShade: 5,
5324
+ customTheme: DEFAULT_PALETTE
5325
+ };
5326
+ function GroupLabel({ label }) {
5327
+ return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: "xs", mt: "xxs", children: [
5328
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", tt: "uppercase", lts: "0.07em", c: "rgba(255,255,255,0.2)", children: label }),
5329
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: "0.05vh", background: "rgba(255,255,255,0.06)" } })
5330
+ ] });
5331
+ }
5332
+ function ThemeOverrideSection({
5333
+ schemaKey = "theme",
5334
+ title
5335
+ }) {
5336
+ const mantineTheme = useMantineTheme();
5337
+ const color = mantineTheme.colors[mantineTheme.primaryColor][5];
5338
+ const raw = useFormField(schemaKey);
5339
+ const value = {
5340
+ useOverride: raw?.useOverride ?? DEFAULT_VALUE.useOverride,
5341
+ primaryColor: raw?.primaryColor ?? DEFAULT_VALUE.primaryColor,
5342
+ primaryShade: raw?.primaryShade ?? DEFAULT_VALUE.primaryShade,
5343
+ customTheme: Array.isArray(raw?.customTheme) && raw.customTheme.length === 10 ? raw.customTheme : DEFAULT_VALUE.customTheme
5344
+ };
5345
+ const { setValue } = useFormActions();
5346
+ const set = (key, val) => setValue(schemaKey, { ...value, [key]: val });
5347
+ const useCustom = value.primaryColor === "custom";
5348
+ const editable = value.useOverride;
5349
+ const setSwatch = (index, hex) => {
5350
+ const next = [...value.customTheme];
5351
+ next[index] = hex;
5352
+ set("customTheme", next);
5353
+ };
5354
+ const generateFromBase = (hex) => {
5355
+ try {
5356
+ const generated = generateColors(hex);
5357
+ set("customTheme", generated);
5358
+ } catch {
5359
+ }
5360
+ };
5361
+ const resetPalette = () => set("customTheme", DEFAULT_PALETTE);
5362
+ return /* @__PURE__ */ jsxs(
5363
+ Flex,
5364
+ {
5365
+ direction: "column",
5366
+ gap: "xs",
5367
+ p: "sm",
5368
+ style: { flex: 1, minHeight: 0, overflowY: "auto" },
5369
+ children: [
5370
+ /* @__PURE__ */ jsx(
5371
+ AdminPageTitle,
5372
+ {
5373
+ icon: Palette,
5374
+ title: title || locale("Theme") || "Theme",
5375
+ color
5376
+ }
5377
+ ),
5378
+ /* @__PURE__ */ jsxs(
5379
+ Flex,
5380
+ {
5381
+ align: "center",
5382
+ justify: "space-between",
5383
+ p: "xs",
5384
+ style: {
5385
+ background: `rgba(255,255,255,${editable ? 0.04 : 0.02})`,
5386
+ border: `0.1vh solid ${editable ? color : "rgba(255,255,255,0.08)"}`,
5387
+ borderRadius: mantineTheme.radius.xs,
5388
+ transition: "background 0.15s, border-color 0.15s"
5389
+ },
5390
+ children: [
5391
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "xxs", style: { flex: 1, minWidth: 0 }, children: [
5392
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xs", c: "rgba(255,255,255,0.9)", children: locale("OverrideGlobalTheme") || "Override global theme" }),
5393
+ /* @__PURE__ */ jsx(Text, { ff: "Akrobat Bold", size: "xxs", c: "rgba(255,255,255,0.4)", children: locale("OverrideGlobalThemeDesc") || "When on, this resource uses its own primary colour and palette instead of dirk_lib's. Turn off to fall back to the global theme \u2014 your custom palette is kept." })
5394
+ ] }),
5395
+ /* @__PURE__ */ jsx(
5396
+ Switch,
5397
+ {
5398
+ size: "md",
5399
+ checked: value.useOverride,
5400
+ onChange: (e) => set("useOverride", e.currentTarget.checked)
5401
+ }
5402
+ )
5403
+ ]
5404
+ }
5405
+ ),
5406
+ /* @__PURE__ */ jsxs(
5407
+ "div",
5408
+ {
5409
+ style: {
5410
+ opacity: editable ? 1 : 0.4,
5411
+ pointerEvents: editable ? "auto" : "none",
5412
+ transition: "opacity 0.15s"
5413
+ },
5414
+ children: [
5415
+ /* @__PURE__ */ jsx(GroupLabel, { label: locale("PrimaryColor") || "Primary Colour" }),
5416
+ /* @__PURE__ */ jsx(
5417
+ Switch,
5418
+ {
5419
+ label: locale("UseCustomPalette") || "Use custom palette",
5420
+ size: "md",
5421
+ checked: useCustom,
5422
+ onChange: (e) => set("primaryColor", e.currentTarget.checked ? "custom" : "dirk"),
5423
+ styles: {
5424
+ label: {
5425
+ fontFamily: "Akrobat Bold",
5426
+ fontSize: "0.65em",
5427
+ letterSpacing: "0.06em",
5428
+ textTransform: "uppercase",
5429
+ color: "rgba(255,255,255,0.35)"
5430
+ }
5431
+ }
5432
+ }
5433
+ ),
5434
+ /* @__PURE__ */ jsxs(Flex, { gap: "xs", mt: "xs", children: [
5435
+ !useCustom && /* @__PURE__ */ jsx(
5436
+ Select,
5437
+ {
5438
+ label: locale("MantinePalette") || "Mantine palette",
5439
+ size: "xs",
5440
+ style: { flex: 1 },
5441
+ value: value.primaryColor,
5442
+ data: MANTINE_COLOR_OPTIONS,
5443
+ allowDeselect: false,
5444
+ onChange: (v) => v && set("primaryColor", v)
5445
+ }
5446
+ ),
5447
+ /* @__PURE__ */ jsx(
5448
+ NumberInput,
5449
+ {
5450
+ label: locale("Shade") || "Shade",
5451
+ size: "xs",
5452
+ style: { flex: 1 },
5453
+ min: 0,
5454
+ max: 9,
5455
+ value: value.primaryShade,
5456
+ onChange: (v) => set("primaryShade", Number(v))
5457
+ }
5458
+ )
5459
+ ] }),
5460
+ useCustom && /* @__PURE__ */ jsxs(Fragment, { children: [
5461
+ /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", mt: "sm", children: [
5462
+ /* @__PURE__ */ jsx(
5463
+ Text,
5464
+ {
5465
+ ff: "Akrobat Bold",
5466
+ size: "xxs",
5467
+ tt: "uppercase",
5468
+ lts: "0.07em",
5469
+ c: "rgba(255,255,255,0.2)",
5470
+ children: locale("CustomPalette") || "Custom palette"
5471
+ }
5472
+ ),
5473
+ /* @__PURE__ */ jsx(
5474
+ ActionIcon,
5475
+ {
5476
+ size: "sm",
5477
+ variant: "subtle",
5478
+ onClick: resetPalette,
5479
+ title: locale("ResetPalette") || "Reset palette",
5480
+ children: /* @__PURE__ */ jsx(RotateCcw, { size: "1.4vh" })
5481
+ }
5482
+ )
5483
+ ] }),
5484
+ /* @__PURE__ */ jsx(
5485
+ ColorInput,
5486
+ {
5487
+ label: locale("BaseColor") || "Base colour",
5488
+ size: "xs",
5489
+ value: value.customTheme[value.primaryShade] ?? value.customTheme[5] ?? "#000000",
5490
+ onChange: generateFromBase,
5491
+ eyeDropperIcon: /* @__PURE__ */ jsx(Fragment, {})
5492
+ }
5493
+ ),
5494
+ /* @__PURE__ */ jsx(Flex, { gap: "xxs", mt: "xxs", children: value.customTheme.map((swatch, i) => /* @__PURE__ */ jsx(
5495
+ SwatchTile,
5496
+ {
5497
+ index: i,
5498
+ value: swatch,
5499
+ isPrimary: i === value.primaryShade,
5500
+ onChange: (v) => setSwatch(i, v)
5501
+ },
5502
+ i
5503
+ )) })
5504
+ ] })
5505
+ ]
5506
+ }
5507
+ )
5508
+ ]
5509
+ }
5510
+ );
5511
+ }
5512
+ function SwatchTile({
5513
+ index,
5514
+ value,
5515
+ isPrimary,
5516
+ onChange
5517
+ }) {
5518
+ const [opened, setOpened] = useState(false);
5519
+ return /* @__PURE__ */ jsxs(Popover, { opened, onChange: setOpened, position: "bottom", withArrow: true, zIndex: 1e4, children: [
5520
+ /* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(
5521
+ "button",
5522
+ {
5523
+ onClick: () => setOpened((o) => !o),
5524
+ title: `${index} \xB7 ${value}`,
5525
+ style: {
5526
+ flex: 1,
5527
+ aspectRatio: "1 / 1",
5528
+ background: value,
5529
+ border: isPrimary ? "0.2vh solid rgba(255,255,255,0.85)" : "0.1vh solid rgba(255,255,255,0.15)",
5530
+ borderRadius: "0.4vh",
5531
+ cursor: "pointer",
5532
+ padding: 0,
5533
+ display: "flex",
5534
+ alignItems: "flex-end",
5535
+ justifyContent: "flex-end",
5536
+ position: "relative"
5537
+ },
5538
+ children: /* @__PURE__ */ jsx(
5539
+ "span",
5540
+ {
5541
+ style: {
5542
+ fontFamily: "Akrobat Bold",
5543
+ fontSize: "0.9vh",
5544
+ lineHeight: 1,
5545
+ padding: "0.2vh 0.3vh",
5546
+ color: "rgba(0,0,0,0.55)",
5547
+ background: "rgba(255,255,255,0.55)",
5548
+ borderRadius: "0.25vh",
5549
+ margin: "0.2vh"
5550
+ },
5551
+ children: index
5552
+ }
5553
+ )
5554
+ }
5555
+ ) }),
5556
+ /* @__PURE__ */ jsx(Popover.Dropdown, { p: "xs", children: /* @__PURE__ */ jsx(
5557
+ ColorInput,
5558
+ {
5559
+ size: "xs",
5560
+ value,
5561
+ onChange,
5562
+ format: "hex",
5563
+ eyeDropperIcon: /* @__PURE__ */ jsx(Fragment, {})
5564
+ }
5565
+ ) })
5566
+ ] });
5567
+ }
5260
5568
 
5261
- export { AdminPageTitle, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, FiveMKeyBindInput, FloatingParticles, GroupName, GroupRank, GroupSelect, InfoBox, InputContainer, LevelBanner, LevelPanel, MissingItemsBanner, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PositionPicker, PromptModal, SegmentedControl, SegmentedProgress, SelectItem, TestBed, Title, Vector4DeleteButton, Vector4Display, WorldPositionGotoButton, WorldPositionSetButton, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore };
5569
+ export { AdminPageTitle, AsyncSaveButton, BlipColorSelect, BlipDisplaySelect, BlipIconSelect, BorderedIcon, ConfigPanel, ConfirmModal, ControlMultiSelect, ControlSelect, Counter, FiveMKeyBindInput, FloatingParticles, GroupName, GroupRank, GroupSelect, InfoBox, InputContainer, LevelBanner, LevelPanel, MissingItemsBanner, Modal, ModalContext, ModalProvider, MotionFlex, MotionIcon, MotionImage, MotionText, NavBar, NavigationContext, NavigationProvider, PositionPicker, PromptModal, SegmentedControl, SegmentedProgress, SelectItem, TestBed, ThemeOverrideSection, Title, Vector4DeleteButton, Vector4Display, WorldPositionGotoButton, WorldPositionSetButton, useMissingItemsAudit, useModal, useModalActions, useNavigation, useNavigationStore };
5262
5570
  //# sourceMappingURL=index.js.map
5263
5571
  //# sourceMappingURL=index.js.map