astro-tractstack 2.0.0-rc.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +1 -0
  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
@@ -1,5 +1,5 @@
1
- import { useEffect, useState, useCallback, useMemo } from 'react';
2
- import { Combobox } from '@ark-ui/react';
1
+ import { useEffect, useState, useCallback, useMemo, useRef } from 'react';
2
+ import { Combobox, Portal } from '@ark-ui/react';
3
3
  import { createListCollection } from '@ark-ui/react/collection';
4
4
  import ChevronUpDownIcon from '@heroicons/react/24/outline/ChevronUpDownIcon';
5
5
  import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
@@ -10,6 +10,7 @@ import {
10
10
  } from '@/stores/storykeep';
11
11
  import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
12
12
  import { isMarkdownPaneFragmentNode } from '@/utils/compositor/typeGuards';
13
+ import { useDropdownDirection } from '@/utils/helpers';
13
14
  import {
14
15
  tagTitles,
15
16
  type Tag,
@@ -126,6 +127,8 @@ const StyleElementPanelAdd = ({
126
127
  const [query, setQuery] = useState('');
127
128
  const [showAdvanced, setShowAdvanced] = useState(false);
128
129
  const [selectedStyle, setSelectedStyle] = useState<string | null>(null);
130
+ const comboboxRef = useRef<HTMLDivElement>(null);
131
+ const { openAbove } = useDropdownDirection(comboboxRef);
129
132
 
130
133
  if (
131
134
  !node ||
@@ -138,7 +141,6 @@ const StyleElementPanelAdd = ({
138
141
 
139
142
  const currentClasses = new Set<string>();
140
143
 
141
- // Get existing classes from default classes in parent
142
144
  if (parentNode.defaultClasses?.[node.tagName]) {
143
145
  const defaults = parentNode.defaultClasses[node.tagName];
144
146
  Object.keys(defaults.mobile).forEach((key) => currentClasses.add(key));
@@ -148,33 +150,26 @@ const StyleElementPanelAdd = ({
148
150
  Object.keys(defaults.desktop).forEach((key) => currentClasses.add(key));
149
151
  }
150
152
 
151
- // Get existing classes from override classes in node
152
153
  if (node.overrideClasses) {
153
154
  Object.values(node.overrideClasses).forEach((viewportClasses) => {
154
155
  Object.keys(viewportClasses).forEach((key) => currentClasses.add(key));
155
156
  });
156
157
  }
157
158
 
158
- // Get filtered styles based on query and existing classes
159
159
  const styles = getFilteredStyles(showAdvanced, currentClasses);
160
- const filteredStyles =
161
- query === ''
162
- ? styles
163
- : styles.filter(
164
- (style) =>
165
- style.title.toLowerCase().includes(query.toLowerCase()) ||
166
- style.key.toLowerCase().includes(query.toLowerCase())
167
- );
168
160
 
169
- // Create collection for Ark UI Combobox
170
161
  const collection = useMemo(
171
162
  () =>
172
163
  createListCollection({
173
- items: filteredStyles,
164
+ items: styles.filter(
165
+ (style) =>
166
+ style.title.toLowerCase().includes(query.toLowerCase()) ||
167
+ style.key.toLowerCase().includes(query.toLowerCase())
168
+ ),
174
169
  itemToValue: (item: StyleOption) => item.key,
175
170
  itemToString: (item: StyleOption) => item.title,
176
171
  }),
177
- [filteredStyles]
172
+ [styles, query]
178
173
  );
179
174
 
180
175
  const availableRecommendedStyles =
@@ -228,7 +223,6 @@ const StyleElementPanelAdd = ({
228
223
  };
229
224
  }, [parentNode.id, node.tagName]);
230
225
 
231
- // CSS to properly style the combobox items with hover and selection
232
226
  const comboboxItemStyles = `
233
227
  .style-item[data-highlighted] {
234
228
  background-color: #0891b2; /* bg-cyan-600 */
@@ -249,7 +243,7 @@ const StyleElementPanelAdd = ({
249
243
  `;
250
244
 
251
245
  return (
252
- <div className="min-h-[400px] max-w-md space-y-4">
246
+ <div className="max-w-md space-y-4">
253
247
  <style>{comboboxItemStyles}</style>
254
248
 
255
249
  <div className="flex flex-row flex-nowrap justify-between">
@@ -283,46 +277,57 @@ const StyleElementPanelAdd = ({
283
277
  loopFocus={true}
284
278
  openOnKeyPress={true}
285
279
  composite={true}
280
+ positioning={{
281
+ placement: openAbove ? 'top' : 'bottom',
282
+ gutter: 4,
283
+ sameWidth: true,
284
+ }}
286
285
  >
287
- <div className="relative">
288
- <Combobox.Input
289
- className="border-mydarkgrey focus:border-myblue focus:ring-myblue w-full rounded-md py-2 pl-3 pr-10 text-xl shadow-sm"
290
- onChange={(e) => setQuery(e.target.value)}
291
- placeholder="Search styles..."
292
- />
293
- <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
294
- <ChevronUpDownIcon
295
- className="text-mydarkgrey h-5 w-5"
296
- aria-hidden="true"
286
+ <Combobox.Control ref={comboboxRef}>
287
+ <div className="relative">
288
+ <Combobox.Input
289
+ className="border-mydarkgrey focus:border-myblue focus:ring-myblue w-full rounded-md py-2 pl-3 pr-10 text-xl shadow-sm"
290
+ onChange={(e) => setQuery(e.target.value)}
291
+ placeholder="Search styles..."
297
292
  />
298
- </Combobox.Trigger>
299
- </div>
293
+ <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
294
+ <ChevronUpDownIcon
295
+ className="text-mydarkgrey h-5 w-5"
296
+ aria-hidden="true"
297
+ />
298
+ </Combobox.Trigger>
299
+ </div>
300
+ </Combobox.Control>
300
301
 
301
- <Combobox.Content className="absolute z-50 mt-1 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">
302
- {collection.items.length === 0 ? (
303
- <div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
304
- Nothing found.
305
- </div>
306
- ) : (
307
- collection.items.map((style) => (
308
- <Combobox.Item
309
- key={style.key}
310
- item={style}
311
- className="style-item relative cursor-default select-none py-2 pl-10 pr-4 text-black"
312
- >
313
- <span className="block truncate">
314
- {style.title}
315
- <span className="ml-2 text-sm opacity-60">
316
- {style.className}
317
- </span>
318
- </span>
319
- <span className="style-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
320
- <CheckIcon className="h-5 w-5" aria-hidden="true" />
321
- </span>
322
- </Combobox.Item>
323
- ))
324
- )}
325
- </Combobox.Content>
302
+ <Portal>
303
+ <Combobox.Positioner style={{ zIndex: 1002 }}>
304
+ <Combobox.Content className="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">
305
+ {collection.items.length === 0 ? (
306
+ <div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
307
+ Nothing found.
308
+ </div>
309
+ ) : (
310
+ collection.items.map((style) => (
311
+ <Combobox.Item
312
+ key={style.key}
313
+ item={style}
314
+ className="style-item relative cursor-default select-none py-2 pl-10 pr-4 text-black"
315
+ >
316
+ <span className="block truncate">
317
+ {style.title}
318
+ <span className="ml-2 text-sm opacity-60">
319
+ {style.className}
320
+ </span>
321
+ </span>
322
+ <span className="style-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
323
+ <CheckIcon className="h-5 w-5" aria-hidden="true" />
324
+ </span>
325
+ </Combobox.Item>
326
+ ))
327
+ )}
328
+ </Combobox.Content>
329
+ </Combobox.Positioner>
330
+ </Portal>
326
331
  </Combobox.Root>
327
332
  </div>
328
333
 
@@ -1,5 +1,5 @@
1
- import { useState, useCallback, useMemo } from 'react';
2
- import { Combobox } from '@ark-ui/react';
1
+ import { useState, useCallback, useMemo, useRef } from 'react';
2
+ import { Combobox, Portal } from '@ark-ui/react';
3
3
  import { createListCollection } from '@ark-ui/react/collection';
4
4
  import ChevronUpDownIcon from '@heroicons/react/24/outline/ChevronUpDownIcon';
5
5
  import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
@@ -7,6 +7,7 @@ import { settingsPanelStore } from '@/stores/storykeep';
7
7
  import { getCtx } from '@/stores/nodes';
8
8
  import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
9
9
  import { isMarkdownPaneFragmentNode } from '@/utils/compositor/typeGuards';
10
+ import { useDropdownDirection } from '@/utils/helpers';
10
11
  import type { BasePanelProps } from '@/types/compositorTypes';
11
12
 
12
13
  // Recommended styles for images
@@ -73,6 +74,8 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
73
74
  const [query, setQuery] = useState('');
74
75
  const [showAdvanced, setShowAdvanced] = useState(false);
75
76
  const [selectedStyle, setSelectedStyle] = useState<string | null>(null);
77
+ const comboboxRef = useRef<HTMLDivElement>(null);
78
+ const { openAbove } = useDropdownDirection(comboboxRef);
76
79
 
77
80
  if (!node?.tagName || !parentNode || !isMarkdownPaneFragmentNode(parentNode))
78
81
  return null;
@@ -84,13 +87,11 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
84
87
  const ctx = getCtx();
85
88
  const allNodes = ctx.allNodes.get();
86
89
 
87
- // When styling container or outer container, use their IDs directly
88
90
  const targetNode = allNodes.get(node.id);
89
91
  if (!targetNode) return null;
90
92
 
91
93
  const currentClasses = new Set<string>();
92
94
 
93
- // Get existing classes from default classes in parent for THIS element type
94
95
  if (parentNode.defaultClasses?.[node.tagName]) {
95
96
  const defaults = parentNode.defaultClasses[node.tagName];
96
97
  Object.keys(defaults.mobile).forEach((key) => currentClasses.add(key));
@@ -100,14 +101,12 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
100
101
  Object.keys(defaults.desktop).forEach((key) => currentClasses.add(key));
101
102
  }
102
103
 
103
- // Get existing override classes for THIS element
104
104
  if (`overrideClasses` in targetNode && targetNode.overrideClasses) {
105
105
  Object.values(targetNode.overrideClasses).forEach((viewportClasses) => {
106
106
  Object.keys(viewportClasses).forEach((key) => currentClasses.add(key));
107
107
  });
108
108
  }
109
109
 
110
- // Create collection for combobox with filtered styles
111
110
  const styles = getFilteredStyles(showAdvanced, currentClasses);
112
111
  const filteredStyles =
113
112
  query === ''
@@ -118,7 +117,6 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
118
117
  style.key.toLowerCase().includes(query.toLowerCase())
119
118
  );
120
119
 
121
- // Create collection for Ark UI Combobox
122
120
  const collection = useMemo(() => {
123
121
  return createListCollection({
124
122
  items: filteredStyles,
@@ -127,7 +125,6 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
127
125
  });
128
126
  }, [filteredStyles]);
129
127
 
130
- // Get appropriate recommended styles based on element type
131
128
  const recommendedStyles = isOuterContainer
132
129
  ? OUTER_CONTAINER_STYLES
133
130
  : isContainer
@@ -151,7 +148,7 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
151
148
  ? 'style-img-container-update'
152
149
  : 'style-img-update',
153
150
  nodeId: node.id,
154
- childId, // Preserve childId for container context
151
+ childId,
155
152
  className: styleKey,
156
153
  expanded: true,
157
154
  });
@@ -160,10 +157,9 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
160
157
  );
161
158
 
162
159
  const handleCancel = () => {
163
- // Return to main panel with correct context
164
160
  settingsPanelStore.set({
165
161
  action: 'style-image',
166
- nodeId: childId || node.id, // Use childId when available for context
162
+ nodeId: childId || node.id,
167
163
  expanded: true,
168
164
  });
169
165
  };
@@ -174,7 +170,6 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
174
170
  ? 'Container'
175
171
  : 'Image';
176
172
 
177
- // CSS to properly style the combobox items with hover and selection
178
173
  const comboboxItemStyles = `
179
174
  .style-item[data-highlighted] {
180
175
  background-color: #0891b2; /* bg-cyan-600 */
@@ -195,7 +190,7 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
195
190
  `;
196
191
 
197
192
  return (
198
- <div className="min-h-[400px] max-w-md space-y-4">
193
+ <div className="max-w-md space-y-4">
199
194
  <style>{comboboxItemStyles}</style>
200
195
 
201
196
  <div className="flex flex-row flex-nowrap justify-between">
@@ -230,46 +225,57 @@ const StyleImagePanelAdd = ({ node, parentNode, childId }: BasePanelProps) => {
230
225
  loopFocus={true}
231
226
  openOnKeyPress={true}
232
227
  composite={true}
228
+ positioning={{
229
+ placement: openAbove ? 'top' : 'bottom',
230
+ gutter: 4,
231
+ sameWidth: true,
232
+ }}
233
233
  >
234
- <div className="relative">
235
- <Combobox.Input
236
- className="border-mydarkgrey focus:border-myblue focus:ring-myblue w-full rounded-md py-2 pl-3 pr-10 text-xl shadow-sm"
237
- onChange={(e) => setQuery(e.target.value)}
238
- placeholder="Search styles..."
239
- />
240
- <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
241
- <ChevronUpDownIcon
242
- className="text-mydarkgrey h-5 w-5"
243
- aria-hidden="true"
234
+ <Combobox.Control ref={comboboxRef}>
235
+ <div className="relative">
236
+ <Combobox.Input
237
+ className="border-mydarkgrey focus:border-myblue focus:ring-myblue w-full rounded-md py-2 pl-3 pr-10 text-xl shadow-sm"
238
+ onChange={(e) => setQuery(e.target.value)}
239
+ placeholder="Search styles..."
244
240
  />
245
- </Combobox.Trigger>
246
- </div>
241
+ <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
242
+ <ChevronUpDownIcon
243
+ className="text-mydarkgrey h-5 w-5"
244
+ aria-hidden="true"
245
+ />
246
+ </Combobox.Trigger>
247
+ </div>
248
+ </Combobox.Control>
247
249
 
248
- <Combobox.Content className="absolute z-50 mt-1 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">
249
- {collection.items.length === 0 ? (
250
- <div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
251
- Nothing found.
252
- </div>
253
- ) : (
254
- collection.items.map((style) => (
255
- <Combobox.Item
256
- key={style.key}
257
- item={style}
258
- className="style-item relative cursor-default select-none py-2 pl-10 pr-4 text-black"
259
- >
260
- <span className="block truncate">
261
- {style.title}
262
- <span className="ml-2 text-sm opacity-60">
263
- {style.className}
264
- </span>
265
- </span>
266
- <span className="style-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
267
- <CheckIcon className="h-5 w-5" aria-hidden="true" />
268
- </span>
269
- </Combobox.Item>
270
- ))
271
- )}
272
- </Combobox.Content>
250
+ <Portal>
251
+ <Combobox.Positioner style={{ zIndex: 1002 }}>
252
+ <Combobox.Content className="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">
253
+ {collection.items.length === 0 ? (
254
+ <div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
255
+ Nothing found.
256
+ </div>
257
+ ) : (
258
+ collection.items.map((style) => (
259
+ <Combobox.Item
260
+ key={style.key}
261
+ item={style}
262
+ className="style-item relative cursor-default select-none py-2 pl-10 pr-4 text-black"
263
+ >
264
+ <span className="block truncate">
265
+ {style.title}
266
+ <span className="ml-2 text-sm opacity-60">
267
+ {style.className}
268
+ </span>
269
+ </span>
270
+ <span className="style-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
271
+ <CheckIcon className="h-5 w-5" aria-hidden="true" />
272
+ </span>
273
+ </Combobox.Item>
274
+ ))
275
+ )}
276
+ </Combobox.Content>
277
+ </Combobox.Positioner>
278
+ </Portal>
273
279
  </Combobox.Root>
274
280
  </div>
275
281
 
@@ -1,5 +1,5 @@
1
- import { useState, useCallback, useMemo } from 'react';
2
- import { Combobox } from '@ark-ui/react';
1
+ import { useState, useCallback, useMemo, useRef } from 'react';
2
+ import { Combobox, Portal } from '@ark-ui/react';
3
3
  import { createListCollection } from '@ark-ui/react/collection';
4
4
  import ChevronUpDownIcon from '@heroicons/react/24/outline/ChevronUpDownIcon';
5
5
  import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
@@ -7,6 +7,7 @@ import { settingsPanelStore } from '@/stores/storykeep';
7
7
  import { getCtx } from '@/stores/nodes';
8
8
  import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
9
9
  import { isMarkdownPaneFragmentNode } from '@/utils/compositor/typeGuards';
10
+ import { useDropdownDirection } from '@/utils/helpers';
10
11
  import type { BasePanelProps, FlatNode } from '@/types/compositorTypes';
11
12
 
12
13
  // Recommended styles for list items
@@ -66,17 +67,17 @@ const StyleLiElementAddPanel = ({
66
67
  const [query, setQuery] = useState('');
67
68
  const [showAdvanced, setShowAdvanced] = useState(false);
68
69
  const [selectedStyle, setSelectedStyle] = useState<string | null>(null);
70
+ const comboboxRef = useRef<HTMLDivElement>(null);
71
+ const { openAbove } = useDropdownDirection(comboboxRef);
69
72
 
70
73
  if (!node?.tagName || !parentNode || !isMarkdownPaneFragmentNode(parentNode))
71
74
  return null;
72
75
 
73
- // Determine if we're styling a container or list item
74
76
  const isContainer = node.tagName === 'ul' || node.tagName === 'ol';
75
77
 
76
78
  const ctx = getCtx();
77
79
  const allNodes = ctx.allNodes.get();
78
80
 
79
- // Get the correct node based on what we're styling
80
81
  const targetNodeId = isContainer ? node.id : childId || node.id;
81
82
  const targetNode = allNodes.get(targetNodeId) as FlatNode;
82
83
 
@@ -84,7 +85,6 @@ const StyleLiElementAddPanel = ({
84
85
 
85
86
  const currentClasses = new Set<string>();
86
87
 
87
- // Get existing classes from default classes in parent
88
88
  if (parentNode.defaultClasses?.[targetNode.tagName]) {
89
89
  const defaults = parentNode.defaultClasses[targetNode.tagName];
90
90
  Object.keys(defaults.mobile).forEach((key) => currentClasses.add(key));
@@ -94,7 +94,6 @@ const StyleLiElementAddPanel = ({
94
94
  Object.keys(defaults.desktop).forEach((key) => currentClasses.add(key));
95
95
  }
96
96
 
97
- // Get existing classes from override classes in node
98
97
  if (targetNode.overrideClasses) {
99
98
  Object.values(targetNode.overrideClasses).forEach((viewportClasses) => {
100
99
  Object.keys(viewportClasses).forEach((key) => currentClasses.add(key));
@@ -103,7 +102,6 @@ const StyleLiElementAddPanel = ({
103
102
 
104
103
  const styles = getFilteredStyles(showAdvanced, currentClasses);
105
104
 
106
- // Create collection for combobox
107
105
  const collection = useMemo(() => {
108
106
  const filteredStyles =
109
107
  query === ''
@@ -121,7 +119,6 @@ const StyleLiElementAddPanel = ({
121
119
  });
122
120
  }, [styles, query]);
123
121
 
124
- // Get appropriate recommended styles based on element type
125
122
  const recommendedStyles = isContainer
126
123
  ? LIST_CONTAINER_STYLES
127
124
  : LIST_ITEM_STYLES;
@@ -179,7 +176,6 @@ const StyleLiElementAddPanel = ({
179
176
  });
180
177
  };
181
178
 
182
- // CSS to properly style the combobox items with hover and selection
183
179
  const comboboxItemStyles = `
184
180
  .style-item[data-highlighted] {
185
181
  background-color: #0891b2; /* bg-cyan-600 */
@@ -200,7 +196,7 @@ const StyleLiElementAddPanel = ({
200
196
  `;
201
197
 
202
198
  return (
203
- <div className="min-h-[400px] max-w-md space-y-4">
199
+ <div className="max-w-md space-y-4">
204
200
  <style>{comboboxItemStyles}</style>
205
201
 
206
202
  <div className="flex flex-row flex-nowrap justify-between">
@@ -235,46 +231,57 @@ const StyleLiElementAddPanel = ({
235
231
  loopFocus={true}
236
232
  openOnKeyPress={true}
237
233
  composite={true}
234
+ positioning={{
235
+ placement: openAbove ? 'top' : 'bottom',
236
+ gutter: 4,
237
+ sameWidth: true,
238
+ }}
238
239
  >
239
- <div className="relative">
240
- <Combobox.Input
241
- className="border-mydarkgrey focus:border-myblue focus:ring-myblue w-full rounded-md py-2 pl-3 pr-10 text-xl shadow-sm"
242
- placeholder="Search styles..."
243
- autoComplete="off"
244
- />
245
- <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
246
- <ChevronUpDownIcon
247
- className="text-mydarkgrey h-5 w-5"
248
- aria-hidden="true"
240
+ <Combobox.Control ref={comboboxRef}>
241
+ <div className="relative">
242
+ <Combobox.Input
243
+ className="border-mydarkgrey focus:border-myblue focus:ring-myblue w-full rounded-md py-2 pl-3 pr-10 text-xl shadow-sm"
244
+ placeholder="Search styles..."
245
+ autoComplete="off"
249
246
  />
250
- </Combobox.Trigger>
251
- </div>
247
+ <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
248
+ <ChevronUpDownIcon
249
+ className="text-mydarkgrey h-5 w-5"
250
+ aria-hidden="true"
251
+ />
252
+ </Combobox.Trigger>
253
+ </div>
254
+ </Combobox.Control>
252
255
 
253
- <Combobox.Content className="absolute z-50 mt-1 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">
254
- {collection.items.length === 0 ? (
255
- <div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
256
- Nothing found.
257
- </div>
258
- ) : (
259
- collection.items.map((item) => (
260
- <Combobox.Item
261
- key={item.key}
262
- item={item}
263
- className="style-item relative cursor-default select-none py-2 pl-10 pr-4"
264
- >
265
- <span className="block truncate">
266
- {item.title}
267
- <span className="ml-2 text-sm opacity-60">
268
- {item.className}
269
- </span>
270
- </span>
271
- <span className="style-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
272
- <CheckIcon className="h-5 w-5" aria-hidden="true" />
273
- </span>
274
- </Combobox.Item>
275
- ))
276
- )}
277
- </Combobox.Content>
256
+ <Portal>
257
+ <Combobox.Positioner style={{ zIndex: 1002 }}>
258
+ <Combobox.Content className="absolute z-50 mt-1 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">
259
+ {collection.items.length === 0 ? (
260
+ <div className="text-mydarkgrey relative cursor-default select-none px-4 py-2">
261
+ Nothing found.
262
+ </div>
263
+ ) : (
264
+ collection.items.map((item) => (
265
+ <Combobox.Item
266
+ key={item.key}
267
+ item={item}
268
+ className="style-item relative cursor-default select-none py-2 pl-10 pr-4"
269
+ >
270
+ <span className="block truncate">
271
+ {item.title}
272
+ <span className="ml-2 text-sm opacity-60">
273
+ {item.className}
274
+ </span>
275
+ </span>
276
+ <span className="style-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
277
+ <CheckIcon className="h-5 w-5" aria-hidden="true" />
278
+ </span>
279
+ </Combobox.Item>
280
+ ))
281
+ )}
282
+ </Combobox.Content>
283
+ </Combobox.Positioner>
284
+ </Portal>
278
285
  </Combobox.Root>
279
286
  </div>
280
287