Skip to content

Commit 143d816

Browse files
committed
style(editor): finish the bedrock palette, attributes and font spacing
1 parent bede7fc commit 143d816

6 files changed

Lines changed: 123 additions & 63 deletions

File tree

resources/css/editor.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2404,13 +2404,16 @@ body {
24042404
src: url('/editor/font/Mojang-Regular.ttf') format('truetype');
24052405
font-weight: normal;
24062406
font-style: normal;
2407+
/* Everything but U+0020: the font measures the space as zero wide. */
2408+
unicode-range: U+0021-10FFFF;
24072409
}
24082410

24092411
@font-face {
24102412
font-family: 'Mojang';
24112413
src: url('/editor/font/Mojang-Bold.ttf') format('truetype');
24122414
font-weight: bold;
24132415
font-style: normal;
2416+
unicode-range: U+0021-10FFFF;
24142417
}
24152418

24162419
.item-tooltip {

resources/js/components/visual/AttributeEditor.tsx

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -44,46 +44,57 @@ export function AttributeEditor({ attributes, onChange }: AttributeEditorProps)
4444
<section>
4545
<div className="editor-field">
4646
<span className="section-heading">Item flags</span>
47-
{ITEM_FLAGS.map(flag => (
48-
<label key={flag} className="editor-row">
49-
<input
50-
type="checkbox"
51-
checked={flags[flag] === true}
52-
onChange={event => update({ flags: { ...flags, [flag]: event.target.checked } })}
53-
/>
54-
{flag}
55-
</label>
56-
))}
47+
<div className="attributes-list">
48+
{ITEM_FLAGS.map(flag => (
49+
<label key={flag} className="attribute-item">
50+
<input
51+
type="checkbox"
52+
checked={flags[flag] === true}
53+
onChange={event => update({ flags: { ...flags, [flag]: event.target.checked } })}
54+
/>
55+
<span className="attribute-type-badge">FLAG</span>
56+
<span className="attribute-value">{flag}</span>
57+
</label>
58+
))}
59+
</div>
5760
</div>
5861

5962
<div className="editor-field">
6063
<span className="section-heading">Enchantments</span>
6164

62-
{Object.entries(enchantments).map(([name, level]) => (
63-
<div key={name} className="editor-row">
64-
<span style={{ flex: 1, minWidth: 0 }}>{name}</span>
65-
<input
66-
type="number"
67-
min={1}
68-
value={level}
69-
onChange={event => update({ enchantments: { ...enchantments, [name]: Number(event.target.value) } })}
70-
aria-label={`${name} level`}
71-
className="inline-input" style={{ width: '70px' }}
72-
/>
73-
<button
74-
type="button"
75-
onClick={() => {
76-
const next = { ...enchantments };
77-
delete next[name];
78-
update({ enchantments: next });
79-
}}
80-
aria-label={`Remove ${name}`}
81-
className="tab-close"
82-
>
83-
x
84-
</button>
85-
</div>
86-
))}
65+
<div className="attributes-list">
66+
{Object.entries(enchantments).map(([name, level]) => (
67+
<div key={name} className="attribute-item">
68+
<span className="attribute-type-badge">ENCH</span>
69+
<span className="attribute-value">{name}</span>
70+
<input
71+
type="number"
72+
min={1}
73+
value={level}
74+
onChange={event =>
75+
update({ enchantments: { ...enchantments, [name]: Number(event.target.value) } })
76+
}
77+
aria-label={`${name} level`}
78+
className="inline-input"
79+
style={{ width: '70px' }}
80+
/>
81+
<div className="attribute-actions">
82+
<button
83+
type="button"
84+
className="attribute-btn-delete"
85+
title="Delete"
86+
onClick={() => {
87+
const next = { ...enchantments };
88+
delete next[name];
89+
update({ enchantments: next });
90+
}}
91+
>
92+
🗑️
93+
</button>
94+
</div>
95+
</div>
96+
))}
97+
</div>
8798

8899
<select
89100
value=""

resources/js/components/visual/FormBuilder.tsx

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ interface FormBuilderProps {
1111
onChange: (source: string) => void;
1212
}
1313

14+
const COMPONENT_PALETTE: { type: string; icon: string; name: string; description: string }[] = [
15+
{ type: 'LABEL', icon: '📝', name: 'Label', description: 'Informational text' },
16+
{ type: 'INPUT', icon: '✏️', name: 'Text Field', description: 'Text input' },
17+
{ type: 'DROPDOWN', icon: '📋', name: 'Dropdown', description: 'List of options' },
18+
{ type: 'TOGGLE', icon: '🔘', name: 'Toggle', description: 'True/False' },
19+
{ type: 'SLIDER', icon: '🎚️', name: 'Slider', description: 'Numeric value' },
20+
{ type: 'STEPSLIDER', icon: '📊', name: 'Step Slider', description: 'Preset steps' },
21+
];
22+
1423
const FORM_TYPES: { value: string; label: string }[] = [
1524
{ value: 'SIMPLE', label: 'SIMPLE - Button List' },
1625
{ value: 'MODAL', label: 'MODAL - Dialog (2 Buttons)' },
@@ -48,6 +57,12 @@ export function FormBuilder({ source, onChange }: FormBuilderProps) {
4857
const isCustom = menu.type.toUpperCase() === 'CUSTOM';
4958
const atModalLimit = menu.type.toUpperCase() === 'MODAL' && Object.keys(menu.buttons).length >= 2;
5059

60+
const addComponent = (type: string): void => {
61+
const key = nextKey(menu.components, 'component');
62+
commit({ ...menu, components: { ...menu.components, [key]: { type, text: '' } } });
63+
setSelected({ kind: 'component', key });
64+
};
65+
5166
const addElement = (): void => {
5267
if (isCustom) {
5368
const key = nextKey(menu.components, 'component');
@@ -64,9 +79,9 @@ export function FormBuilder({ source, onChange }: FormBuilderProps) {
6479

6580
return (
6681
<div className="bedrock-form-builder">
67-
<div className="bedrock-form-preview-main">
68-
<div className="bedrock-form-screen">
69-
<div className="bedrock-screen-content">
82+
<div className="bedrock-mobile-preview">
83+
<div className="bedrock-mobile-screen">
84+
<div className="bedrock-mobile-content">
7085
<div
7186
className="bedrock-form-title"
7287
dangerouslySetInnerHTML={{ __html: parseMiniMessage(menu.menuName || 'Form Title') }}
@@ -82,7 +97,7 @@ export function FormBuilder({ source, onChange }: FormBuilderProps) {
8297
))}
8398
</div>
8499

85-
<div className="bedrock-form-elements">
100+
<div className={isCustom ? 'bedrock-form-components' : 'bedrock-form-buttons'}>
86101
{isCustom
87102
? Object.entries(menu.components).map(([key, component]) => (
88103
<div
@@ -187,17 +202,42 @@ export function FormBuilder({ source, onChange }: FormBuilderProps) {
187202
/>
188203
</div>
189204

190-
<div className="bedrock-setting-section">
191-
<button
192-
type="button"
193-
className="btn btn-primary"
194-
style={{ width: '100%' }}
195-
disabled={atModalLimit}
196-
onClick={addElement}
197-
>
198-
+ Add {isCustom ? 'Component' : 'Button'}
199-
</button>
200-
{atModalLimit && <p className="empty-note">A modal form shows exactly two buttons.</p>}
205+
<div className="bedrock-palette-section">
206+
{isCustom ? (
207+
COMPONENT_PALETTE.map(entry => (
208+
<div
209+
key={entry.type}
210+
className="bedrock-palette-item"
211+
role="button"
212+
tabIndex={0}
213+
onClick={() => addComponent(entry.type)}
214+
onKeyDown={event => event.key === 'Enter' && addComponent(entry.type)}
215+
>
216+
<div className="bedrock-palette-icon">{entry.icon}</div>
217+
<div className="bedrock-palette-info">
218+
<div className="bedrock-palette-label">{entry.name}</div>
219+
<div className="empty-note">{entry.description}</div>
220+
</div>
221+
</div>
222+
))
223+
) : (
224+
<div
225+
className="bedrock-palette-item"
226+
role="button"
227+
tabIndex={0}
228+
aria-disabled={atModalLimit}
229+
onClick={() => !atModalLimit && addElement()}
230+
onKeyDown={event => event.key === 'Enter' && !atModalLimit && addElement()}
231+
>
232+
<div className="bedrock-palette-icon">🔘</div>
233+
<div className="bedrock-palette-info">
234+
<div className="bedrock-palette-label">Add Empty Button</div>
235+
<div className="empty-note">
236+
{atModalLimit ? 'A modal form shows exactly two buttons.' : 'A button that closes the form'}
237+
</div>
238+
</div>
239+
</div>
240+
)}
201241
</div>
202242
</div>
203243

resources/js/components/visual/FormattedInput.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export function FormattedInput({ value, onChange, multiline, rows = 3, placehold
7272

7373
return (
7474
<div>
75-
<div className="format-toolbar">
75+
<div className="minimessage-toolbar">
7676
<button
7777
type="button"
7878
onClick={() => setShowColors(open => !open)}
@@ -94,7 +94,7 @@ export function FormattedInput({ value, onChange, multiline, rows = 3, placehold
9494
</div>
9595

9696
{showColors && (
97-
<div className="swatch-row">
97+
<div className="color-codes">
9898
{COLORS.map(tag => (
9999
<button
100100
key={tag.name}
@@ -103,7 +103,7 @@ export function FormattedInput({ value, onChange, multiline, rows = 3, placehold
103103
title={tag.name}
104104
aria-label={tag.name}
105105
style={{ backgroundColor: tag.swatch }}
106-
className="swatch"
106+
className="color-code-btn"
107107
/>
108108
))}
109109
</div>

resources/js/components/visual/MenuSettings.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,31 @@ export function MenuSettings({ menu, onChange }: MenuSettingsProps) {
1313
const update = (patch: Partial<VisualJavaMenu>): void => onChange({ ...menu, ...patch });
1414

1515
return (
16-
<section className="item-editor-section">
17-
<h3 className="section-heading">Menu settings</h3>
16+
<section className="menu-settings-container">
17+
1818

19-
<div className="editor-field">
20-
Title
19+
<div className="menu-setting-section">
20+
<span className="menu-setting-label">Title</span>
2121
<FormattedInput ariaLabel="Menu title" value={menu.title} onChange={title => update({ title })} />
2222
</div>
2323

24-
<label>
25-
Open command
24+
<label className="menu-setting-section">
25+
<span className="menu-setting-label">Open command</span>
2626
<input
2727
value={menu.openCommand}
2828
onChange={event => update({ openCommand: event.target.value })}
2929
placeholder="/menu"
30-
className={inputClass}
30+
className="menu-setting-input"
3131
/>
3232
</label>
3333

34-
<label>
35-
Open permission
34+
<label className="menu-setting-section">
35+
<span className="menu-setting-label">Open permission</span>
3636
<input
3737
value={menu.openPermission}
3838
onChange={event => update({ openPermission: event.target.value })}
3939
placeholder="bluemenu.menu.example"
40-
className={inputClass}
40+
className="menu-setting-input"
4141
/>
4242
</label>
4343

resources/js/editor/__tests__/miniMessage.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ describe('parseMiniMessage', () => {
3232
expect(html).toContain('&gt;');
3333
});
3434

35+
it('keeps the space between two words', () => {
36+
const html = parseMiniMessage('two words');
37+
38+
expect(html.match(/>[ ]</g)).toHaveLength(1);
39+
});
40+
3541
it('labels empty text instead of rendering nothing', () => {
3642
expect(parseMiniMessage('')).toContain('Empty');
3743
});

0 commit comments

Comments
 (0)