@wonderlandlabs-pixi-ux/button 1.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.
- package/CHANGELOG.md +9 -0
- package/README.md +103 -0
- package/dist/Button.stories.d.ts +12 -0
- package/dist/Button.stories.js +412 -0
- package/dist/ButtonStore.d.ts +50 -0
- package/dist/ButtonStore.js +592 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.js +100 -0
- package/package.json +30 -0
- package/src/Button.stories.ts +485 -0
- package/src/ButtonStore.ts +772 -0
- package/src/index.ts +11 -0
- package/src/types.ts +121 -0
- package/test/ButtonStore.sizing.test.ts +351 -0
- package/test/fixtures/button.sizing.styles.json +38 -0
- package/test/setupNavigator.ts +6 -0
- package/tsconfig.json +19 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @wonderlandlabs-pixi-ux/button
|
|
2
|
+
|
|
3
|
+
Composable button store built on `@wonderlandlabs-pixi-ux/box` and styled through `@wonderlandlabs-pixi-ux/style-tree`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @wonderlandlabs-pixi-ux/button @wonderlandlabs-pixi-ux/style-tree
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Application, Sprite, Assets } from 'pixi.js';
|
|
15
|
+
import { fromJSON } from '@wonderlandlabs-pixi-ux/style-tree';
|
|
16
|
+
import { ButtonStore } from '@wonderlandlabs-pixi-ux/button';
|
|
17
|
+
|
|
18
|
+
const app = new Application();
|
|
19
|
+
await app.init({ width: 800, height: 600 });
|
|
20
|
+
|
|
21
|
+
const styleTree = fromJSON({
|
|
22
|
+
button: {
|
|
23
|
+
inline: {
|
|
24
|
+
padding: { $*: { x: 12, y: 6 } },
|
|
25
|
+
borderRadius: { $*: 6 },
|
|
26
|
+
iconGap: { $*: 8 },
|
|
27
|
+
fill: {
|
|
28
|
+
$*: { color: { r: 0.2, g: 0.55, b: 0.85 }, alpha: 1 },
|
|
29
|
+
$hover: { color: { r: 0.25, g: 0.62, b: 0.9 }, alpha: 1 }
|
|
30
|
+
},
|
|
31
|
+
label: {
|
|
32
|
+
fontSize: { $*: 14 },
|
|
33
|
+
color: { $*: { r: 1, g: 1, b: 1 } },
|
|
34
|
+
alpha: { $*: 1 }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const texture = await Assets.load('/placeholder-art.png');
|
|
41
|
+
const button = new ButtonStore({
|
|
42
|
+
id: 'save',
|
|
43
|
+
mode: 'inline',
|
|
44
|
+
sprite: new Sprite(texture),
|
|
45
|
+
label: 'Save',
|
|
46
|
+
onClick: () => console.log('clicked'),
|
|
47
|
+
}, styleTree, app);
|
|
48
|
+
|
|
49
|
+
button.setPosition(120, 120);
|
|
50
|
+
app.stage.addChild(button.container);
|
|
51
|
+
button.kickoff();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Button Config
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
{
|
|
58
|
+
id: string,
|
|
59
|
+
mode?: 'icon' | 'iconVertical' | 'text' | 'inline',
|
|
60
|
+
sprite?: Sprite,
|
|
61
|
+
icon?: Container,
|
|
62
|
+
rightSprite?: Sprite,
|
|
63
|
+
rightIcon?: Container,
|
|
64
|
+
label?: string,
|
|
65
|
+
isDisabled?: boolean,
|
|
66
|
+
onClick?: () => void,
|
|
67
|
+
variant?: string,
|
|
68
|
+
bitmapFont?: string,
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Mode behavior:
|
|
73
|
+
- `icon`: icon-only.
|
|
74
|
+
- `iconVertical`: icon with label below.
|
|
75
|
+
- `text`: label-only.
|
|
76
|
+
- `inline`: icon + label in a row (optionally right icon too).
|
|
77
|
+
|
|
78
|
+
If `mode` is omitted it is inferred from available icon/label fields.
|
|
79
|
+
|
|
80
|
+
## StyleTree Expectations
|
|
81
|
+
|
|
82
|
+
State keys are read using noun paths under `button` and optional states (`hover`, `disabled`).
|
|
83
|
+
|
|
84
|
+
Common keys:
|
|
85
|
+
- `button.padding.x`, `button.padding.y`
|
|
86
|
+
- `button.borderRadius`
|
|
87
|
+
- `button.fill.color`, `button.fill.alpha`
|
|
88
|
+
- `button.stroke.color`, `button.stroke.width`, `button.stroke.alpha`
|
|
89
|
+
- `button.label.fontSize`, `button.label.color`, `button.label.alpha`
|
|
90
|
+
- `button.icon.size.x`, `button.icon.size.y`, `button.icon.alpha`
|
|
91
|
+
- Mode-specific variants: `button.inline.*`, `button.text.*`, `button.iconVertical.*`
|
|
92
|
+
|
|
93
|
+
Variant lookup inserts `variant` after `button` (for example `button.primary.inline.fill.color`).
|
|
94
|
+
|
|
95
|
+
## Main API
|
|
96
|
+
|
|
97
|
+
- `setHovered(isHovered)`
|
|
98
|
+
- `setDisabled(isDisabled)`
|
|
99
|
+
- `isHovered`
|
|
100
|
+
- `isDisabled`
|
|
101
|
+
- `mode`
|
|
102
|
+
- `getConfig()`
|
|
103
|
+
- `getPreferredSize()`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/html';
|
|
2
|
+
interface ButtonArgs {
|
|
3
|
+
mode: 'icon' | 'iconVertical' | 'text' | 'inline';
|
|
4
|
+
}
|
|
5
|
+
declare const meta: Meta<ButtonArgs>;
|
|
6
|
+
export default meta;
|
|
7
|
+
type Story = StoryObj<ButtonArgs>;
|
|
8
|
+
export declare const IconButtons: Story;
|
|
9
|
+
export declare const IconVerticalButtons: Story;
|
|
10
|
+
export declare const TextButtons: Story;
|
|
11
|
+
export declare const InlineButtons: Story;
|
|
12
|
+
export declare const AllModes: Story;
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { Application, Graphics, Sprite, Assets } from 'pixi.js';
|
|
2
|
+
import { StyleTree } from '@wonderlandlabs-pixi-ux/style-tree';
|
|
3
|
+
import { ButtonStore } from './ButtonStore';
|
|
4
|
+
/**
|
|
5
|
+
* Create a default StyleTree with button styles
|
|
6
|
+
*/
|
|
7
|
+
function createDefaultStyleTree() {
|
|
8
|
+
const tree = new StyleTree();
|
|
9
|
+
// Icon button styles
|
|
10
|
+
tree.set('button.padding.x', [], 8);
|
|
11
|
+
tree.set('button.padding.y', [], 8);
|
|
12
|
+
tree.set('button.borderRadius', [], 4);
|
|
13
|
+
tree.set('button.icon.size.x', [], 32);
|
|
14
|
+
tree.set('button.icon.size.y', [], 32);
|
|
15
|
+
tree.set('button.icon.alpha', [], 1);
|
|
16
|
+
tree.set('button.stroke.color', [], { r: 0.6, g: 0.6, b: 0.6 });
|
|
17
|
+
tree.set('button.stroke.width', [], 1);
|
|
18
|
+
tree.set('button.stroke.alpha', [], 1);
|
|
19
|
+
tree.set('button.label.fontSize', [], 11);
|
|
20
|
+
tree.set('button.label.color', [], { r: 0.2, g: 0.2, b: 0.2 });
|
|
21
|
+
tree.set('button.label.alpha', [], 0.8);
|
|
22
|
+
tree.set('button.label.padding', [], 8);
|
|
23
|
+
// Icon button hover state
|
|
24
|
+
tree.set('button.fill.color', ['hover'], { r: 0.9, g: 0.95, b: 1 });
|
|
25
|
+
tree.set('button.fill.alpha', ['hover'], 1);
|
|
26
|
+
tree.set('button.stroke.color', ['hover'], { r: 0.4, g: 0.6, b: 0.9 });
|
|
27
|
+
// Icon button disabled state
|
|
28
|
+
tree.set('button.icon.alpha', ['disabled'], 0.4);
|
|
29
|
+
tree.set('button.stroke.alpha', ['disabled'], 0.4);
|
|
30
|
+
// IconVertical button styles (icon with label below)
|
|
31
|
+
tree.set('button.iconVertical.padding.x', [], 8);
|
|
32
|
+
tree.set('button.iconVertical.padding.y', [], 8);
|
|
33
|
+
tree.set('button.iconVertical.borderRadius', [], 4);
|
|
34
|
+
tree.set('button.iconVertical.icon.size.x', [], 32);
|
|
35
|
+
tree.set('button.iconVertical.icon.size.y', [], 32);
|
|
36
|
+
tree.set('button.iconVertical.icon.alpha', [], 1);
|
|
37
|
+
tree.set('button.iconVertical.iconGap', [], 6);
|
|
38
|
+
tree.set('button.iconVertical.stroke.color', [], { r: 0.6, g: 0.6, b: 0.6 });
|
|
39
|
+
tree.set('button.iconVertical.stroke.width', [], 1);
|
|
40
|
+
tree.set('button.iconVertical.stroke.alpha', [], 1);
|
|
41
|
+
tree.set('button.iconVertical.label.fontSize', [], 11);
|
|
42
|
+
tree.set('button.iconVertical.label.color', [], { r: 0.2, g: 0.2, b: 0.2 });
|
|
43
|
+
tree.set('button.iconVertical.label.alpha', [], 0.8);
|
|
44
|
+
// IconVertical button hover state
|
|
45
|
+
tree.set('button.iconVertical.fill.color', ['hover'], { r: 0.9, g: 0.95, b: 1 });
|
|
46
|
+
tree.set('button.iconVertical.fill.alpha', ['hover'], 1);
|
|
47
|
+
tree.set('button.iconVertical.stroke.color', ['hover'], { r: 0.4, g: 0.6, b: 0.9 });
|
|
48
|
+
// IconVertical button disabled state
|
|
49
|
+
tree.set('button.iconVertical.icon.alpha', ['disabled'], 0.4);
|
|
50
|
+
tree.set('button.iconVertical.stroke.alpha', ['disabled'], 0.4);
|
|
51
|
+
tree.set('button.iconVertical.label.alpha', ['disabled'], 0.4);
|
|
52
|
+
// Text button styles
|
|
53
|
+
tree.set('button.text.padding.x', [], 16);
|
|
54
|
+
tree.set('button.text.padding.y', [], 8);
|
|
55
|
+
tree.set('button.text.borderRadius', [], 6);
|
|
56
|
+
tree.set('button.text.fill.color', [], { r: 0.2, g: 0.5, b: 0.8 });
|
|
57
|
+
tree.set('button.text.fill.alpha', [], 1);
|
|
58
|
+
tree.set('button.text.label.fontSize', [], 14);
|
|
59
|
+
tree.set('button.text.label.color', [], { r: 1, g: 1, b: 1 });
|
|
60
|
+
tree.set('button.text.label.alpha', [], 1);
|
|
61
|
+
// Text button hover state
|
|
62
|
+
tree.set('button.text.fill.color', ['hover'], { r: 0.3, g: 0.6, b: 0.9 });
|
|
63
|
+
// Text button disabled state
|
|
64
|
+
tree.set('button.text.fill.alpha', ['disabled'], 0.5);
|
|
65
|
+
tree.set('button.text.label.alpha', ['disabled'], 0.5);
|
|
66
|
+
// Inline button styles
|
|
67
|
+
tree.set('button.inline.padding.x', [], 12);
|
|
68
|
+
tree.set('button.inline.padding.y', [], 8);
|
|
69
|
+
tree.set('button.inline.borderRadius', [], 6);
|
|
70
|
+
tree.set('button.inline.iconGap', [], 8);
|
|
71
|
+
tree.set('button.inline.icon.size.x', [], 20);
|
|
72
|
+
tree.set('button.inline.icon.size.y', [], 20);
|
|
73
|
+
tree.set('button.inline.icon.alpha', [], 1);
|
|
74
|
+
tree.set('button.inline.fill.color', [], { r: 0.15, g: 0.65, b: 0.45 });
|
|
75
|
+
tree.set('button.inline.fill.alpha', [], 1);
|
|
76
|
+
tree.set('button.inline.label.fontSize', [], 14);
|
|
77
|
+
tree.set('button.inline.label.color', [], { r: 1, g: 1, b: 1 });
|
|
78
|
+
tree.set('button.inline.label.alpha', [], 1);
|
|
79
|
+
// Inline button hover state - blue 1px border
|
|
80
|
+
tree.set('button.inline.fill.color', ['hover'], { r: 0.1, g: 0.8, b: 0.6 });
|
|
81
|
+
tree.set('button.inline.stroke.color', ['hover'], { r: 0.2, g: 0.4, b: 0.9 });
|
|
82
|
+
tree.set('button.inline.stroke.width', ['hover'], 1);
|
|
83
|
+
tree.set('button.inline.stroke.alpha', ['hover'], 1);
|
|
84
|
+
// Inline button disabled state
|
|
85
|
+
tree.set('button.inline.fill.alpha', ['disabled'], 0.5);
|
|
86
|
+
tree.set('button.inline.icon.alpha', ['disabled'], 0.5);
|
|
87
|
+
tree.set('button.inline.label.alpha', ['disabled'], 0.5);
|
|
88
|
+
// Primary variant (for text/inline buttons)
|
|
89
|
+
tree.set('button.primary.text.fill.color', [], { r: 0.8, g: 0.2, b: 0.2 });
|
|
90
|
+
tree.set('button.primary.text.fill.color', ['hover'], { r: 0.9, g: 0.3, b: 0.3 });
|
|
91
|
+
return tree;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a simple icon graphic (fallback)
|
|
95
|
+
*/
|
|
96
|
+
function createIconGraphic(size, color) {
|
|
97
|
+
const g = new Graphics();
|
|
98
|
+
g.rect(0, 0, size, size);
|
|
99
|
+
g.fill(color);
|
|
100
|
+
// Add a simple shape inside
|
|
101
|
+
g.circle(size / 2, size / 2, size / 3);
|
|
102
|
+
g.fill(0xffffff);
|
|
103
|
+
return g;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create a sprite from the placeholder texture with optional tint
|
|
107
|
+
*/
|
|
108
|
+
function createIconSprite(texture, tint) {
|
|
109
|
+
const sprite = new Sprite(texture);
|
|
110
|
+
if (tint !== undefined) {
|
|
111
|
+
sprite.tint = tint;
|
|
112
|
+
}
|
|
113
|
+
return sprite;
|
|
114
|
+
}
|
|
115
|
+
const meta = {
|
|
116
|
+
title: 'Button',
|
|
117
|
+
args: {
|
|
118
|
+
mode: 'icon',
|
|
119
|
+
},
|
|
120
|
+
argTypes: {
|
|
121
|
+
mode: {
|
|
122
|
+
control: 'select',
|
|
123
|
+
options: ['icon', 'iconVertical', 'text', 'inline'],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
export default meta;
|
|
128
|
+
export const IconButtons = {
|
|
129
|
+
render: () => {
|
|
130
|
+
const wrapper = document.createElement('div');
|
|
131
|
+
wrapper.style.width = '100%';
|
|
132
|
+
wrapper.style.height = '400px';
|
|
133
|
+
wrapper.style.position = 'relative';
|
|
134
|
+
const app = new Application();
|
|
135
|
+
app.init({
|
|
136
|
+
width: 800,
|
|
137
|
+
height: 400,
|
|
138
|
+
backgroundColor: 0xf5f5f5,
|
|
139
|
+
antialias: true,
|
|
140
|
+
}).then(async () => {
|
|
141
|
+
wrapper.appendChild(app.canvas);
|
|
142
|
+
// Load placeholder texture
|
|
143
|
+
const texture = await Assets.load('/placeholder-art.png');
|
|
144
|
+
const styleTree = createDefaultStyleTree();
|
|
145
|
+
// Icon-only buttons (no labels) using sprites
|
|
146
|
+
const button1 = new ButtonStore({
|
|
147
|
+
id: 'btn-1',
|
|
148
|
+
mode: 'icon',
|
|
149
|
+
sprite: createIconSprite(texture, 0x4488cc),
|
|
150
|
+
onClick: () => console.log('Blue clicked'),
|
|
151
|
+
}, styleTree, app);
|
|
152
|
+
button1.setPosition(50, 50);
|
|
153
|
+
const button2 = new ButtonStore({
|
|
154
|
+
id: 'btn-2',
|
|
155
|
+
mode: 'icon',
|
|
156
|
+
sprite: createIconSprite(texture, 0x44cc88),
|
|
157
|
+
onClick: () => console.log('Green clicked'),
|
|
158
|
+
}, styleTree, app);
|
|
159
|
+
button2.setPosition(110, 50);
|
|
160
|
+
const button3 = new ButtonStore({
|
|
161
|
+
id: 'btn-3',
|
|
162
|
+
mode: 'icon',
|
|
163
|
+
sprite: createIconSprite(texture, 0xcc8844),
|
|
164
|
+
isDisabled: true,
|
|
165
|
+
}, styleTree, app);
|
|
166
|
+
button3.setPosition(170, 50);
|
|
167
|
+
app.stage.addChild(button1.container);
|
|
168
|
+
app.stage.addChild(button2.container);
|
|
169
|
+
app.stage.addChild(button3.container);
|
|
170
|
+
button1.kickoff();
|
|
171
|
+
button2.kickoff();
|
|
172
|
+
button3.kickoff();
|
|
173
|
+
});
|
|
174
|
+
return wrapper;
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
export const IconVerticalButtons = {
|
|
178
|
+
render: () => {
|
|
179
|
+
const wrapper = document.createElement('div');
|
|
180
|
+
wrapper.style.width = '100%';
|
|
181
|
+
wrapper.style.height = '400px';
|
|
182
|
+
wrapper.style.position = 'relative';
|
|
183
|
+
const app = new Application();
|
|
184
|
+
app.init({
|
|
185
|
+
width: 800,
|
|
186
|
+
height: 400,
|
|
187
|
+
backgroundColor: 0xf5f5f5,
|
|
188
|
+
antialias: true,
|
|
189
|
+
}).then(async () => {
|
|
190
|
+
wrapper.appendChild(app.canvas);
|
|
191
|
+
// Load placeholder texture
|
|
192
|
+
const texture = await Assets.load('/placeholder-art.png');
|
|
193
|
+
const styleTree = createDefaultStyleTree();
|
|
194
|
+
// IconVertical buttons (icon with label below) using sprites
|
|
195
|
+
const button1 = new ButtonStore({
|
|
196
|
+
id: 'btn-1',
|
|
197
|
+
mode: 'iconVertical',
|
|
198
|
+
sprite: createIconSprite(texture, 0x4488cc),
|
|
199
|
+
label: 'Blue',
|
|
200
|
+
onClick: () => console.log('Blue clicked'),
|
|
201
|
+
}, styleTree, app);
|
|
202
|
+
button1.setPosition(50, 50);
|
|
203
|
+
const button2 = new ButtonStore({
|
|
204
|
+
id: 'btn-2',
|
|
205
|
+
mode: 'iconVertical',
|
|
206
|
+
sprite: createIconSprite(texture, 0x44cc88),
|
|
207
|
+
label: 'Green',
|
|
208
|
+
onClick: () => console.log('Green clicked'),
|
|
209
|
+
}, styleTree, app);
|
|
210
|
+
button2.setPosition(130, 50);
|
|
211
|
+
const button3 = new ButtonStore({
|
|
212
|
+
id: 'btn-3',
|
|
213
|
+
mode: 'iconVertical',
|
|
214
|
+
sprite: createIconSprite(texture, 0xcc8844),
|
|
215
|
+
label: 'Orange',
|
|
216
|
+
isDisabled: true,
|
|
217
|
+
}, styleTree, app);
|
|
218
|
+
button3.setPosition(210, 50);
|
|
219
|
+
app.stage.addChild(button1.container);
|
|
220
|
+
app.stage.addChild(button2.container);
|
|
221
|
+
app.stage.addChild(button3.container);
|
|
222
|
+
button1.kickoff();
|
|
223
|
+
button2.kickoff();
|
|
224
|
+
button3.kickoff();
|
|
225
|
+
});
|
|
226
|
+
return wrapper;
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
export const TextButtons = {
|
|
230
|
+
render: () => {
|
|
231
|
+
const wrapper = document.createElement('div');
|
|
232
|
+
wrapper.style.width = '100%';
|
|
233
|
+
wrapper.style.height = '400px';
|
|
234
|
+
wrapper.style.position = 'relative';
|
|
235
|
+
const app = new Application();
|
|
236
|
+
app.init({
|
|
237
|
+
width: 800,
|
|
238
|
+
height: 400,
|
|
239
|
+
backgroundColor: 0xf5f5f5,
|
|
240
|
+
antialias: true,
|
|
241
|
+
}).then(() => {
|
|
242
|
+
wrapper.appendChild(app.canvas);
|
|
243
|
+
const styleTree = createDefaultStyleTree();
|
|
244
|
+
// Create text buttons
|
|
245
|
+
const button1 = new ButtonStore({
|
|
246
|
+
id: 'text-btn-1',
|
|
247
|
+
label: 'Submit',
|
|
248
|
+
mode: 'text',
|
|
249
|
+
onClick: () => console.log('Submit clicked'),
|
|
250
|
+
}, styleTree, app);
|
|
251
|
+
button1.setPosition(50, 50);
|
|
252
|
+
const button2 = new ButtonStore({
|
|
253
|
+
id: 'text-btn-2',
|
|
254
|
+
label: 'Cancel',
|
|
255
|
+
mode: 'text',
|
|
256
|
+
onClick: () => console.log('Cancel clicked'),
|
|
257
|
+
}, styleTree, app);
|
|
258
|
+
button2.setPosition(180, 50);
|
|
259
|
+
const button3 = new ButtonStore({
|
|
260
|
+
id: 'text-btn-3',
|
|
261
|
+
label: 'Disabled',
|
|
262
|
+
mode: 'text',
|
|
263
|
+
isDisabled: true,
|
|
264
|
+
}, styleTree, app);
|
|
265
|
+
button3.setPosition(310, 50);
|
|
266
|
+
app.stage.addChild(button1.container);
|
|
267
|
+
app.stage.addChild(button2.container);
|
|
268
|
+
app.stage.addChild(button3.container);
|
|
269
|
+
button1.kickoff();
|
|
270
|
+
button2.kickoff();
|
|
271
|
+
button3.kickoff();
|
|
272
|
+
});
|
|
273
|
+
return wrapper;
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
export const InlineButtons = {
|
|
277
|
+
render: () => {
|
|
278
|
+
const wrapper = document.createElement('div');
|
|
279
|
+
wrapper.style.width = '100%';
|
|
280
|
+
wrapper.style.height = '400px';
|
|
281
|
+
wrapper.style.position = 'relative';
|
|
282
|
+
const app = new Application();
|
|
283
|
+
app.init({
|
|
284
|
+
width: 800,
|
|
285
|
+
height: 400,
|
|
286
|
+
backgroundColor: 0xf5f5f5,
|
|
287
|
+
antialias: true,
|
|
288
|
+
}).then(async () => {
|
|
289
|
+
wrapper.appendChild(app.canvas);
|
|
290
|
+
// Load placeholder texture
|
|
291
|
+
const texture = await Assets.load('/placeholder-art.png');
|
|
292
|
+
const styleTree = createDefaultStyleTree();
|
|
293
|
+
// Row 1: Inline buttons with left icon
|
|
294
|
+
const button1 = new ButtonStore({
|
|
295
|
+
id: 'inline-btn-1',
|
|
296
|
+
sprite: createIconSprite(texture, 0x4488cc),
|
|
297
|
+
label: 'Add Item',
|
|
298
|
+
mode: 'inline',
|
|
299
|
+
onClick: () => console.log('Add Item clicked'),
|
|
300
|
+
}, styleTree, app);
|
|
301
|
+
button1.setPosition(50, 50);
|
|
302
|
+
const button2 = new ButtonStore({
|
|
303
|
+
id: 'inline-btn-2',
|
|
304
|
+
sprite: createIconSprite(texture, 0xcc4444),
|
|
305
|
+
label: 'Delete',
|
|
306
|
+
mode: 'inline',
|
|
307
|
+
onClick: () => console.log('Delete clicked'),
|
|
308
|
+
}, styleTree, app);
|
|
309
|
+
button2.setPosition(200, 50);
|
|
310
|
+
const button3 = new ButtonStore({
|
|
311
|
+
id: 'inline-btn-3',
|
|
312
|
+
sprite: createIconSprite(texture, 0x888888),
|
|
313
|
+
label: 'Disabled',
|
|
314
|
+
mode: 'inline',
|
|
315
|
+
isDisabled: true,
|
|
316
|
+
}, styleTree, app);
|
|
317
|
+
button3.setPosition(330, 50);
|
|
318
|
+
// Row 2: Inline buttons with right icon
|
|
319
|
+
const button4 = new ButtonStore({
|
|
320
|
+
id: 'inline-btn-4',
|
|
321
|
+
label: 'Dropdown',
|
|
322
|
+
rightSprite: createIconSprite(texture, 0xffffff),
|
|
323
|
+
mode: 'inline',
|
|
324
|
+
onClick: () => console.log('Dropdown clicked'),
|
|
325
|
+
}, styleTree, app);
|
|
326
|
+
button4.setPosition(50, 120);
|
|
327
|
+
const button5 = new ButtonStore({
|
|
328
|
+
id: 'inline-btn-5',
|
|
329
|
+
sprite: createIconSprite(texture, 0x4488cc),
|
|
330
|
+
label: 'Both Icons',
|
|
331
|
+
rightSprite: createIconSprite(texture, 0xffffff),
|
|
332
|
+
mode: 'inline',
|
|
333
|
+
onClick: () => console.log('Both Icons clicked'),
|
|
334
|
+
}, styleTree, app);
|
|
335
|
+
button5.setPosition(200, 120);
|
|
336
|
+
app.stage.addChild(button1.container);
|
|
337
|
+
app.stage.addChild(button2.container);
|
|
338
|
+
app.stage.addChild(button3.container);
|
|
339
|
+
app.stage.addChild(button4.container);
|
|
340
|
+
app.stage.addChild(button5.container);
|
|
341
|
+
button1.kickoff();
|
|
342
|
+
button2.kickoff();
|
|
343
|
+
button3.kickoff();
|
|
344
|
+
button4.kickoff();
|
|
345
|
+
button5.kickoff();
|
|
346
|
+
});
|
|
347
|
+
return wrapper;
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
export const AllModes = {
|
|
351
|
+
render: () => {
|
|
352
|
+
const wrapper = document.createElement('div');
|
|
353
|
+
wrapper.style.width = '100%';
|
|
354
|
+
wrapper.style.height = '400px';
|
|
355
|
+
wrapper.style.position = 'relative';
|
|
356
|
+
const app = new Application();
|
|
357
|
+
app.init({
|
|
358
|
+
width: 800,
|
|
359
|
+
height: 400,
|
|
360
|
+
backgroundColor: 0xf5f5f5,
|
|
361
|
+
antialias: true,
|
|
362
|
+
}).then(async () => {
|
|
363
|
+
wrapper.appendChild(app.canvas);
|
|
364
|
+
// Load placeholder texture
|
|
365
|
+
const texture = await Assets.load('/placeholder-art.png');
|
|
366
|
+
const styleTree = createDefaultStyleTree();
|
|
367
|
+
// Row 1: Icon button (icon only, no label)
|
|
368
|
+
const iconBtn = new ButtonStore({
|
|
369
|
+
id: 'icon-demo',
|
|
370
|
+
mode: 'icon',
|
|
371
|
+
sprite: createIconSprite(texture, 0x4488cc),
|
|
372
|
+
onClick: () => console.log('Icon clicked'),
|
|
373
|
+
}, styleTree, app);
|
|
374
|
+
iconBtn.setPosition(50, 30);
|
|
375
|
+
// Row 2: IconVertical button (icon with label below)
|
|
376
|
+
const iconVerticalBtn = new ButtonStore({
|
|
377
|
+
id: 'icon-vertical-demo',
|
|
378
|
+
mode: 'iconVertical',
|
|
379
|
+
sprite: createIconSprite(texture, 0x8844cc),
|
|
380
|
+
label: 'Vertical',
|
|
381
|
+
onClick: () => console.log('IconVertical clicked'),
|
|
382
|
+
}, styleTree, app);
|
|
383
|
+
iconVerticalBtn.setPosition(50, 100);
|
|
384
|
+
// Row 3: Text button
|
|
385
|
+
const textBtn = new ButtonStore({
|
|
386
|
+
id: 'text-demo',
|
|
387
|
+
mode: 'text',
|
|
388
|
+
label: 'Text Mode',
|
|
389
|
+
onClick: () => console.log('Text clicked'),
|
|
390
|
+
}, styleTree, app);
|
|
391
|
+
textBtn.setPosition(50, 190);
|
|
392
|
+
// Row 4: Inline button (icon + text side-by-side)
|
|
393
|
+
const inlineBtn = new ButtonStore({
|
|
394
|
+
id: 'inline-demo',
|
|
395
|
+
mode: 'inline',
|
|
396
|
+
sprite: createIconSprite(texture, 0x44cc88),
|
|
397
|
+
label: 'Inline Mode',
|
|
398
|
+
onClick: () => console.log('Inline clicked'),
|
|
399
|
+
}, styleTree, app);
|
|
400
|
+
inlineBtn.setPosition(50, 250);
|
|
401
|
+
app.stage.addChild(iconBtn.container);
|
|
402
|
+
app.stage.addChild(iconVerticalBtn.container);
|
|
403
|
+
app.stage.addChild(textBtn.container);
|
|
404
|
+
app.stage.addChild(inlineBtn.container);
|
|
405
|
+
iconBtn.kickoff();
|
|
406
|
+
iconVerticalBtn.kickoff();
|
|
407
|
+
textBtn.kickoff();
|
|
408
|
+
inlineBtn.kickoff();
|
|
409
|
+
});
|
|
410
|
+
return wrapper;
|
|
411
|
+
},
|
|
412
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { BoxTree } from '@wonderlandlabs-pixi-ux/box';
|
|
2
|
+
import { TickerForest } from '@wonderlandlabs-pixi-ux/ticker-forest';
|
|
3
|
+
import type { StyleTree } from '@wonderlandlabs-pixi-ux/style-tree';
|
|
4
|
+
import { Application, Container, type ContainerOptions, type Ticker } from 'pixi.js';
|
|
5
|
+
import type { ButtonConfig, ButtonMode } from './types';
|
|
6
|
+
type ButtonState = {
|
|
7
|
+
dirty: boolean;
|
|
8
|
+
};
|
|
9
|
+
type TickerSource = Application | {
|
|
10
|
+
ticker: Ticker;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* ButtonStore - BoxTree-based button layout with Pixi rendering.
|
|
14
|
+
*
|
|
15
|
+
* Layout model:
|
|
16
|
+
* - A root BoxTree node represents button bounds.
|
|
17
|
+
* - Child BoxTree nodes represent icon/label slots in content space.
|
|
18
|
+
* - Padding is applied at render placement time (child tree positions remain padding-free).
|
|
19
|
+
*/
|
|
20
|
+
export declare class ButtonStore extends TickerForest<ButtonState> {
|
|
21
|
+
#private;
|
|
22
|
+
readonly id: string;
|
|
23
|
+
constructor(config: ButtonConfig, styleTree: StyleTree, tickerSource: TickerSource, rootProps?: ContainerOptions);
|
|
24
|
+
get container(): Container;
|
|
25
|
+
get rect(): {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
};
|
|
31
|
+
get children(): readonly BoxTree[];
|
|
32
|
+
get isHovered(): boolean;
|
|
33
|
+
get isDisabled(): boolean;
|
|
34
|
+
get mode(): ButtonMode;
|
|
35
|
+
protected isDirty(): boolean;
|
|
36
|
+
protected clearDirty(): void;
|
|
37
|
+
markDirty(): void;
|
|
38
|
+
protected resolve(): void;
|
|
39
|
+
setHovered(isHovered: boolean): void;
|
|
40
|
+
setDisabled(isDisabled: boolean): void;
|
|
41
|
+
setPosition(x: number, y: number): void;
|
|
42
|
+
setMinSize(minWidth?: number, minHeight?: number): boolean;
|
|
43
|
+
getConfig(): ButtonConfig;
|
|
44
|
+
getPreferredSize(): {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
cleanup(): void;
|
|
49
|
+
}
|
|
50
|
+
export {};
|