astro-tractstack 2.0.0-rc.32 → 2.0.0-rc.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1028,6 +1028,12 @@ async function w(t, e, c) {
1028
1028
  dest: "src/components/form/advanced/APIConfigSection.tsx"
1029
1029
  },
1030
1030
  // StoryKeep Dashboard Components
1031
+ {
1032
+ src: t(
1033
+ "../templates/src/components/storykeep/StoryKeepBackdrop.astro"
1034
+ ),
1035
+ dest: "src/components/storykeep/StoryKeepBackdrop.astro"
1036
+ },
1031
1037
  {
1032
1038
  src: t("../templates/src/components/storykeep/Dashboard.tsx"),
1033
1039
  dest: "src/components/storykeep/Dashboard.tsx"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.0-rc.32",
3
+ "version": "2.0.0-rc.33",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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 flex-wrap 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
  ) : (
@@ -202,7 +202,9 @@ export default function ActionBuilderField({
202
202
  onChange={(e) => {
203
203
  const newValue = e.target.value;
204
204
  setParam1(newValue);
205
- updateValue(selectedTarget, '', newValue);
205
+ }}
206
+ onBlur={(e) => {
207
+ updateValue(selectedTarget, '', e.target.value);
206
208
  }}
207
209
  placeholder="https://..."
208
210
  className="w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-700 focus:ring-cyan-700"
@@ -0,0 +1,86 @@
1
+ ---
2
+ let MODE = 'logo'; // 'wordmark' | 'logo'
3
+
4
+ interface Props {
5
+ brandConfig: {
6
+ LOGO?: string;
7
+ WORDMARK?: string;
8
+ };
9
+ }
10
+
11
+ const { brandConfig } = Astro.props;
12
+
13
+ const getAssetPath = (
14
+ configPath: string | undefined,
15
+ fallback: string
16
+ ): string => {
17
+ if (configPath && configPath !== '') {
18
+ return configPath;
19
+ }
20
+ return fallback;
21
+ };
22
+
23
+ let assetUrl;
24
+ if (MODE === `wordmark`)
25
+ assetUrl = getAssetPath(brandConfig?.WORDMARK, '/brand/wordmark.svg');
26
+ else assetUrl = getAssetPath(brandConfig?.LOGO, '/brand/logo.svg');
27
+
28
+ // Generate positions programmatically for triple density
29
+ const generatePositions = () => {
30
+ const positions = [];
31
+ const rows = 15; // More rows to extend beyond boundaries
32
+ const cols = 12; // More cols to extend beyond boundaries
33
+
34
+ for (let row = 0; row < rows; row++) {
35
+ for (let col = 0; col < cols; col++) {
36
+ // Skip some positions for natural spacing
37
+ if ((row + col) % 3 !== 0) continue;
38
+
39
+ // Allow logos to extend beyond container edges (no margins)
40
+ const top = (row / (rows - 1)) * 120 - 10; // Extend 10% beyond top/bottom
41
+ const left = (col / (cols - 1)) * 120 - 10; // Extend 10% beyond left/right
42
+ const rotation = -45 + Math.random() * 90;
43
+
44
+ positions.push({
45
+ top: `${top}%`,
46
+ left: `${left}%`,
47
+ rotation: `${rotation}deg`,
48
+ });
49
+ }
50
+ }
51
+
52
+ return positions;
53
+ };
54
+
55
+ const logoPositions = generatePositions();
56
+ ---
57
+
58
+ {
59
+ assetUrl && (
60
+ <div
61
+ class="pointer-events-none absolute overflow-hidden rounded-2xl p-1.5 md:p-3.5"
62
+ style={{
63
+ top: '2rem',
64
+ left: '64rem',
65
+ right: '0',
66
+ bottom: '3.5rem',
67
+ opacity: '0.85',
68
+ //filter: 'grayscale(1)',
69
+ }}
70
+ >
71
+ {logoPositions.map((position) => (
72
+ <img
73
+ src={assetUrl}
74
+ style={{
75
+ position: 'absolute',
76
+ top: position.top,
77
+ left: position.left,
78
+ width: '120px',
79
+ height: 'auto',
80
+ transform: `rotate(${position.rotation})`,
81
+ }}
82
+ />
83
+ ))}
84
+ </div>
85
+ )
86
+ }
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import Layout from '@/layouts/Layout.astro';
3
+ import StoryKeepBackdrop from '@/components/storykeep/StoryKeepBackdrop.astro';
3
4
  import StoryKeepDashboard from '@/components/storykeep/Dashboard';
4
5
  import StoryKeepDashboard_Advanced from '@/components/storykeep/Dashboard_Advanced';
5
6
  import { requireAdminOrEditor, isAuthenticated, isAdmin } from '@/utils/auth';
@@ -53,6 +54,7 @@ try {
53
54
 
54
55
  <Layout title={title} slug="storykeep" isStoryKeep={true}>
55
56
  <main id="main-content" class="min-h-screen w-full">
57
+ <StoryKeepBackdrop brandConfig={brandConfig} />
56
58
  <div class="max-w-5xl p-3.5 md:p-8">
57
59
  <StoryKeepDashboard
58
60
  client:only="react"
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import Layout from '@/layouts/Layout.astro';
3
+ import StoryKeepBackdrop from '@/components/storykeep/StoryKeepBackdrop.astro';
3
4
  import BrandingPageWrapper from '@/components/storykeep/state/BrandingWrapper';
4
5
  import { requireAdminOrEditor, isAuthenticated, isAdmin } from '@/utils/auth';
5
6
  import { getFullContentMap } from '@/stores/analytics';
@@ -42,6 +43,7 @@ try {
42
43
 
43
44
  <Layout title={title} slug="storykeep" isStoryKeep={true}>
44
45
  <main id="main-content" class="min-h-screen w-full">
46
+ <StoryKeepBackdrop brandConfig={brandConfig} />
45
47
  <div class="max-w-5xl p-3.5 md:p-8">
46
48
  <BrandingPageWrapper
47
49
  client:only="react"
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import Layout from '@/layouts/Layout.astro';
3
+ import StoryKeepBackdrop from '@/components/storykeep/StoryKeepBackdrop.astro';
3
4
  import StoryKeepDashboard from '@/components/storykeep/Dashboard';
4
5
  import StoryKeepDashboard_Content from '@/components/storykeep/Dashboard_Content';
5
6
  import { requireAdminOrEditor, isAuthenticated, isAdmin } from '@/utils/auth';
@@ -49,6 +50,7 @@ try {
49
50
 
50
51
  <Layout title={title} slug="storykeep" isStoryKeep={true}>
51
52
  <main id="main-content" class="min-h-screen w-full">
53
+ <StoryKeepBackdrop brandConfig={brandConfig} />
52
54
  <div class="max-w-5xl p-3.5 md:p-8">
53
55
  <StoryKeepDashboard
54
56
  client:only="react"
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import Layout from '@/layouts/Layout.astro';
3
+ import StoryKeepBackdrop from '@/components/storykeep/StoryKeepBackdrop.astro';
3
4
  import StoryKeepDashboard from '@/components/storykeep/Dashboard';
4
5
  import StoryKeepDashboard_Analytics from '@/components/storykeep/Dashboard_Analytics';
5
6
  import { requireAdminOrEditor, isAuthenticated, isAdmin } from '@/utils/auth';
@@ -50,7 +51,8 @@ try {
50
51
  ---
51
52
 
52
53
  <Layout title={title} isStoryKeep={true} slug="storykeep">
53
- <main id="main-content" class="min-h-screen w-full">
54
+ <main id="main-content" class="relative min-h-screen w-full">
55
+ <StoryKeepBackdrop brandConfig={brandConfig} />
54
56
  <div class="max-w-5xl p-3.5 md:p-8">
55
57
  <StoryKeepDashboard
56
58
  client:only="react"
@@ -340,6 +340,18 @@ export class NodesContext {
340
340
  handleClickEventDefault(node, dblClick, this.clickedParentLayer.get());
341
341
  break;
342
342
  case `text`:
343
+ if (
344
+ node.nodeType === 'TagElement' &&
345
+ 'tagName' in node &&
346
+ (node.tagName === 'a' || node.tagName === 'button')
347
+ ) {
348
+ this.toolModeValStore.set({ value: 'styles' });
349
+ handleClickEventDefault(
350
+ node,
351
+ dblClick,
352
+ this.clickedParentLayer.get()
353
+ );
354
+ }
343
355
  if (dblClick && ![`Markdown`].includes(node.nodeType)) {
344
356
  this.toolModeValStore.set({ value: 'styles' });
345
357
  handleClickEventDefault(
@@ -1046,6 +1046,12 @@ export async function injectTemplateFiles(
1046
1046
  },
1047
1047
 
1048
1048
  // StoryKeep Dashboard Components
1049
+ {
1050
+ src: resolve(
1051
+ '../templates/src/components/storykeep/StoryKeepBackdrop.astro'
1052
+ ),
1053
+ dest: 'src/components/storykeep/StoryKeepBackdrop.astro',
1054
+ },
1049
1055
  {
1050
1056
  src: resolve('../templates/src/components/storykeep/Dashboard.tsx'),
1051
1057
  dest: 'src/components/storykeep/Dashboard.tsx',