@bupple/vss-plugin-typography 1.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.
- package/dist/chunk-5IHSZBVF.cjs +2174 -0
- package/dist/chunk-YI7CKNOM.js +2169 -0
- package/dist/index.cjs +49 -0
- package/dist/index.d.cts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +40 -0
- package/dist/panel-4TAKN5WR.cjs +320 -0
- package/dist/panel-NCQBLXF6.js +318 -0
- package/dist/panel-mobile-3RLRYQAK.cjs +198 -0
- package/dist/panel-mobile-4A4K4QIO.js +196 -0
- package/package.json +42 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk5IHSZBVF_cjs = require('./chunk-5IHSZBVF.cjs');
|
|
4
|
+
var plugins = require('@bupple/vss/plugins');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
|
|
7
|
+
function buildCategoriesFromPresets(presets) {
|
|
8
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const p of presets) {
|
|
10
|
+
const list = seen.get(p.category) ?? [];
|
|
11
|
+
list.push(p);
|
|
12
|
+
seen.set(p.category, list);
|
|
13
|
+
}
|
|
14
|
+
return Array.from(seen.entries()).map(([id, items]) => ({
|
|
15
|
+
id,
|
|
16
|
+
name: id.charAt(0).toUpperCase() + id.slice(1),
|
|
17
|
+
presets: items
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
function typographyPlugin(options = {}) {
|
|
21
|
+
return plugins.createPlugin({
|
|
22
|
+
id: "bupple:typography",
|
|
23
|
+
name: "Typography",
|
|
24
|
+
version: "1.0.0",
|
|
25
|
+
onRegister(ctx) {
|
|
26
|
+
const presets = options.presets ?? chunk5IHSZBVF_cjs.TEXT_PRESETS;
|
|
27
|
+
const categories = options.categories ?? (options.presets ? buildCategoriesFromPresets(presets) : chunk5IHSZBVF_cjs.TEXT_PRESET_CATEGORIES);
|
|
28
|
+
chunk5IHSZBVF_cjs.setPresets(presets, categories);
|
|
29
|
+
ctx.registerPanel({
|
|
30
|
+
id: "text",
|
|
31
|
+
label: "Text",
|
|
32
|
+
icon: ctx.icons.pluginTypography,
|
|
33
|
+
component: react.lazy(() => import('./panel-4TAKN5WR.cjs')),
|
|
34
|
+
mobileComponent: react.lazy(() => import('./panel-mobile-3RLRYQAK.cjs')),
|
|
35
|
+
order: options.order ?? 3
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(exports, "TEXT_PRESETS", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
get: function () { return chunk5IHSZBVF_cjs.TEXT_PRESETS; }
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(exports, "TEXT_PRESET_CATEGORIES", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
get: function () { return chunk5IHSZBVF_cjs.TEXT_PRESET_CATEGORIES; }
|
|
48
|
+
});
|
|
49
|
+
exports.typographyPlugin = typographyPlugin;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { TextStyle } from '@bupple/vss/core';
|
|
2
|
+
import { PluginDefinition } from '@bupple/vss/plugins';
|
|
3
|
+
|
|
4
|
+
type TextPresetCategoryId = 'headings' | 'body' | 'minimal' | 'modern' | 'creative' | 'cinematic' | 'social' | 'neon-glow' | 'retro-vintage' | 'editorial' | 'tech' | 'handwritten';
|
|
5
|
+
interface TextPreset {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
category: TextPresetCategoryId;
|
|
9
|
+
style: Partial<TextStyle>;
|
|
10
|
+
}
|
|
11
|
+
interface TextPresetCategory {
|
|
12
|
+
id: TextPresetCategoryId;
|
|
13
|
+
name: string;
|
|
14
|
+
presets: TextPreset[];
|
|
15
|
+
}
|
|
16
|
+
declare const TEXT_PRESETS: TextPreset[];
|
|
17
|
+
declare const TEXT_PRESET_CATEGORIES: TextPresetCategory[];
|
|
18
|
+
|
|
19
|
+
interface TypographyPluginOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Order of the panel in the sidebar.
|
|
22
|
+
*/
|
|
23
|
+
order?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Custom presets to use. When provided, these **replace** the built-in presets entirely.
|
|
26
|
+
* To extend the defaults, spread `TEXT_PRESETS` and append your own:
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { TEXT_PRESETS, typographyPlugin } from '@bupple/vss-plugin-typography'
|
|
31
|
+
*
|
|
32
|
+
* typographyPlugin({
|
|
33
|
+
* presets: [...TEXT_PRESETS, { id: 'my-preset', label: 'My Preset', category: 'headings', style: { ... } }],
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
presets?: TextPreset[];
|
|
38
|
+
/**
|
|
39
|
+
* Custom categories to use. When provided, these **replace** the built-in categories.
|
|
40
|
+
* Each category references presets by filtering the `presets` array.
|
|
41
|
+
*
|
|
42
|
+
* If `presets` is provided but `categories` is not, categories are auto-generated
|
|
43
|
+
* from the unique `category` values in the presets array.
|
|
44
|
+
*/
|
|
45
|
+
categories?: TextPresetCategory[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a typography plugin that registers a Text panel with preset categories.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { typographyPlugin } from '@bupple/vss-plugin-typography'
|
|
53
|
+
*
|
|
54
|
+
* // Use built-in presets
|
|
55
|
+
* typographyPlugin()
|
|
56
|
+
*
|
|
57
|
+
* // Replace with custom presets
|
|
58
|
+
* typographyPlugin({ presets: myPresets, categories: myCategories })
|
|
59
|
+
*
|
|
60
|
+
* // Extend built-in presets
|
|
61
|
+
* import { TEXT_PRESETS } from '@bupple/vss-plugin-typography'
|
|
62
|
+
* typographyPlugin({ presets: [...TEXT_PRESETS, ...myExtraPresets] })
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function typographyPlugin(options?: TypographyPluginOptions): PluginDefinition;
|
|
66
|
+
|
|
67
|
+
export { TEXT_PRESETS, TEXT_PRESET_CATEGORIES, type TextPreset, type TextPresetCategory, type TextPresetCategoryId, type TypographyPluginOptions, typographyPlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { TextStyle } from '@bupple/vss/core';
|
|
2
|
+
import { PluginDefinition } from '@bupple/vss/plugins';
|
|
3
|
+
|
|
4
|
+
type TextPresetCategoryId = 'headings' | 'body' | 'minimal' | 'modern' | 'creative' | 'cinematic' | 'social' | 'neon-glow' | 'retro-vintage' | 'editorial' | 'tech' | 'handwritten';
|
|
5
|
+
interface TextPreset {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
category: TextPresetCategoryId;
|
|
9
|
+
style: Partial<TextStyle>;
|
|
10
|
+
}
|
|
11
|
+
interface TextPresetCategory {
|
|
12
|
+
id: TextPresetCategoryId;
|
|
13
|
+
name: string;
|
|
14
|
+
presets: TextPreset[];
|
|
15
|
+
}
|
|
16
|
+
declare const TEXT_PRESETS: TextPreset[];
|
|
17
|
+
declare const TEXT_PRESET_CATEGORIES: TextPresetCategory[];
|
|
18
|
+
|
|
19
|
+
interface TypographyPluginOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Order of the panel in the sidebar.
|
|
22
|
+
*/
|
|
23
|
+
order?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Custom presets to use. When provided, these **replace** the built-in presets entirely.
|
|
26
|
+
* To extend the defaults, spread `TEXT_PRESETS` and append your own:
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { TEXT_PRESETS, typographyPlugin } from '@bupple/vss-plugin-typography'
|
|
31
|
+
*
|
|
32
|
+
* typographyPlugin({
|
|
33
|
+
* presets: [...TEXT_PRESETS, { id: 'my-preset', label: 'My Preset', category: 'headings', style: { ... } }],
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
presets?: TextPreset[];
|
|
38
|
+
/**
|
|
39
|
+
* Custom categories to use. When provided, these **replace** the built-in categories.
|
|
40
|
+
* Each category references presets by filtering the `presets` array.
|
|
41
|
+
*
|
|
42
|
+
* If `presets` is provided but `categories` is not, categories are auto-generated
|
|
43
|
+
* from the unique `category` values in the presets array.
|
|
44
|
+
*/
|
|
45
|
+
categories?: TextPresetCategory[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a typography plugin that registers a Text panel with preset categories.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { typographyPlugin } from '@bupple/vss-plugin-typography'
|
|
53
|
+
*
|
|
54
|
+
* // Use built-in presets
|
|
55
|
+
* typographyPlugin()
|
|
56
|
+
*
|
|
57
|
+
* // Replace with custom presets
|
|
58
|
+
* typographyPlugin({ presets: myPresets, categories: myCategories })
|
|
59
|
+
*
|
|
60
|
+
* // Extend built-in presets
|
|
61
|
+
* import { TEXT_PRESETS } from '@bupple/vss-plugin-typography'
|
|
62
|
+
* typographyPlugin({ presets: [...TEXT_PRESETS, ...myExtraPresets] })
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function typographyPlugin(options?: TypographyPluginOptions): PluginDefinition;
|
|
66
|
+
|
|
67
|
+
export { TEXT_PRESETS, TEXT_PRESET_CATEGORIES, type TextPreset, type TextPresetCategory, type TextPresetCategoryId, type TypographyPluginOptions, typographyPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { TEXT_PRESETS, TEXT_PRESET_CATEGORIES, setPresets } from './chunk-YI7CKNOM.js';
|
|
2
|
+
export { TEXT_PRESETS, TEXT_PRESET_CATEGORIES } from './chunk-YI7CKNOM.js';
|
|
3
|
+
import { createPlugin } from '@bupple/vss/plugins';
|
|
4
|
+
import { lazy } from 'react';
|
|
5
|
+
|
|
6
|
+
function buildCategoriesFromPresets(presets) {
|
|
7
|
+
const seen = /* @__PURE__ */ new Map();
|
|
8
|
+
for (const p of presets) {
|
|
9
|
+
const list = seen.get(p.category) ?? [];
|
|
10
|
+
list.push(p);
|
|
11
|
+
seen.set(p.category, list);
|
|
12
|
+
}
|
|
13
|
+
return Array.from(seen.entries()).map(([id, items]) => ({
|
|
14
|
+
id,
|
|
15
|
+
name: id.charAt(0).toUpperCase() + id.slice(1),
|
|
16
|
+
presets: items
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
function typographyPlugin(options = {}) {
|
|
20
|
+
return createPlugin({
|
|
21
|
+
id: "bupple:typography",
|
|
22
|
+
name: "Typography",
|
|
23
|
+
version: "1.0.0",
|
|
24
|
+
onRegister(ctx) {
|
|
25
|
+
const presets = options.presets ?? TEXT_PRESETS;
|
|
26
|
+
const categories = options.categories ?? (options.presets ? buildCategoriesFromPresets(presets) : TEXT_PRESET_CATEGORIES);
|
|
27
|
+
setPresets(presets, categories);
|
|
28
|
+
ctx.registerPanel({
|
|
29
|
+
id: "text",
|
|
30
|
+
label: "Text",
|
|
31
|
+
icon: ctx.icons.pluginTypography,
|
|
32
|
+
component: lazy(() => import('./panel-NCQBLXF6.js')),
|
|
33
|
+
mobileComponent: lazy(() => import('./panel-mobile-4A4K4QIO.js')),
|
|
34
|
+
order: options.order ?? 3
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { typographyPlugin };
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk5IHSZBVF_cjs = require('./chunk-5IHSZBVF.cjs');
|
|
4
|
+
var core = require('@bupple/vss/core');
|
|
5
|
+
var ui = require('@bupple/vss/ui');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
|
|
9
|
+
function TextPanel() {
|
|
10
|
+
const icons = ui.useIconMap();
|
|
11
|
+
const {
|
|
12
|
+
chevronDown: ChevronDownIcon,
|
|
13
|
+
add: PlusIcon,
|
|
14
|
+
search: SearchIcon,
|
|
15
|
+
itemText: TypeIcon,
|
|
16
|
+
close: XIcon
|
|
17
|
+
} = icons;
|
|
18
|
+
const settings = ui.useStudioStore((s) => s.project.settings);
|
|
19
|
+
const addItem = ui.useStudioStore((s) => s.addItem);
|
|
20
|
+
const addTrack = ui.useStudioStore((s) => s.addTrack);
|
|
21
|
+
const currentFrame = ui.useStudioStore((s) => s.player.currentFrame);
|
|
22
|
+
const tracks = ui.useStudioStore((s) => s.timeline.tracks);
|
|
23
|
+
const items = ui.useStudioStore((s) => s.timeline.items);
|
|
24
|
+
const itemIdsByTrack = ui.useStudioStore((s) => s.timeline.itemIdsByTrack);
|
|
25
|
+
const [search, setSearch] = react.useState("");
|
|
26
|
+
const [collapsedCategories, setCollapsedCategories] = react.useState(
|
|
27
|
+
/* @__PURE__ */ new Set()
|
|
28
|
+
);
|
|
29
|
+
const toggleCategory = react.useCallback((id) => {
|
|
30
|
+
setCollapsedCategories((prev) => {
|
|
31
|
+
const next = new Set(prev);
|
|
32
|
+
if (next.has(id)) next.delete(id);
|
|
33
|
+
else next.add(id);
|
|
34
|
+
return next;
|
|
35
|
+
});
|
|
36
|
+
}, []);
|
|
37
|
+
const handleAddText = react.useCallback(
|
|
38
|
+
(preset) => {
|
|
39
|
+
const fps = settings.fps;
|
|
40
|
+
const durationFrames = fps * 5;
|
|
41
|
+
let resolvedTrackId;
|
|
42
|
+
const available = ui.findAvailableTrack(
|
|
43
|
+
tracks,
|
|
44
|
+
itemIdsByTrack,
|
|
45
|
+
items,
|
|
46
|
+
"track",
|
|
47
|
+
currentFrame,
|
|
48
|
+
durationFrames
|
|
49
|
+
);
|
|
50
|
+
if (available) {
|
|
51
|
+
resolvedTrackId = available.id;
|
|
52
|
+
} else {
|
|
53
|
+
const existingCount = tracks.filter((t) => t.kind === "track").length;
|
|
54
|
+
resolvedTrackId = addTrack({
|
|
55
|
+
name: `Track ${existingCount + 1}`,
|
|
56
|
+
kind: "track",
|
|
57
|
+
order: tracks.length,
|
|
58
|
+
locked: false,
|
|
59
|
+
muted: false,
|
|
60
|
+
hidden: false
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const item = {
|
|
64
|
+
type: "text",
|
|
65
|
+
trackId: resolvedTrackId,
|
|
66
|
+
startFrame: currentFrame,
|
|
67
|
+
durationFrames,
|
|
68
|
+
zIndex: 10,
|
|
69
|
+
locked: false,
|
|
70
|
+
disabled: false,
|
|
71
|
+
source: {},
|
|
72
|
+
transform: {
|
|
73
|
+
...core.createDefaultTransform(settings),
|
|
74
|
+
width: settings.width * 0.8,
|
|
75
|
+
height: 0,
|
|
76
|
+
x: settings.width * 0.1,
|
|
77
|
+
y: settings.height * 0.4
|
|
78
|
+
},
|
|
79
|
+
style: { ...preset.style },
|
|
80
|
+
filters: [],
|
|
81
|
+
metadata: { presetId: preset.id }
|
|
82
|
+
};
|
|
83
|
+
addItem(item);
|
|
84
|
+
},
|
|
85
|
+
[tracks, settings, currentFrame, addItem, addTrack, items, itemIdsByTrack]
|
|
86
|
+
);
|
|
87
|
+
const handleAddBlank = react.useCallback(() => {
|
|
88
|
+
handleAddText({
|
|
89
|
+
id: "blank",
|
|
90
|
+
label: "Text",
|
|
91
|
+
category: "body",
|
|
92
|
+
style: {
|
|
93
|
+
text: "Type something...",
|
|
94
|
+
fontFamily: "Inter",
|
|
95
|
+
fontSize: 48,
|
|
96
|
+
fontWeight: 400,
|
|
97
|
+
fontStyle: "normal",
|
|
98
|
+
fontColor: "#ffffff",
|
|
99
|
+
textDecoration: [],
|
|
100
|
+
textTransform: "none",
|
|
101
|
+
backgroundColor: "transparent",
|
|
102
|
+
textAlign: "center",
|
|
103
|
+
verticalText: false,
|
|
104
|
+
lineHeight: 1.3,
|
|
105
|
+
letterSpacing: 0,
|
|
106
|
+
background: null,
|
|
107
|
+
shadow: null,
|
|
108
|
+
stroke: null,
|
|
109
|
+
glow: null,
|
|
110
|
+
curve: null
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}, [handleAddText]);
|
|
114
|
+
const filteredCategories = react.useMemo(() => {
|
|
115
|
+
const categories = chunk5IHSZBVF_cjs.getCategories();
|
|
116
|
+
if (!search.trim()) return categories;
|
|
117
|
+
const q = search.toLowerCase();
|
|
118
|
+
return categories.map((cat) => ({
|
|
119
|
+
...cat,
|
|
120
|
+
presets: cat.presets.filter(
|
|
121
|
+
(p) => p.label.toLowerCase().includes(q) || p.style.fontFamily?.toLowerCase().includes(q)
|
|
122
|
+
)
|
|
123
|
+
})).filter((cat) => cat.presets.length > 0);
|
|
124
|
+
}, [search]);
|
|
125
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col", children: [
|
|
126
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [
|
|
127
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1", children: [
|
|
128
|
+
/* @__PURE__ */ jsxRuntime.jsx(SearchIcon, { className: "absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground pointer-events-none" }),
|
|
129
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
130
|
+
"input",
|
|
131
|
+
{
|
|
132
|
+
placeholder: "Search presets...",
|
|
133
|
+
value: search,
|
|
134
|
+
onChange: (e) => setSearch(e.target.value),
|
|
135
|
+
className: "w-full pl-7 pr-7 h-7 text-xs bg-muted/40 rounded-md border-none outline-none focus:ring-1 focus:ring-ring"
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
search && /* @__PURE__ */ jsxRuntime.jsx(
|
|
139
|
+
"button",
|
|
140
|
+
{
|
|
141
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 opacity-50 hover:opacity-100",
|
|
142
|
+
onClick: () => setSearch(""),
|
|
143
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(XIcon, { className: "size-3" })
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
] }),
|
|
147
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
148
|
+
"button",
|
|
149
|
+
{
|
|
150
|
+
onClick: handleAddBlank,
|
|
151
|
+
className: "flex size-7 shrink-0 items-center justify-center rounded-md bg-primary/10 hover:bg-primary/20 text-primary transition-colors",
|
|
152
|
+
title: "Add blank text",
|
|
153
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(PlusIcon, { className: "size-3.5" })
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
] }),
|
|
157
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: filteredCategories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 py-12", children: [
|
|
158
|
+
/* @__PURE__ */ jsxRuntime.jsx(TypeIcon, { className: "size-6 opacity-20" }),
|
|
159
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs opacity-40", children: [
|
|
160
|
+
'No presets match "',
|
|
161
|
+
search,
|
|
162
|
+
'"'
|
|
163
|
+
] })
|
|
164
|
+
] }) : filteredCategories.map((category) => {
|
|
165
|
+
const isCollapsed = collapsedCategories.has(category.id);
|
|
166
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
167
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
168
|
+
"button",
|
|
169
|
+
{
|
|
170
|
+
onClick: () => toggleCategory(category.id),
|
|
171
|
+
className: "flex w-full items-center gap-1.5 px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground hover:text-foreground transition-colors",
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
174
|
+
ChevronDownIcon,
|
|
175
|
+
{
|
|
176
|
+
className: ui.cn(
|
|
177
|
+
"size-3 transition-transform",
|
|
178
|
+
isCollapsed && "-rotate-90"
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
category.name,
|
|
183
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto text-[9px] opacity-50", children: category.presets.length })
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
!isCollapsed && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-2 flex flex-col gap-1.5", children: category.presets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
188
|
+
PresetCard,
|
|
189
|
+
{
|
|
190
|
+
preset,
|
|
191
|
+
onClick: () => handleAddText(preset)
|
|
192
|
+
},
|
|
193
|
+
preset.id
|
|
194
|
+
)) })
|
|
195
|
+
] }, category.id);
|
|
196
|
+
}) })
|
|
197
|
+
] });
|
|
198
|
+
}
|
|
199
|
+
function hexToRgba(hex, alpha) {
|
|
200
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
201
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
202
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
203
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
204
|
+
}
|
|
205
|
+
function buildPreviewShadow(shadow, scale) {
|
|
206
|
+
if (!shadow) return void 0;
|
|
207
|
+
const rad = (shadow.angle ?? 135) * Math.PI / 180;
|
|
208
|
+
const dist = (shadow.distance ?? 4) * scale;
|
|
209
|
+
const ox = Math.round(Math.cos(rad) * dist);
|
|
210
|
+
const oy = Math.round(Math.sin(rad) * dist);
|
|
211
|
+
const blur = (shadow.blur ?? 0) * scale;
|
|
212
|
+
const alpha = (shadow.opacity ?? 100) / 100;
|
|
213
|
+
const color = shadow.color?.startsWith("#") ? hexToRgba(shadow.color, alpha) : shadow.color;
|
|
214
|
+
return `${ox}px ${oy}px ${blur}px ${color}`;
|
|
215
|
+
}
|
|
216
|
+
function buildPreviewGlow(glow, scale) {
|
|
217
|
+
if (!glow) return void 0;
|
|
218
|
+
const alpha = (glow.intensity ?? 50) / 100;
|
|
219
|
+
const color = glow.color?.startsWith("#") ? hexToRgba(glow.color, alpha) : glow.color;
|
|
220
|
+
const blur = (glow.range ?? 10) * scale;
|
|
221
|
+
return `${(glow.offsetX ?? 0) * scale}px ${(glow.offsetY ?? 0) * scale}px ${blur}px ${color}`;
|
|
222
|
+
}
|
|
223
|
+
function PresetCard({
|
|
224
|
+
preset,
|
|
225
|
+
onClick
|
|
226
|
+
}) {
|
|
227
|
+
const s = preset.style;
|
|
228
|
+
const scale = Math.min(s.fontSize / 3.5, 24) / s.fontSize;
|
|
229
|
+
const previewFontSize = s.fontSize * scale;
|
|
230
|
+
const bg = s.background;
|
|
231
|
+
const hasAdvancedBg = bg && bg.color;
|
|
232
|
+
const [fontLoaded, setFontLoaded] = react.useState(false);
|
|
233
|
+
const { dragProps, isDragging } = ui.useItemDrag({
|
|
234
|
+
getData: () => ({
|
|
235
|
+
itemType: "text",
|
|
236
|
+
source: {},
|
|
237
|
+
style: { ...preset.style },
|
|
238
|
+
durationSeconds: 3,
|
|
239
|
+
name: preset.label
|
|
240
|
+
})
|
|
241
|
+
});
|
|
242
|
+
react.useEffect(() => {
|
|
243
|
+
if (s.fontFamily) {
|
|
244
|
+
ui.ensureFontLoaded(s.fontFamily).then(() => setFontLoaded(true));
|
|
245
|
+
}
|
|
246
|
+
}, [s.fontFamily]);
|
|
247
|
+
const combinedTextShadow = (() => {
|
|
248
|
+
const parts = [];
|
|
249
|
+
const shadowStr = buildPreviewShadow(s.shadow, scale);
|
|
250
|
+
if (shadowStr) parts.push(shadowStr);
|
|
251
|
+
const glowStr = buildPreviewGlow(s.glow, scale);
|
|
252
|
+
if (glowStr) parts.push(glowStr);
|
|
253
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
254
|
+
})();
|
|
255
|
+
const advancedBgStyle = hasAdvancedBg ? (() => {
|
|
256
|
+
const alpha = (bg.opacity ?? 100) / 100;
|
|
257
|
+
const color = bg.color.startsWith("#") ? hexToRgba(bg.color, alpha) : bg.color;
|
|
258
|
+
return {
|
|
259
|
+
backgroundColor: color,
|
|
260
|
+
borderRadius: `${bg.borderRadius ?? 0}%`,
|
|
261
|
+
padding: `${bg.height ?? 0}% ${bg.width ?? 0}%`
|
|
262
|
+
};
|
|
263
|
+
})() : void 0;
|
|
264
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
265
|
+
"div",
|
|
266
|
+
{
|
|
267
|
+
...dragProps,
|
|
268
|
+
onClick,
|
|
269
|
+
className: ui.cn(
|
|
270
|
+
"group rounded-lg border border-border/50 bg-muted/10 hover:bg-muted/40 overflow-hidden transition-colors text-left w-full cursor-grab active:cursor-grabbing select-none",
|
|
271
|
+
isDragging && "opacity-50"
|
|
272
|
+
),
|
|
273
|
+
children: [
|
|
274
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
275
|
+
"div",
|
|
276
|
+
{
|
|
277
|
+
className: "relative flex items-center justify-center h-12 px-3 overflow-hidden",
|
|
278
|
+
style: {
|
|
279
|
+
background: !hasAdvancedBg ? "linear-gradient(135deg, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0.08) 100%)" : void 0
|
|
280
|
+
},
|
|
281
|
+
children: [
|
|
282
|
+
advancedBgStyle && /* @__PURE__ */ jsxRuntime.jsx(
|
|
283
|
+
"div",
|
|
284
|
+
{
|
|
285
|
+
className: "absolute inset-0 flex items-center justify-center",
|
|
286
|
+
style: advancedBgStyle
|
|
287
|
+
}
|
|
288
|
+
),
|
|
289
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
290
|
+
"span",
|
|
291
|
+
{
|
|
292
|
+
className: "truncate max-w-full relative z-[1]",
|
|
293
|
+
style: {
|
|
294
|
+
fontFamily: fontLoaded ? s.fontFamily : "inherit",
|
|
295
|
+
fontSize: previewFontSize,
|
|
296
|
+
fontWeight: s.fontWeight,
|
|
297
|
+
fontStyle: s.fontStyle || void 0,
|
|
298
|
+
color: s.fontColor,
|
|
299
|
+
letterSpacing: s.letterSpacing ? `${s.letterSpacing * scale}px` : void 0,
|
|
300
|
+
textShadow: combinedTextShadow,
|
|
301
|
+
textDecoration: s.textDecoration?.includes("underline") ? "underline" : s.textDecoration?.includes("line-through") ? "line-through" : void 0,
|
|
302
|
+
textTransform: s.textTransform || void 0,
|
|
303
|
+
WebkitTextStroke: s.stroke ? `${Math.max(0.5, s.stroke.width * scale)}px ${s.stroke.color}` : void 0
|
|
304
|
+
},
|
|
305
|
+
children: s.text
|
|
306
|
+
}
|
|
307
|
+
)
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
),
|
|
311
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-2.5 py-1.5 border-t border-border/30", children: [
|
|
312
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-medium truncate", children: preset.label }),
|
|
313
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[9px] opacity-40 ml-auto shrink-0", children: s.fontFamily })
|
|
314
|
+
] })
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module.exports = TextPanel;
|