astro-tractstack 2.0.0-rc.9 → 2.0.1

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 (142) hide show
  1. package/LICENSE +8 -97
  2. package/README.md +7 -5
  3. package/bin/create-tractstack.js +31 -8
  4. package/dist/index.js +106 -29
  5. package/package.json +10 -5
  6. package/templates/css/frontend.css +1 -1
  7. package/templates/custom/minimal/CodeHook.astro +13 -12
  8. package/templates/custom/minimal/CustomRoutes.astro +25 -31
  9. package/templates/custom/with-examples/CodeHook.astro +22 -11
  10. package/templates/custom/with-examples/CustomRoutes.astro +4 -8
  11. package/templates/custom/with-examples/ProductCard.astro +29 -0
  12. package/templates/custom/with-examples/ProductCardWrapper.astro +43 -0
  13. package/templates/custom/with-examples/ProductGrid.astro +64 -0
  14. package/templates/custom/with-examples/pages/Collections.astro +58 -98
  15. package/templates/gitignore +42 -0
  16. package/templates/prettierignore +5 -0
  17. package/templates/prettierrc +19 -0
  18. package/templates/src/client/app.js +127 -0
  19. package/templates/src/client/htmx.min.js +3519 -0
  20. package/templates/src/client/view.js +429 -0
  21. package/templates/src/components/Footer.astro +4 -9
  22. package/templates/src/components/Header.astro +67 -60
  23. package/templates/src/components/Menu.tsx +188 -52
  24. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
  25. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +9 -13
  26. package/templates/src/components/codehooks/EpinetTableView.tsx +11 -7
  27. package/templates/src/components/codehooks/EpinetWrapper.tsx +10 -9
  28. package/templates/src/components/codehooks/FeaturedArticle.astro +105 -0
  29. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +318 -0
  30. package/templates/src/components/codehooks/ListContent.astro +32 -162
  31. package/templates/src/components/codehooks/ListContentSetup.tsx +43 -138
  32. package/templates/src/components/codehooks/ProductCardSetup.tsx +152 -0
  33. package/templates/src/components/codehooks/ProductGridSetup.tsx +274 -0
  34. package/templates/src/components/codehooks/SearchWidget.tsx +453 -0
  35. package/templates/src/components/compositor/Node.tsx +3 -6
  36. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +21 -11
  37. package/templates/src/components/compositor/elements/BunnyVideo.tsx +21 -20
  38. package/templates/src/components/compositor/nodes/Pane.tsx +51 -21
  39. package/templates/src/components/compositor/nodes/RenderChildren.tsx +6 -1
  40. package/templates/src/components/compositor/nodes/Widget.tsx +16 -2
  41. package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +155 -0
  42. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +20 -1
  43. package/templates/src/components/edit/Header.tsx +10 -4
  44. package/templates/src/components/edit/PanelSwitch.tsx +11 -7
  45. package/templates/src/components/edit/SettingsPanel.tsx +29 -18
  46. package/templates/src/components/edit/ToolBar.tsx +1 -28
  47. package/templates/src/components/edit/ToolMode.tsx +45 -32
  48. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
  49. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +8 -2
  50. package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +1 -1
  51. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +17 -27
  52. package/templates/src/components/edit/pane/PageGenSelector.tsx +16 -16
  53. package/templates/src/components/edit/pane/PageGenSpecial.tsx +26 -49
  54. package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
  55. package/templates/src/components/edit/pane/PanePanel_path.tsx +2 -4
  56. package/templates/src/components/edit/pane/PanePanel_title.tsx +243 -76
  57. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
  58. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +48 -37
  59. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +60 -55
  60. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +56 -50
  61. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +54 -47
  62. package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +54 -44
  63. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +113 -138
  64. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +54 -40
  65. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +3 -3
  66. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +56 -49
  67. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +14 -5
  68. package/templates/src/components/edit/state/SaveModal.tsx +316 -169
  69. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +1 -1
  70. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +56 -55
  71. package/templates/src/components/edit/widgets/BunnyWidget.tsx +538 -59
  72. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +656 -0
  73. package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
  74. package/templates/src/components/fields/ArtpackImage.tsx +4 -1
  75. package/templates/src/components/fields/BackgroundImage.tsx +1 -1
  76. package/templates/src/components/fields/BackgroundImageWrapper.tsx +127 -35
  77. package/templates/src/components/fields/ColorPickerCombo.tsx +66 -62
  78. package/templates/src/components/fields/ImageUpload.tsx +1 -1
  79. package/templates/src/components/fields/ViewportComboBox.tsx +59 -42
  80. package/templates/src/components/form/ActionBuilderBeliefSelector.tsx +117 -0
  81. package/templates/src/components/form/ActionBuilderField.tsx +306 -87
  82. package/templates/src/components/search/SearchModal.tsx +420 -0
  83. package/templates/src/components/search/SearchResults.tsx +367 -0
  84. package/templates/src/components/search/SearchWrapper.tsx +46 -0
  85. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -1
  86. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +34 -8
  87. package/templates/src/components/storykeep/Dashboard_Branding.tsx +1 -1
  88. package/templates/src/components/storykeep/Dashboard_Content.tsx +6 -0
  89. package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
  90. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +38 -34
  91. package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +1 -1
  92. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +56 -8
  93. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +18 -3
  94. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +5 -8
  95. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +274 -228
  96. package/templates/src/components/storykeep/widgets/Wizard.tsx +14 -7
  97. package/templates/src/components/widgets/ImpressionWrapper.tsx +0 -1
  98. package/templates/src/constants/shapes.ts +9 -0
  99. package/templates/src/constants.ts +2121 -16
  100. package/templates/src/hooks/useSearch.ts +228 -0
  101. package/templates/src/layouts/Layout.astro +213 -104
  102. package/templates/src/lib/storyData.ts +4 -1
  103. package/templates/src/pages/[...slug]/edit.astro +14 -14
  104. package/templates/src/pages/[...slug].astro +82 -21
  105. package/templates/src/pages/api/orphan-analysis.ts +0 -1
  106. package/templates/src/pages/api/tailwind.ts +23 -21
  107. package/templates/src/pages/context/[...contextSlug]/edit.astro +14 -14
  108. package/templates/src/pages/context/[...contextSlug].astro +7 -2
  109. package/templates/src/pages/storykeep/advanced.astro +5 -4
  110. package/templates/src/pages/storykeep/branding.astro +5 -4
  111. package/templates/src/pages/storykeep/content.astro +5 -4
  112. package/templates/src/pages/storykeep/init.astro +40 -1
  113. package/templates/src/pages/storykeep/login.astro +1 -1
  114. package/templates/src/pages/storykeep.astro +5 -4
  115. package/templates/src/stores/nodes.ts +59 -88
  116. package/templates/src/stores/orphanAnalysis.ts +19 -21
  117. package/templates/src/stores/storykeep.ts +7 -0
  118. package/templates/src/types/compositorTypes.ts +6 -0
  119. package/templates/src/types/tractstack.ts +17 -0
  120. package/templates/src/utils/actions/lispLexer.ts +2 -2
  121. package/templates/src/utils/actions/preParse_Action.ts +3 -0
  122. package/templates/src/utils/api/beliefHelpers.ts +12 -36
  123. package/templates/src/utils/api/menuHelpers.ts +2 -2
  124. package/templates/src/utils/api.ts +26 -0
  125. package/templates/src/utils/compositor/TemplateNodes.ts +7 -0
  126. package/templates/src/utils/compositor/allowInsert.ts +5 -3
  127. package/templates/src/utils/compositor/nodesHelper.ts +4 -0
  128. package/templates/src/utils/compositor/processMarkdown.ts +16 -2
  129. package/templates/src/utils/compositor/reduceNodesClassNames.ts +4 -0
  130. package/templates/src/utils/compositor/templateMarkdownStyles.ts +13 -13
  131. package/templates/src/utils/compositor/typeGuards.ts +1 -0
  132. package/templates/src/utils/customHelpers.ts +38 -0
  133. package/templates/src/utils/helpers.ts +2 -2
  134. package/templates/src/utils/layout.ts +65 -144
  135. package/utils/inject-files.ts +95 -18
  136. package/templates/src/client/analytics-events.js +0 -207
  137. package/templates/src/client/belief-events.js +0 -191
  138. package/templates/src/client/sse.js +0 -613
  139. package/templates/src/components/codehooks/FeaturedContent.astro +0 -273
  140. package/templates/src/components/codehooks/FeaturedContentSetup.tsx +0 -738
  141. package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +0 -128
  142. package/templates/src/components/edit/pane/PanePanel_slug.tsx +0 -219
@@ -12,28 +12,27 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
12
12
  const [beliefs, setBeliefs] = useState<BeliefNode[]>([]);
13
13
  const [selectedBeliefTag, setSelectedBeliefTag] = useState<string>('');
14
14
  const [currentPrompt, setCurrentPrompt] = useState<string>('');
15
+ const [currentScale, setCurrentScale] = useState<string>('');
15
16
  const [isInitialized, setIsInitialized] = useState(false);
16
17
 
17
- // Get parameter metadata from the widgetMeta constant
18
18
  const widgetInfo = widgetMeta.toggle;
19
19
 
20
20
  const params = node.codeHookParams || [];
21
21
  const beliefTag = String(params[0] || '');
22
22
  const prompt = String(params[1] || '');
23
+ const scale = String(params[2] || '');
23
24
 
24
- // Check if beliefTag is the placeholder value
25
25
  const isPlaceholder = beliefTag === 'BeliefTag';
26
26
 
27
- // Update local state when props change
28
27
  useEffect(() => {
29
28
  if (!isPlaceholder && beliefTag) {
30
29
  setSelectedBeliefTag(beliefTag);
31
30
  }
32
31
  setCurrentPrompt(prompt);
32
+ setCurrentScale(scale);
33
33
  setIsInitialized(true);
34
- }, [beliefTag, prompt, isPlaceholder]);
34
+ }, [beliefTag, prompt, scale, isPlaceholder]);
35
35
 
36
- // Fetch beliefs using new Go backend pattern
37
36
  useEffect(() => {
38
37
  const fetchData = async () => {
39
38
  try {
@@ -41,7 +40,6 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
41
40
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
42
41
  const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
43
42
 
44
- // Step 1: Get all belief IDs
45
43
  const idsResponse = await fetch(`${goBackend}/api/v1/nodes/beliefs`, {
46
44
  headers: {
47
45
  'X-Tenant-ID': tenantId,
@@ -60,7 +58,6 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
60
58
  return;
61
59
  }
62
60
 
63
- // Step 2: Get belief data by IDs
64
61
  const beliefsResponse = await fetch(
65
62
  `${goBackend}/api/v1/nodes/beliefs`,
66
63
  {
@@ -92,34 +89,30 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
92
89
  const handleBeliefChange = (selectedValue: string) => {
93
90
  if (!isInitialized) return;
94
91
  setSelectedBeliefTag(selectedValue);
95
- onUpdate([selectedValue, currentPrompt]);
92
+ const selectedBelief = beliefs.find((b) => b.slug === selectedValue);
93
+ const newScale = selectedBelief ? selectedBelief.scale || '' : '';
94
+ setCurrentScale(newScale);
95
+ onUpdate([selectedValue, currentPrompt, newScale]);
96
96
  };
97
97
 
98
98
  const handlePromptChange = (value: string) => {
99
99
  if (!isInitialized) return;
100
- // Sanitize the input value (remove newlines and pipe characters)
101
100
  const sanitizedValue = value.replace(/[\n\r|]/g, '');
102
101
  setCurrentPrompt(sanitizedValue);
103
-
104
- // Use the actual selected tag (from state) or the original belief tag as fallback
105
102
  const tagToUse = selectedBeliefTag || (isPlaceholder ? '' : beliefTag);
106
- onUpdate([tagToUse, sanitizedValue]);
103
+ onUpdate([tagToUse, sanitizedValue, currentScale]);
107
104
  };
108
105
 
109
- // Show beliefs that can be selected for the toggle
110
106
  const filteredBeliefs = beliefs.filter(
111
107
  (b) => b.scale === 'yn' || b.scale === 'tf'
112
108
  );
113
109
 
114
- // Find the selected belief (if any)
115
110
  const selectedBelief = beliefs.find(
116
111
  (b) => b.slug === (selectedBeliefTag || (isPlaceholder ? '' : beliefTag))
117
112
  );
118
113
 
119
- // Determine if we have a real selection - either from state or props
120
114
  const hasRealSelection = !!selectedBelief || (!isPlaceholder && !!beliefTag);
121
115
 
122
- // Calculate the current value to show in the select dropdown
123
116
  const selectValue = selectedBeliefTag || (isPlaceholder ? '' : beliefTag);
124
117
 
125
118
  return (
@@ -351,7 +351,10 @@ const ArtpackImage = ({ paneId, onUpdate }: ArtpackImageProps) => {
351
351
  className="fixed inset-0 flex items-center justify-center p-4"
352
352
  style={{ zIndex: 10010 }}
353
353
  >
354
- <Dialog.Content className="dialog-content">
354
+ <Dialog.Content
355
+ className="dialog-content overflow-y-auto"
356
+ style={{ maxHeight: '80vh' }}
357
+ >
355
358
  <Dialog.Title className="mb-4 text-lg font-bold">
356
359
  Select Artpack Image
357
360
  </Dialog.Title>
@@ -504,7 +504,7 @@ const BackgroundImage = ({ paneId, onUpdate }: BackgroundImageProps) => {
504
504
  />
505
505
  </div>
506
506
  <div className="flex-1">
507
- <div className="font-medium">
507
+ <div className="font-bold">
508
508
  {file.altDescription || file.filename}
509
509
  </div>
510
510
  {file.altDescription && (
@@ -1,10 +1,12 @@
1
- import { useState, useCallback } from 'react';
1
+ import { useState, useCallback, useMemo } from 'react';
2
2
  import { useStore } from '@nanostores/react';
3
+ import { Select } from '@ark-ui/react/select';
4
+ import { createListCollection } from '@ark-ui/react/collection';
3
5
  import BackgroundImage from './BackgroundImage';
4
6
  import ArtpackImage from './ArtpackImage';
5
7
  import ColorPickerCombo from './ColorPickerCombo';
6
8
  import { getCtx } from '@/stores/nodes';
7
- import { hasArtpacksStore } from '@/stores/storykeep';
9
+ import { hasArtpacksStore, settingsPanelStore } from '@/stores/storykeep';
8
10
  import { cloneDeep } from '@/utils/helpers';
9
11
  import type { BrandConfig } from '@/types/tractstack';
10
12
  import type {
@@ -19,6 +21,36 @@ export interface BackgroundImageWrapperProps {
19
21
  config?: BrandConfig;
20
22
  }
21
23
 
24
+ const CheckIcon = () => (
25
+ <svg
26
+ xmlns="http://www.w3.org/2000/svg"
27
+ className="h-5 w-5"
28
+ viewBox="0 0 20 20"
29
+ fill="currentColor"
30
+ >
31
+ <path
32
+ fillRule="evenodd"
33
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
34
+ clipRule="evenodd"
35
+ />
36
+ </svg>
37
+ );
38
+
39
+ const ChevronDownIcon = () => (
40
+ <svg
41
+ xmlns="http://www.w3.org/2000/svg"
42
+ className="h-5 w-5"
43
+ viewBox="0 0 20 20"
44
+ fill="currentColor"
45
+ >
46
+ <path
47
+ fillRule="evenodd"
48
+ d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
49
+ clipRule="evenodd"
50
+ />
51
+ </svg>
52
+ );
53
+
22
54
  const BackgroundImageWrapper = ({
23
55
  paneId,
24
56
  config,
@@ -27,11 +59,8 @@ const BackgroundImageWrapper = ({
27
59
  const allNodes = useStore(ctx.allNodes);
28
60
  const $artpacks = useStore(hasArtpacksStore);
29
61
  const hasArtpacks = $artpacks && Object.keys($artpacks).length > 0;
30
-
31
- // State to force re-renders when child components need it
32
62
  const [, setUpdateCounter] = useState(0);
33
63
 
34
- // Using useCallback to create a stable reference to the update function
35
64
  const onUpdate = useCallback(() => {
36
65
  setUpdateCounter((prev) => prev + 1);
37
66
  }, []);
@@ -89,8 +118,47 @@ const BackgroundImageWrapper = ({
89
118
  const position = bgNode?.position || 'background';
90
119
  const size = bgNode?.size || 'equal';
91
120
 
121
+ const positionOptions = [
122
+ { label: 'Background', value: 'background' },
123
+ { label: 'Left', value: 'left' },
124
+ { label: 'Right', value: 'right' },
125
+ { label: 'Left Bleed', value: 'leftBleed' },
126
+ { label: 'Right Bleed', value: 'rightBleed' },
127
+ ];
128
+
129
+ const collection = useMemo(
130
+ () =>
131
+ createListCollection({
132
+ items: positionOptions,
133
+ itemToValue: (item) => item.value,
134
+ itemToString: (item) => item.label,
135
+ }),
136
+ []
137
+ );
138
+
139
+ const selectItemStyles = `
140
+ .position-item[data-highlighted] {
141
+ background-color: #0891b2; /* bg-cyan-600 */
142
+ color: white;
143
+ }
144
+ .position-item[data-highlighted] .position-indicator {
145
+ color: white;
146
+ }
147
+ .position-item[data-state="checked"] .position-indicator {
148
+ display: flex;
149
+ }
150
+ .position-item .position-indicator {
151
+ display: none;
152
+ }
153
+ .position-item[data-state="checked"] {
154
+ font-weight: bold;
155
+ }
156
+ `;
157
+
92
158
  return (
93
159
  <div className="w-full space-y-6">
160
+ <style>{selectItemStyles}</style>
161
+
94
162
  <h3 className="text-sm font-bold text-gray-700">Background</h3>
95
163
 
96
164
  <ColorPickerCombo
@@ -115,39 +183,64 @@ const BackgroundImageWrapper = ({
115
183
  )}
116
184
  {bgNode && (
117
185
  <div className="w-full space-y-6">
118
- {/* Position Toggle */}
119
186
  <div className="space-y-2">
120
- <label className="block text-sm font-bold text-gray-700">
121
- Position
122
- </label>
123
- <div className="flex space-x-4">
124
- {(
125
- [
126
- 'background',
127
- 'left',
128
- 'right',
129
- 'leftBleed',
130
- 'rightBleed',
131
- ] as const
132
- ).map((pos) => (
133
- <label key={pos} className="inline-flex items-center">
134
- <input
135
- type="radio"
136
- name="position"
137
- value={pos}
138
- checked={position === pos}
139
- onChange={() => handlePositionChange(pos)}
140
- className="text-myblue focus:ring-myblue h-4 w-4 border-gray-300"
187
+ <Select.Root
188
+ collection={collection}
189
+ positioning={{ sameWidth: true }}
190
+ value={[position]}
191
+ onValueChange={(details) => {
192
+ const currentSignal = settingsPanelStore.get();
193
+ if (currentSignal) {
194
+ settingsPanelStore.set({
195
+ ...currentSignal,
196
+ editLock: Date.now(),
197
+ });
198
+ }
199
+ handlePositionChange(
200
+ details.value[0] as
201
+ | 'background'
202
+ | 'left'
203
+ | 'right'
204
+ | 'leftBleed'
205
+ | 'rightBleed'
206
+ );
207
+ }}
208
+ >
209
+ <Select.Label className="block text-sm font-bold text-gray-700">
210
+ Position
211
+ </Select.Label>
212
+ <Select.Control>
213
+ <Select.Trigger className="focus:border-myblue focus:ring-myblue flex w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1">
214
+ <Select.ValueText
215
+ className="capitalize"
216
+ placeholder="Select a position"
141
217
  />
142
- <span className="ml-2 text-sm capitalize text-gray-700">
143
- {pos}
144
- </span>
145
- </label>
146
- ))}
147
- </div>
218
+ <Select.Indicator>
219
+ <ChevronDownIcon />
220
+ </Select.Indicator>
221
+ </Select.Trigger>
222
+ </Select.Control>
223
+ <Select.Positioner>
224
+ <Select.Content className="z-10 mt-1 w-full rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
225
+ <Select.ItemGroup>
226
+ {collection.items.map((item) => (
227
+ <Select.Item
228
+ key={item.value}
229
+ item={item}
230
+ className="position-item relative cursor-pointer select-none py-2 pl-10 pr-4 text-sm text-gray-900"
231
+ >
232
+ <Select.ItemText>{item.label}</Select.ItemText>
233
+ <Select.ItemIndicator className="position-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
234
+ <CheckIcon />
235
+ </Select.ItemIndicator>
236
+ </Select.Item>
237
+ ))}
238
+ </Select.ItemGroup>
239
+ </Select.Content>
240
+ </Select.Positioner>
241
+ </Select.Root>
148
242
  </div>
149
243
 
150
- {/* Size Toggle - Only show when position is left or right */}
151
244
  {position !== 'background' && (
152
245
  <div className="space-y-2">
153
246
  <label className="block text-sm font-bold text-gray-700">
@@ -177,7 +270,6 @@ const BackgroundImageWrapper = ({
177
270
  </div>
178
271
  )}
179
272
 
180
- {/* Render the appropriate image component */}
181
273
  {isArtpackImageNode(bgNode) ? (
182
274
  <ArtpackImage paneId={paneId} onUpdate={onUpdate} />
183
275
  ) : (
@@ -1,5 +1,6 @@
1
1
  import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
2
2
  import { Combobox } from '@ark-ui/react';
3
+ import { Portal } from '@ark-ui/react/portal';
3
4
  import { createListCollection } from '@ark-ui/react/collection';
4
5
  import ChevronUpDownIcon from '@heroicons/react/24/outline/ChevronUpDownIcon';
5
6
  import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
@@ -42,7 +43,7 @@ const ColorPickerCombo = ({
42
43
 
43
44
  // Add ref and useDropdownDirection hook
44
45
  const comboboxRef = useRef<HTMLDivElement>(null);
45
- const { openAbove, maxHeight } = useDropdownDirection(comboboxRef);
46
+ const { openAbove } = useDropdownDirection(comboboxRef);
46
47
 
47
48
  // Get all available Tailwind color options
48
49
  const allTailwindColorOptions = useMemo(() => {
@@ -59,19 +60,10 @@ const ColorPickerCombo = ({
59
60
  }, [allTailwindColorOptions, query]);
60
61
 
61
62
  // Create collection for combobox
62
- const collection = useMemo(() => {
63
- // Make sure all tailwind colors are included in the collection
64
- const items = [...allTailwindColorOptions];
65
-
66
- // Ensure the initial color is in the collection if it exists
67
- if (initialTailwindColor && !items.includes(initialTailwindColor)) {
68
- items.push(initialTailwindColor);
69
- }
70
-
71
- return createListCollection({
72
- items,
73
- });
74
- }, [allTailwindColorOptions, initialTailwindColor]);
63
+ const collection = useMemo(
64
+ () => createListCollection({ items: filteredColors }),
65
+ [filteredColors]
66
+ );
75
67
 
76
68
  // Set default value during initial render
77
69
  useEffect(() => {
@@ -223,56 +215,68 @@ const ColorPickerCombo = ({
223
215
  onValueChange={handleTailwindColorChange}
224
216
  onInputValueChange={handleInputChange}
225
217
  selectionBehavior="replace"
218
+ loopFocus={true}
219
+ openOnKeyPress={true}
220
+ positioning={{
221
+ placement: openAbove ? 'top' : 'bottom',
222
+ gutter: 4,
223
+ sameWidth: true,
224
+ }}
226
225
  >
227
- <div ref={comboboxRef} className="relative max-w-48">
228
- <Combobox.Input
229
- className="border-mydarkgrey focus:border-myblue focus:ring-myblue xs:text-sm w-full rounded-md py-2 pl-3 pr-10 shadow-sm"
230
- placeholder="Search Tailwind colors..."
231
- autoComplete="off"
232
- />
233
- <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
234
- <ChevronUpDownIcon
235
- className="text-mydarkgrey h-5 w-5"
236
- aria-hidden="true"
226
+ <Combobox.Control ref={comboboxRef}>
227
+ <div className="relative">
228
+ <Combobox.Input
229
+ className="border-mydarkgrey focus:border-myblue focus:ring-myblue xs:text-sm w-full max-w-xl rounded-md py-2 pl-3 pr-10 shadow-sm"
230
+ placeholder="Search Tailwind colors..."
231
+ autoComplete="off"
237
232
  />
238
- </Combobox.Trigger>
239
- <Combobox.Content
240
- className={`xs:text-sm absolute z-10 mt-1 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${
241
- openAbove ? 'bottom-full mb-1' : 'top-full'
242
- }`}
243
- style={{ maxHeight }}
244
- >
245
- {filteredColors.length === 0 ? (
246
- <div className="relative cursor-default select-none py-2 pl-3 pr-4 text-black">
247
- Nothing found.
248
- </div>
249
- ) : (
250
- filteredColors.map((color) => (
251
- <Combobox.Item
252
- key={color}
253
- item={color}
254
- className="color-item relative cursor-default select-none py-2 pl-3 pr-4"
255
- >
256
- <div className="flex items-center">
257
- <div
258
- className="mr-3 h-6 w-6 flex-shrink-0 rounded"
259
- style={{
260
- backgroundColor: tailwindToHex(
261
- `bg-${color}`,
262
- config.BRAND_COLOURS || null
263
- ),
264
- }}
265
- />
266
- <span className="block truncate">{color}</span>
267
- <span className="color-indicator absolute inset-y-0 right-0 flex items-center pr-3 text-cyan-600">
268
- <CheckIcon className="h-5 w-5" aria-hidden="true" />
269
- </span>
270
- </div>
271
- </Combobox.Item>
272
- ))
273
- )}
274
- </Combobox.Content>
275
- </div>
233
+ <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
234
+ <ChevronUpDownIcon
235
+ className="text-mydarkgrey h-5 w-5"
236
+ aria-hidden="true"
237
+ />
238
+ </Combobox.Trigger>
239
+ </div>
240
+ </Combobox.Control>
241
+
242
+ <Portal>
243
+ <Combobox.Positioner style={{ zIndex: 1002 }}>
244
+ <Combobox.Content className="xs:text-sm max-h-64 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
245
+ {filteredColors.length === 0 ? (
246
+ <div className="relative cursor-default select-none py-2 pl-3 pr-4 text-black">
247
+ Nothing found.
248
+ </div>
249
+ ) : (
250
+ filteredColors.map((color) => (
251
+ <Combobox.Item
252
+ key={color}
253
+ item={color}
254
+ className="color-item relative cursor-default select-none py-2 pl-3 pr-4"
255
+ >
256
+ <div className="flex items-center">
257
+ <div
258
+ className="mr-3 h-6 w-6 flex-shrink-0 rounded"
259
+ style={{
260
+ backgroundColor: tailwindToHex(
261
+ `bg-${color}`,
262
+ config.BRAND_COLOURS || null
263
+ ),
264
+ }}
265
+ />
266
+ <span className="block truncate">{color}</span>
267
+ <span className="color-indicator absolute inset-y-0 right-0 flex items-center pr-3 text-cyan-600">
268
+ <CheckIcon
269
+ className="h-5 w-5"
270
+ aria-hidden="true"
271
+ />
272
+ </span>
273
+ </div>
274
+ </Combobox.Item>
275
+ ))
276
+ )}
277
+ </Combobox.Content>
278
+ </Combobox.Positioner>
279
+ </Portal>
276
280
  </Combobox.Root>
277
281
  </div>
278
282
  )}
@@ -323,7 +323,7 @@ export const ImageUpload = ({
323
323
  {isSelectingFile && (
324
324
  <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
325
325
  <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-lg">
326
- <h3 className="text-mydarkgrey mb-4 text-lg font-medium">
326
+ <h3 className="text-mydarkgrey mb-4 text-lg font-bold">
327
327
  Select an Image
328
328
  </h3>
329
329
 
@@ -1,12 +1,11 @@
1
1
  import {
2
- useRef,
3
2
  useState,
4
3
  useEffect,
5
4
  useCallback,
6
5
  useMemo,
7
6
  type ChangeEvent,
8
7
  } from 'react';
9
- import { Combobox } from '@ark-ui/react';
8
+ import { Combobox, Portal } from '@ark-ui/react';
10
9
  import { createListCollection } from '@ark-ui/react/collection';
11
10
  import ChevronUpDownIcon from '@heroicons/react/20/solid/ChevronUpDownIcon';
12
11
  import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
@@ -14,6 +13,7 @@ import DevicePhoneMobileIcon from '@heroicons/react/24/outline/DevicePhoneMobile
14
13
  import DeviceTabletIcon from '@heroicons/react/24/outline/DeviceTabletIcon';
15
14
  import ComputerDesktopIcon from '@heroicons/react/24/outline/ComputerDesktopIcon';
16
15
  import { classNames } from '@/utils/helpers';
16
+ import { settingsPanelStore } from '@/stores/storykeep';
17
17
  import { tailwindToHex, colorValues } from '@/utils/compositor/tailwindColors';
18
18
  import type { BrandConfig } from '@/types/tractstack';
19
19
 
@@ -45,7 +45,6 @@ const ViewportComboBox = ({
45
45
  const [internalValue, setInternalValue] = useState(value);
46
46
  const [query, setQuery] = useState('');
47
47
  const [isNowNegative, setIsNowNegative] = useState(isNegative);
48
- const inputRef = useRef<HTMLInputElement>(null);
49
48
 
50
49
  const Icon =
51
50
  viewport === 'mobile'
@@ -100,6 +99,13 @@ const ViewportComboBox = ({
100
99
  const selectedValue = details.value[0] || '';
101
100
  setInternalValue(selectedValue);
102
101
  setQuery('');
102
+ const currentSignal = settingsPanelStore.get();
103
+ if (currentSignal) {
104
+ settingsPanelStore.set({
105
+ ...currentSignal,
106
+ editLock: Date.now(),
107
+ });
108
+ }
103
109
  onFinalChange(selectedValue, viewport, isNowNegative);
104
110
  },
105
111
  [onFinalChange, viewport, isNowNegative]
@@ -154,8 +160,13 @@ const ViewportComboBox = ({
154
160
  loopFocus={true}
155
161
  openOnKeyPress={true}
156
162
  composite={true}
163
+ positioning={{
164
+ placement: 'bottom',
165
+ gutter: 4,
166
+ sameWidth: true,
167
+ }}
157
168
  >
158
- <div className="relative">
169
+ <Combobox.Control>
159
170
  <div className="relative flex items-center">
160
171
  {isColorValue && (
161
172
  <div
@@ -169,7 +180,6 @@ const ViewportComboBox = ({
169
180
  />
170
181
  )}
171
182
  <Combobox.Input
172
- ref={inputRef}
173
183
  className={classNames(
174
184
  'border-mydarkgrey w-full rounded-md border py-2 text-xl leading-5 focus:border-cyan-600 focus:ring-1 focus:ring-cyan-600',
175
185
  isInferred ? 'text-black/20' : 'text-black',
@@ -186,44 +196,51 @@ const ViewportComboBox = ({
186
196
  />
187
197
  </Combobox.Trigger>
188
198
  </div>
189
- </div>
190
- <Combobox.Content className="absolute z-50 mt-1 max-h-32 w-full overflow-auto rounded-md bg-white py-1 text-xl shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
191
- {collection.items.length === 0 ? (
192
- <div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
193
- Nothing found.
194
- </div>
195
- ) : (
196
- collection.items.map((item) => (
197
- <Combobox.Item
198
- key={item}
199
- item={item}
200
- className="viewport-item relative cursor-default select-none py-2"
201
- >
202
- <div className="flex items-center">
203
- {colorValues.includes(item) && (
204
- <div
205
- className="absolute left-3 top-1/2 h-6 w-6 -translate-y-1/2 rounded border border-black/10 shadow-sm"
206
- style={{
207
- backgroundColor: tailwindToHex(
208
- item,
209
- config.BRAND_COLOURS || null
210
- ),
211
- }}
212
- />
213
- )}
214
- <span
215
- className={`block truncate ${colorValues.includes(item) ? 'pl-12' : 'pl-6'} pr-9`}
216
- >
217
- {item}
218
- </span>
219
- <span className="viewport-indicator absolute inset-y-0 right-0 flex items-center pr-4 text-cyan-600">
220
- <CheckIcon className="h-5 w-5" aria-hidden="true" />
221
- </span>
199
+ </Combobox.Control>
200
+ <Portal>
201
+ <Combobox.Positioner style={{ zIndex: 1002 }}>
202
+ <Combobox.Content className="max-h-64 w-full overflow-auto rounded-md bg-white py-1 text-xl shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
203
+ {collection.items.length === 0 ? (
204
+ <div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
205
+ Nothing found.
222
206
  </div>
223
- </Combobox.Item>
224
- ))
225
- )}
226
- </Combobox.Content>
207
+ ) : (
208
+ collection.items.map((item) => (
209
+ <Combobox.Item
210
+ key={item}
211
+ item={item}
212
+ className="viewport-item relative cursor-default select-none py-2"
213
+ >
214
+ <div className="flex items-center">
215
+ {colorValues.includes(item) && (
216
+ <div
217
+ className="absolute left-3 top-1/2 h-6 w-6 -translate-y-1/2 rounded border border-black/10 shadow-sm"
218
+ style={{
219
+ backgroundColor: tailwindToHex(
220
+ item,
221
+ config.BRAND_COLOURS || null
222
+ ),
223
+ }}
224
+ />
225
+ )}
226
+ <span
227
+ className={`block truncate ${colorValues.includes(item) ? 'pl-12' : 'pl-6'} pr-9`}
228
+ >
229
+ {item}
230
+ </span>
231
+ <span className="viewport-indicator absolute inset-y-0 right-0 flex items-center pr-4 text-cyan-600">
232
+ <CheckIcon
233
+ className="h-5 w-5"
234
+ aria-hidden="true"
235
+ />
236
+ </span>
237
+ </div>
238
+ </Combobox.Item>
239
+ ))
240
+ )}
241
+ </Combobox.Content>
242
+ </Combobox.Positioner>
243
+ </Portal>
227
244
  </Combobox.Root>
228
245
  </div>
229
246
  {allowNegative && (