astro-tractstack 2.0.27 → 2.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.27",
3
+ "version": "2.0.28",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,15 +1,19 @@
1
- import { useMemo, useEffect } from 'react';
1
+ import { useMemo, useEffect, useState } from 'react';
2
2
  import Cog6ToothIcon from '@heroicons/react/24/outline/Cog6ToothIcon';
3
3
  import {
4
4
  styleElementInfoStore,
5
5
  resetStyleElementInfo,
6
6
  settingsPanelStore,
7
7
  } from '@/stores/storykeep';
8
+ import { getCtx } from '@/stores/nodes';
8
9
  import { StylesMemory } from '@/components/edit/state/StylesMemory';
9
10
  import {
10
11
  isMarkdownPaneFragmentNode,
11
12
  isGridLayoutNode,
12
13
  } from '@/utils/compositor/typeGuards';
14
+ import { getNodeText } from '@/utils/compositor/nodesHelper';
15
+ import { cloneDeep } from '@/utils/helpers';
16
+ import { processClassesForViewports } from '@/utils/compositor/reduceNodesClassNames';
13
17
  import SelectedTailwindClass from '@/components/fields/SelectedTailwindClass';
14
18
  import { tagTitles } from '@/types/compositorTypes';
15
19
  import type {
@@ -19,6 +23,78 @@ import type {
19
23
  GridLayoutNode,
20
24
  } from '@/types/compositorTypes';
21
25
 
26
+ type SpanOverride = {
27
+ mobile?: Record<string, string>;
28
+ tablet?: Record<string, string>;
29
+ desktop?: Record<string, string>;
30
+ };
31
+
32
+ const spanStyleClasses: SpanOverride[] = [
33
+ {
34
+ mobile: {
35
+ bgCLIP: 'text',
36
+ bgGradientDIRECTION: 'r',
37
+ gradientFrom: 'blue-600',
38
+ gradientTo: 'teal-500',
39
+ textCOLOR: 'transparent',
40
+ },
41
+ },
42
+ {
43
+ mobile: {
44
+ textCOLOR: 'blue-600',
45
+ },
46
+ },
47
+ {
48
+ mobile: {
49
+ bgCOLOR: 'yellow-300',
50
+ textCOLOR: 'slate-900',
51
+ px: '1',
52
+ rounded: 'sm',
53
+ },
54
+ },
55
+ {
56
+ mobile: {
57
+ display: 'inline-block',
58
+ bgCOLOR: 'indigo-100',
59
+ textCOLOR: 'indigo-700',
60
+ textSIZE: 'xs',
61
+ fontWEIGHT: 'bold',
62
+ px: '2.5',
63
+ py: '0.5',
64
+ rounded: 'full',
65
+ },
66
+ },
67
+ {
68
+ mobile: {
69
+ bgCLIP: 'text',
70
+ textCOLOR: 'transparent',
71
+ bgGradientDIRECTION: 'r',
72
+ gradientFrom: 'orange-400',
73
+ gradientVia: 'pink-500',
74
+ gradientTo: 'purple-600',
75
+ fontWEIGHT: 'bold',
76
+ },
77
+ },
78
+ {
79
+ mobile: {
80
+ textDECORATION: 'underline',
81
+ textDECORATIONSTYLE: 'wavy',
82
+ textDECORATIONCOLOR: 'teal-400',
83
+ textDECORATIONTHICKNESS: '4',
84
+ textUNDERLINEOFFSET: '4',
85
+ },
86
+ },
87
+ {
88
+ mobile: {
89
+ display: 'inline-block',
90
+ bgCOLOR: 'rose-500',
91
+ textCOLOR: 'white',
92
+ px: '2',
93
+ skew: '-3',
94
+ },
95
+ },
96
+ ];
97
+
22
98
  export interface StyleElementPanelProps {
23
99
  node: FlatNode;
24
100
  parentNode: MarkdownPaneFragmentNode | GridLayoutNode;
@@ -30,6 +106,8 @@ const StyleElementPanel = ({
30
106
  parentNode,
31
107
  onTitleChange,
32
108
  }: StyleElementPanelProps) => {
109
+ const [showPresets, setShowPresets] = useState(true);
110
+
33
111
  if (
34
112
  !node?.tagName ||
35
113
  (!isMarkdownPaneFragmentNode(parentNode) && !isGridLayoutNode(parentNode))
@@ -40,6 +118,18 @@ const StyleElementPanel = ({
40
118
  const defaultClasses = parentNode.defaultClasses?.[node.tagName];
41
119
  const overrideClasses = node.overrideClasses;
42
120
 
121
+ const hasOverrides = useMemo(() => {
122
+ return (
123
+ overrideClasses &&
124
+ ((overrideClasses.mobile &&
125
+ Object.keys(overrideClasses.mobile).length > 0) ||
126
+ (overrideClasses.tablet &&
127
+ Object.keys(overrideClasses.tablet).length > 0) ||
128
+ (overrideClasses.desktop &&
129
+ Object.keys(overrideClasses.desktop).length > 0))
130
+ );
131
+ }, [overrideClasses]);
132
+
43
133
  const mergedClasses = useMemo(() => {
44
134
  const result: {
45
135
  [key: string]: {
@@ -49,7 +139,6 @@ const StyleElementPanel = ({
49
139
  };
50
140
  } = {};
51
141
 
52
- // First add all default classes
53
142
  if (defaultClasses) {
54
143
  Object.keys(defaultClasses.mobile).forEach((className) => {
55
144
  result[className] = {
@@ -64,7 +153,6 @@ const StyleElementPanel = ({
64
153
  });
65
154
  }
66
155
 
67
- // Then overlay any override classes
68
156
  if (overrideClasses) {
69
157
  ['mobile', 'tablet', 'desktop'].forEach((viewport) => {
70
158
  const viewportOverrides =
@@ -114,6 +202,24 @@ const StyleElementPanel = ({
114
202
  });
115
203
  };
116
204
 
205
+ const applySpanPreset = (styleIndex: number) => {
206
+ const ctx = getCtx();
207
+ const allNodes = ctx.allNodes.get();
208
+ const targetNode = cloneDeep(allNodes.get(node.id)) as FlatNode;
209
+ if (!targetNode) return;
210
+
211
+ const preset = spanStyleClasses[styleIndex];
212
+
213
+ targetNode.overrideClasses = {
214
+ ...targetNode.overrideClasses,
215
+ ...preset,
216
+ };
217
+
218
+ ctx.modifyNodes([{ ...targetNode, isChanged: true }]);
219
+
220
+ setShowPresets(false);
221
+ };
222
+
117
223
  useEffect(() => {
118
224
  if (
119
225
  styleElementInfoStore.get().markdownParentId !== parentNode.id ||
@@ -140,6 +246,10 @@ const StyleElementPanel = ({
140
246
  }
141
247
  }, [node?.tagName, onTitleChange]);
142
248
 
249
+ const shouldShowQuickStyles =
250
+ node.tagName === 'span' && !hasOverrides && showPresets;
251
+ const nodeText = shouldShowQuickStyles ? getNodeText(node) : '';
252
+
143
253
  return (
144
254
  <div className="space-y-4">
145
255
  {node.wordCarouselPayload && (
@@ -165,42 +275,90 @@ const StyleElementPanel = ({
165
275
  </div>
166
276
  </div>
167
277
  )}
168
- {Object.keys(mergedClasses).length > 0 ? (
169
- <div className="flex flex-wrap gap-2">
170
- {Object.entries(mergedClasses).map(([className, values]) => (
171
- <SelectedTailwindClass
172
- key={className}
173
- name={className}
174
- values={values}
175
- onRemove={handleRemove}
176
- onUpdate={handleUpdate}
177
- />
178
- ))}
179
- </div>
180
- ) : (
181
- <div className="space-y-4">
182
- <em>No styles.</em>
183
- </div>
184
- )}
185
278
 
186
- <div className="space-y-4">
187
- <ul className="text-mydarkgrey flex flex-wrap gap-x-4 gap-y-1">
188
- <li>
189
- <em>Actions:</em>
190
- </li>
191
- <li>
279
+ {shouldShowQuickStyles ? (
280
+ <div className="space-y-6">
281
+ <div className="space-y-2">
282
+ <h3 className="text-mydarkgrey text-sm font-bold">
283
+ Quick Style Selection
284
+ </h3>
285
+ <p className="text-xs text-gray-500">
286
+ Select a preset style for your text selection.
287
+ </p>
288
+ </div>
289
+
290
+ <div className="flex flex-col gap-4">
291
+ {spanStyleClasses.map((style, index) => {
292
+ const [classesPayload] = processClassesForViewports(
293
+ style as any,
294
+ {},
295
+ 1
296
+ );
297
+ const combinedClasses = classesPayload[0] || '';
298
+
299
+ return (
300
+ <button
301
+ key={index}
302
+ onClick={() => applySpanPreset(index)}
303
+ className="group w-full text-left text-xl transition-colors hover:outline-dotted hover:outline-2 hover:outline-black"
304
+ >
305
+ <span className={combinedClasses}>
306
+ {nodeText || 'Sample Text'}
307
+ </span>
308
+ </button>
309
+ );
310
+ })}
311
+ </div>
312
+
313
+ <div className="border-t border-gray-100 pt-4">
192
314
  <button
193
- onClick={() => handleClickAdd()}
194
- className="text-myblue font-bold underline hover:text-black"
315
+ onClick={() => setShowPresets(false)}
316
+ className="text-myblue w-full text-center text-sm underline hover:text-black"
195
317
  >
196
- Add Style
318
+ Apply your own styles manually
197
319
  </button>
198
- </li>
199
- <li>
200
- <StylesMemory node={node} parentNode={parentNode} />
201
- </li>
202
- </ul>
203
- </div>
320
+ </div>
321
+ </div>
322
+ ) : (
323
+ <>
324
+ {Object.keys(mergedClasses).length > 0 ? (
325
+ <div className="flex flex-wrap gap-2">
326
+ {Object.entries(mergedClasses).map(([className, values]) => (
327
+ <SelectedTailwindClass
328
+ key={className}
329
+ name={className}
330
+ values={values}
331
+ onRemove={handleRemove}
332
+ onUpdate={handleUpdate}
333
+ />
334
+ ))}
335
+ </div>
336
+ ) : (
337
+ <div className="space-y-4">
338
+ <em>No styles.</em>
339
+ </div>
340
+ )}
341
+
342
+ <div className="space-y-4">
343
+ <ul className="text-mydarkgrey flex flex-wrap gap-x-4 gap-y-1">
344
+ <li>
345
+ <em>Actions:</em>
346
+ </li>
347
+ <li>
348
+ <button
349
+ onClick={() => handleClickAdd()}
350
+ className="text-myblue font-bold underline hover:text-black"
351
+ >
352
+ Add Style
353
+ </button>
354
+ </li>
355
+ <li>
356
+ <StylesMemory node={node} parentNode={parentNode} />
357
+ </li>
358
+ </ul>
359
+ </div>
360
+ </>
361
+ )}
204
362
  </div>
205
363
  );
206
364
  };
@@ -32,10 +32,13 @@ type ButtonStylePair = [ButtonStyle, ButtonStyle];
32
32
 
33
33
  const buttonStyleOptions = [
34
34
  'Plain text inline',
35
- 'Fancy text inline',
36
- 'Fancy button',
35
+ 'Primary',
36
+ 'Primary inverse',
37
+ 'Dark',
38
+ 'Dark inverse',
37
39
  ];
38
40
  const buttonStyleClasses: ButtonStylePair[] = [
41
+ // Plain text inline
39
42
  [
40
43
  {
41
44
  fontWEIGHT: ['bold'],
@@ -48,32 +51,93 @@ const buttonStyleClasses: ButtonStylePair[] = [
48
51
  textCOLOR: ['brand-1'],
49
52
  },
50
53
  ],
54
+ // Primary Solid Button
51
55
  [
52
56
  {
53
- bgCOLOR: ['brand-4'],
54
- fontWEIGHT: ['bold'],
55
- px: ['3.5'],
56
- py: ['1.5'],
57
- rounded: ['lg'],
58
- textCOLOR: ['brand-1'],
57
+ alignITEMS: ['center'],
58
+ bgCOLOR: ['blue-600'],
59
+ borderCOLOR: ['transparent'],
60
+ display: ['inline-flex'],
61
+ justifyCONTENT: ['center'],
62
+ px: ['6'],
63
+ py: ['3'],
64
+ rounded: ['md'],
65
+ textCOLOR: ['white'],
66
+ textSIZE: ['base'],
67
+ transition: ['colors'],
68
+ transitionDURATION: ['200'],
69
+ w: ['full'],
59
70
  },
60
71
  {
61
- bgCOLOR: ['brand-3'],
72
+ bgCOLOR: ['blue-700'],
62
73
  },
63
74
  ],
75
+ // Secondary Outline Button
64
76
  [
65
77
  {
66
- bgCOLOR: ['brand-4'],
67
- display: ['inline-block'],
68
- fontWEIGHT: ['bold'],
69
- px: ['3.5'],
70
- py: ['2.5'],
78
+ alignITEMS: ['center'],
79
+ bgCOLOR: ['transparent'],
80
+ borderCOLOR: ['blue-600'],
81
+ borderWIDTH: ['2'],
82
+ display: ['inline-flex'],
83
+ justifyCONTENT: ['center'],
84
+ px: ['6'],
85
+ py: ['3'],
71
86
  rounded: ['md'],
72
- textCOLOR: ['brand-1'],
87
+ textCOLOR: ['blue-600'],
88
+ textSIZE: ['base'],
89
+ transition: ['colors'],
90
+ transitionDURATION: ['200'],
91
+ w: ['full'],
92
+ },
93
+ {
94
+ bgCOLOR: ['blue-50'],
95
+ },
96
+ ],
97
+ [
98
+ {
99
+ alignITEMS: ['center'],
100
+ bgCOLOR: ['black'],
101
+ borderCOLOR: ['transparent'],
102
+ borderWIDTH: ['2'],
103
+ display: ['inline-flex'],
104
+ justifyCONTENT: ['center'],
105
+ px: ['6'],
106
+ py: ['3'],
107
+ rounded: ['md'],
108
+ textCOLOR: ['white'],
109
+ textSIZE: ['base'],
110
+ transition: ['colors'],
111
+ transitionDURATION: ['200'],
112
+ w: ['full'],
113
+ },
114
+ {
115
+ bgCOLOR: ['white'],
116
+ textCOLOR: ['black'],
117
+ borderCOLOR: ['black'],
118
+ borderWIDTH: ['2'],
119
+ },
120
+ ],
121
+ [
122
+ {
123
+ alignITEMS: ['center'],
124
+ bgCOLOR: ['transparent'],
125
+ borderCOLOR: ['black'],
126
+ borderWIDTH: ['2'],
127
+ display: ['inline-flex'],
128
+ justifyCONTENT: ['center'],
129
+ px: ['6'],
130
+ py: ['3'],
131
+ rounded: ['md'],
132
+ textCOLOR: ['black'],
133
+ textSIZE: ['base'],
134
+ transition: ['colors'],
135
+ transitionDURATION: ['200'],
136
+ w: ['full'],
73
137
  },
74
138
  {
75
- bgCOLOR: ['brand-3'],
76
- rotate: ['2'],
139
+ textCOLOR: ['myblack'],
140
+ bgCOLOR: ['slate-100'],
77
141
  },
78
142
  ],
79
143
  ];