@meddleware/token-deployer-ui 0.0.2 → 0.0.4

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": "@meddleware/token-deployer-ui",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": { "default": "./src/index.ts" }
@@ -30,6 +30,8 @@
30
30
  "verify:template": "node scripts/verify-bytecode.mjs && vitest run tests/templateArtifact.test.ts tests/templateParity.test.ts"
31
31
  },
32
32
  "dependencies": {
33
+ "@meddleware/design-tokens": "^0.1.5",
34
+ "@meddleware/ui": "^0.1.23",
33
35
  "@meddleware/wallet-adapter": "^0.0.7",
34
36
  "@meddleware/walrus-relay": "^0.1.16",
35
37
  "@mysten/bcs": "~2.1.0",
package/src/App.vue CHANGED
@@ -1,8 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import WalletBar from './components/WalletBar.vue'
3
3
  import TokenDeployerView from './components/TokenDeployerView.vue'
4
- import ColorModeControl from './components/ColorModeControl.vue'
5
- import { useColorMode } from './composables/useColorMode.js'
4
+ import { ColorModeControl, useColorMode } from '@meddleware/ui'
6
5
 
7
6
  const { mode: colorMode, set: setColorMode } = useColorMode('system')
8
7
  </script>
@@ -0,0 +1,257 @@
1
+ /* Component utility styles for TokenDeployerView and its sub-components.
2
+ Imported by index.ts so these are available in both standalone and library (dashboard) mode.
3
+ Relies on design-token CSS variables (--bg, --surface, --border, etc.) provided by the
4
+ consumer — either @meddleware/design-tokens (standalone) or the dashboard's own import. */
5
+
6
+ .card {
7
+ background: var(--surface);
8
+ border: 1px solid var(--border);
9
+ border-radius: var(--radius);
10
+ padding: 1.25rem;
11
+ margin: 1.5rem 0 1rem;
12
+ border-top: 2px solid var(--border);
13
+ }
14
+
15
+ label {
16
+ display: block;
17
+ font-weight: 600;
18
+ margin: 0.9rem 0 0.3rem;
19
+ }
20
+ .hint {
21
+ color: var(--muted);
22
+ font-weight: 400;
23
+ font-size: 0.9rem;
24
+ }
25
+
26
+ input,
27
+ select,
28
+ textarea {
29
+ width: 100%;
30
+ padding: 0.6rem 0.7rem;
31
+ font: inherit;
32
+ color: var(--text);
33
+ background: var(--bg);
34
+ border: 1px solid var(--border);
35
+ border-radius: 8px;
36
+ }
37
+ textarea {
38
+ min-height: 4rem;
39
+ resize: vertical;
40
+ }
41
+
42
+ input[aria-invalid='true'],
43
+ select[aria-invalid='true'],
44
+ textarea[aria-invalid='true'] {
45
+ border-color: var(--danger);
46
+ }
47
+
48
+ .field-error {
49
+ color: var(--danger);
50
+ font-size: 0.88rem;
51
+ margin-top: 0.3rem;
52
+ }
53
+
54
+ button {
55
+ font: inherit;
56
+ font-weight: 600;
57
+ padding: 0.6rem 1rem;
58
+ border-radius: 8px;
59
+ border: 1px solid var(--border);
60
+ background: var(--surface);
61
+ color: var(--text);
62
+ cursor: pointer;
63
+ }
64
+ button.primary {
65
+ background: var(--accent);
66
+ color: var(--accent-contrast);
67
+ border-color: transparent;
68
+ }
69
+ button:disabled {
70
+ opacity: 0.55;
71
+ cursor: not-allowed;
72
+ }
73
+
74
+ .row {
75
+ display: flex;
76
+ gap: 0.75rem;
77
+ flex-wrap: wrap;
78
+ align-items: center;
79
+ }
80
+ .grid2 {
81
+ display: grid;
82
+ grid-template-columns: 1fr;
83
+ gap: 0 1rem;
84
+ }
85
+ @media (min-width: 520px) {
86
+ .grid2 {
87
+ grid-template-columns: 1fr 1fr;
88
+ }
89
+ }
90
+
91
+ fieldset {
92
+ background: var(--lift);
93
+ border: 1px solid var(--border);
94
+ border-radius: 8px;
95
+ padding: 0.5rem 1rem 1rem;
96
+ margin: 1rem 0;
97
+ }
98
+ legend {
99
+ font-weight: 700;
100
+ padding: 0 0.4rem;
101
+ }
102
+
103
+ details {
104
+ margin: 1rem 0;
105
+ }
106
+
107
+ details > summary {
108
+ color: var(--accent);
109
+ font-weight: 700;
110
+ margin: 0.5rem 0;
111
+ padding: 0.75rem;
112
+ background: var(--lift);
113
+ border-radius: 8px;
114
+ border: 1px solid transparent;
115
+ transition: border-color 0.2s ease;
116
+ }
117
+
118
+ details > summary:hover {
119
+ border-color: var(--border);
120
+ }
121
+
122
+ details[open] > summary {
123
+ border-color: var(--border);
124
+ margin-bottom: 1rem;
125
+ }
126
+
127
+ details > fieldset {
128
+ margin-top: 0;
129
+ }
130
+
131
+ .permanent {
132
+ color: var(--danger);
133
+ font-weight: 600;
134
+ }
135
+ .mono {
136
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
137
+ word-break: break-all;
138
+ }
139
+ .badge {
140
+ display: inline-block;
141
+ font-size: 0.8rem;
142
+ padding: 0.15rem 0.5rem;
143
+ border-radius: 999px;
144
+ background: var(--border);
145
+ }
146
+
147
+ .visually-hidden {
148
+ position: absolute;
149
+ width: 1px;
150
+ height: 1px;
151
+ padding: 0;
152
+ margin: -1px;
153
+ overflow: hidden;
154
+ clip: rect(0 0 0 0);
155
+ white-space: nowrap;
156
+ border: 0;
157
+ }
158
+
159
+ .skip-link {
160
+ position: absolute;
161
+ left: 0.5rem;
162
+ top: -3rem;
163
+ z-index: 100;
164
+ background: var(--accent);
165
+ color: #fff;
166
+ padding: 0.5rem 0.9rem;
167
+ border-radius: 6px;
168
+ text-decoration: none;
169
+ transition: top 0.15s ease;
170
+ }
171
+ .skip-link:focus {
172
+ top: 0.5rem;
173
+ }
174
+ @media (prefers-reduced-motion: reduce) {
175
+ .skip-link {
176
+ transition: none;
177
+ }
178
+ }
179
+
180
+ .steps {
181
+ list-style: none;
182
+ padding: 0;
183
+ }
184
+ .steps li {
185
+ padding: 0.35rem 0;
186
+ color: var(--muted);
187
+ }
188
+ .steps li[data-state='active'] {
189
+ color: var(--text);
190
+ font-weight: 600;
191
+ }
192
+ .steps li[data-state='done'] {
193
+ color: var(--ok);
194
+ }
195
+
196
+ .spinner {
197
+ width: 1em;
198
+ height: 1em;
199
+ border: 2px solid currentColor;
200
+ border-right-color: transparent;
201
+ border-radius: 50%;
202
+ display: inline-block;
203
+ animation: spin 0.7s linear infinite;
204
+ vertical-align: -0.15em;
205
+ }
206
+ @keyframes spin {
207
+ to {
208
+ transform: rotate(360deg);
209
+ }
210
+ }
211
+ @media (prefers-reduced-motion: reduce) {
212
+ .spinner {
213
+ animation: none;
214
+ }
215
+ * {
216
+ scroll-behavior: auto;
217
+ }
218
+ }
219
+
220
+ dialog {
221
+ background: var(--surface);
222
+ border: 1px solid var(--border);
223
+ border-radius: var(--radius);
224
+ padding: 1.5rem;
225
+ width: min(420px, 90vw);
226
+ max-height: 90vh;
227
+ overflow-y: auto;
228
+ color: var(--text);
229
+ }
230
+ dialog::backdrop {
231
+ background: rgba(0, 0, 0, 0.45);
232
+ -webkit-backdrop-filter: blur(2px);
233
+ backdrop-filter: blur(2px);
234
+ }
235
+ .dialog-header {
236
+ display: flex;
237
+ justify-content: space-between;
238
+ align-items: center;
239
+ margin-bottom: 1.25rem;
240
+ }
241
+ .dialog-header h2 {
242
+ margin: 0;
243
+ font-size: 1.1rem;
244
+ }
245
+ .wallet-list {
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: 0.5rem;
249
+ margin-top: 1rem;
250
+ }
251
+ .wallet-list button {
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 0.6rem;
255
+ width: 100%;
256
+ justify-content: flex-start;
257
+ }
@@ -6,9 +6,9 @@ import { deriveStructName } from '../lib/validation.js'
6
6
  import type { Network } from '../lib/types.js'
7
7
  import LicensePicker from './LicensePicker.vue'
8
8
  import IconPicker from './IconPicker.vue'
9
- import FormField from './FormField.vue'
10
- import FormSelect from './FormSelect.vue'
11
- import FieldHint from './FieldHint.vue'
9
+ import { UiFormField, UiSelect, UiFieldHint } from '@meddleware/ui'
10
+
11
+ export type ConfigFormStep = 'identity' | 'token' | 'settings'
12
12
 
13
13
  const props = defineProps<{
14
14
  form: FormModel
@@ -16,8 +16,9 @@ const props = defineProps<{
16
16
  canProceed: boolean
17
17
  network: Network
18
18
  connected: boolean
19
+ formStep: ConfigFormStep
19
20
  }>()
20
- const emit = defineEmits<{ (e: 'submit'): void }>()
21
+ const emit = defineEmits<{ (e: 'next'): void; (e: 'back'): void }>()
21
22
 
22
23
  const structPreview = computed(() => {
23
24
  const m = props.form.moduleName.trim()
@@ -41,152 +42,162 @@ function onBlur(key: string): void {
41
42
  </script>
42
43
 
43
44
  <template>
44
- <form novalidate @submit.prevent="emit('submit')">
45
- <fieldset>
46
- <legend>Package identity <span class="permanent">(permanent)</span></legend>
47
- <div class="grid2">
48
- <FormField id="package" label="Package name" :error="visibleError('packageName')" v-slot="{ attrs }">
49
- <input v-bind="attrs" v-model="form.packageName" placeholder="my_token" autocomplete="off" @blur="onBlur('packageName')" />
50
- </FormField>
51
- <FormField id="module" label="Module name" :error="visibleError('moduleName')" v-slot="{ attrs }">
52
- <input v-bind="attrs" v-model="form.moduleName" placeholder="mytoken" autocomplete="off" @blur="onBlur('moduleName')" />
53
- </FormField>
54
- </div>
55
- <p class="hint" aria-live="polite">
56
- <template v-if="identifierError">
57
- Fix the package and module names to preview the coin type.
58
- </template>
59
- <template v-else>
60
- Coin type will be <span class="mono">{{ coinTypePreview }}</span
61
- >. The witness struct <span class="mono">{{ structPreview }}</span> is derived from
62
- the module name (a Sui requirement).
63
- </template>
64
- </p>
65
- </fieldset>
66
-
67
- <fieldset>
68
- <legend>Token details</legend>
69
-
70
- <div class="grid2">
71
- <FormField id="name" label="Token name" :error="visibleError('name')" v-slot="{ attrs }">
72
- <input v-bind="attrs" v-model="form.name" autocomplete="off" @blur="onBlur('name')" />
73
- </FormField>
74
- <FormField id="symbol" label="Symbol" :error="visibleError('symbol')" v-slot="{ attrs }">
75
- <input v-bind="attrs" v-model="form.symbol" autocomplete="off" @blur="onBlur('symbol')" />
76
- </FormField>
77
- </div>
78
-
79
- <FormField id="description" label="Description" :error="visibleError('description')">
80
- <template #label-suffix>&nbsp;<span class="hint">(optional)</span></template>
81
- <template #default="{ attrs }">
82
- <textarea v-bind="attrs" v-model="form.description" @blur="onBlur('description')" />
83
- </template>
84
- </FormField>
85
-
86
- <FormField id="decimals" label="Decimals" :error="visibleError('decimals')">
87
- <template #label-suffix>&nbsp;<span class="permanent">(permanent)</span></template>
88
- <template #default="{ attrs }">
89
- <input v-bind="attrs" v-model="form.decimals" inputmode="numeric" @blur="onBlur('decimals')" />
90
- </template>
91
- </FormField>
92
-
93
- <IconPicker
94
- v-model="form.iconUrl"
95
- :network="network"
96
- :connected="connected"
97
- :error="visibleError('iconUrl')"
98
- @blur="onBlur('iconUrl')"
99
- />
100
- </fieldset>
101
-
102
- <details>
103
- <summary>Advanced options</summary>
45
+ <form novalidate @submit.prevent="emit('next')">
46
+
47
+ <!-- Step 1: Identity -->
48
+ <template v-if="formStep === 'identity'">
49
+ <fieldset>
50
+ <legend>Package identity <span class="permanent">(permanent)</span></legend>
51
+ <div class="grid2">
52
+ <UiFormField id="package" label="Package name" :error="visibleError('packageName')" v-slot="{ attrs }">
53
+ <input v-bind="attrs" v-model="form.packageName" placeholder="my_token" autocomplete="off" @blur="onBlur('packageName')" />
54
+ </UiFormField>
55
+ <UiFormField id="module" label="Module name" :error="visibleError('moduleName')" v-slot="{ attrs }">
56
+ <input v-bind="attrs" v-model="form.moduleName" placeholder="mytoken" autocomplete="off" @blur="onBlur('moduleName')" />
57
+ </UiFormField>
58
+ </div>
59
+ <p class="hint" aria-live="polite">
60
+ <template v-if="identifierError">
61
+ Fix the package and module names to preview the coin type.
62
+ </template>
63
+ <template v-else>
64
+ Coin type will be <span class="mono">{{ coinTypePreview }}</span
65
+ >. The witness struct <span class="mono">{{ structPreview }}</span> is derived from
66
+ the module name (a Sui requirement).
67
+ </template>
68
+ </p>
69
+ </fieldset>
70
+ </template>
71
+
72
+ <!-- Step 2: Token -->
73
+ <template v-if="formStep === 'token'">
74
+ <fieldset>
75
+ <legend>Token details</legend>
76
+
77
+ <div class="grid2">
78
+ <UiFormField id="name" label="Token name" :error="visibleError('name')" v-slot="{ attrs }">
79
+ <input v-bind="attrs" v-model="form.name" autocomplete="off" @blur="onBlur('name')" />
80
+ </UiFormField>
81
+ <UiFormField id="symbol" label="Symbol" :error="visibleError('symbol')" v-slot="{ attrs }">
82
+ <input v-bind="attrs" v-model="form.symbol" autocomplete="off" @blur="onBlur('symbol')" />
83
+ </UiFormField>
84
+ </div>
85
+
86
+ <UiFormField id="description" label="Description" :error="visibleError('description')">
87
+ <template #label-suffix>&nbsp;<span class="hint">(optional)</span></template>
88
+ <template #default="{ attrs }">
89
+ <textarea v-bind="attrs" v-model="form.description" @blur="onBlur('description')" />
90
+ </template>
91
+ </UiFormField>
92
+
93
+ <UiFormField id="decimals" label="Decimals" :error="visibleError('decimals')">
94
+ <template #label-suffix>&nbsp;<span class="permanent">(permanent)</span></template>
95
+ <template #default="{ attrs }">
96
+ <input v-bind="attrs" v-model="form.decimals" inputmode="numeric" @blur="onBlur('decimals')" />
97
+ </template>
98
+ </UiFormField>
99
+
100
+ <IconPicker
101
+ v-model="form.iconUrl"
102
+ :network="network"
103
+ :connected="connected"
104
+ :error="visibleError('iconUrl')"
105
+ @blur="onBlur('iconUrl')"
106
+ />
107
+ </fieldset>
108
+ </template>
109
+
110
+ <!-- Step 3: Settings -->
111
+ <template v-if="formStep === 'settings'">
104
112
  <fieldset>
105
- <legend class="visually-hidden">Advanced options</legend>
113
+ <legend>Supply &amp; policies</legend>
106
114
 
107
- <FormField id="supply" label="Initial supply" :error="visibleError('initialSupply')">
115
+ <UiFormField id="supply" label="Initial supply" :error="visibleError('initialSupply')">
108
116
  <template #label-suffix>
109
- <FieldHint field-id="supply">
117
+ <UiFieldHint field-id="supply">
110
118
  The number of whole tokens to mint and send to the recipient at deployment.
111
119
  Uses your chosen decimal precision — with 9 decimals, 1 token = 1,000,000,000 base units on-chain.
112
120
  Enter <b>0</b> or leave blank for no initial supply.
113
121
  You can mint more later if you choose the Mintable supply policy.
114
- </FieldHint>
122
+ </UiFieldHint>
115
123
  </template>
116
124
  <template #default="{ attrs }">
117
125
  <input v-bind="attrs" v-model="form.initialSupply" inputmode="numeric" placeholder="0" @blur="onBlur('initialSupply')" />
118
126
  </template>
119
- </FormField>
127
+ </UiFormField>
120
128
 
121
129
  <div class="grid2">
122
- <FormField id="supply-policy" label="Supply">
130
+ <UiFormField id="supply-policy" label="Supply">
123
131
  <template #label-suffix>
124
- <FieldHint field-id="supply-policy">
132
+ <UiFieldHint field-id="supply-policy">
125
133
  <b>Mintable</b>: you keep the TreasuryCap and can mint more tokens at any time.<br />
126
134
  <b>Fixed</b>: the TreasuryCap is frozen — the total supply is sealed at deployment.
127
- </FieldHint>
135
+ </UiFieldHint>
128
136
  </template>
129
137
  <template #default="{ attrs }">
130
- <FormSelect v-bind="attrs" v-model="form.supplyPolicy">
138
+ <UiSelect v-bind="attrs" v-model="form.supplyPolicy">
131
139
  <option value="mintable">Mintable</option>
132
140
  <option value="fixed">Fixed</option>
133
- </FormSelect>
141
+ </UiSelect>
134
142
  </template>
135
- </FormField>
136
- <FormField id="metadata-policy" label="Metadata">
143
+ </UiFormField>
144
+ <UiFormField id="metadata-policy" label="Metadata">
137
145
  <template #label-suffix>
138
- <FieldHint field-id="metadata-policy">
146
+ <UiFieldHint field-id="metadata-policy">
139
147
  <b>Updatable</b>: you keep the MetadataCap and can later change the name, symbol,
140
148
  description, or icon.<br />
141
149
  <b>Frozen</b>: the MetadataCap is discarded — all metadata is permanently locked.
142
- </FieldHint>
150
+ </UiFieldHint>
143
151
  </template>
144
152
  <template #default="{ attrs }">
145
- <FormSelect v-bind="attrs" v-model="form.metadataPolicy">
153
+ <UiSelect v-bind="attrs" v-model="form.metadataPolicy">
146
154
  <option value="updatable">Updatable</option>
147
155
  <option value="frozen">Frozen</option>
148
- </FormSelect>
156
+ </UiSelect>
149
157
  </template>
150
- </FormField>
158
+ </UiFormField>
151
159
  </div>
152
160
 
153
- <FormField id="package-policy" label="Package code">
161
+ <UiFormField id="package-policy" label="Package code">
154
162
  <template #label-suffix>
155
- <FieldHint field-id="package-policy">
163
+ <UiFieldHint field-id="package-policy">
156
164
  <b>Immutable</b>: the UpgradeCap is burned — the on-chain code is sealed.
157
165
  Recommended for trust and auditability.<br />
158
166
  <b>Upgradeable</b>: you keep the UpgradeCap and can push code changes to the package
159
167
  after deployment.
160
- </FieldHint>
168
+ </UiFieldHint>
161
169
  </template>
162
170
  <template #default="{ attrs }">
163
- <FormSelect v-bind="attrs" v-model="form.packagePolicy">
171
+ <UiSelect v-bind="attrs" v-model="form.packagePolicy">
164
172
  <option value="immutable">Immutable</option>
165
173
  <option value="upgradeable">Upgradeable</option>
166
- </FormSelect>
174
+ </UiSelect>
167
175
  </template>
168
- </FormField>
176
+ </UiFormField>
169
177
 
170
- <FormField id="recipient" label="Recipient" :error="visibleError('recipient')">
178
+ <UiFormField id="recipient" label="Recipient" :error="visibleError('recipient')">
171
179
  <template #label-suffix>
172
- <FieldHint field-id="recipient">
180
+ <UiFieldHint field-id="recipient">
173
181
  The Sui address that receives the TreasuryCap, MetadataCap, UpgradeCap (if kept),
174
182
  and any initial supply minted at deployment.
175
183
  Leave blank to send everything to the connected wallet.
176
184
  Enter a different address to separate the deployer wallet from the custody wallet.
177
- </FieldHint>
185
+ </UiFieldHint>
178
186
  </template>
179
187
  <template #default="{ attrs }">
180
188
  <input v-bind="attrs" v-model="form.recipient" placeholder="0x…" autocomplete="off" @blur="onBlur('recipient')" />
181
189
  </template>
182
- </FormField>
190
+ </UiFormField>
183
191
 
184
192
  <LicensePicker v-model="form.license" @name="form.licenseName = $event" />
185
193
  </fieldset>
186
- </details>
194
+ </template>
187
195
 
188
196
  <div class="row" style="margin-top: 1rem">
189
- <button type="submit" class="primary" :disabled="!canProceed">Review &amp; deploy</button>
197
+ <button v-if="formStep !== 'identity'" type="button" @click="emit('back')">Back</button>
198
+ <button type="submit" class="primary" :disabled="!canProceed">
199
+ {{ formStep === 'settings' ? 'Review &amp; deploy' : 'Next' }}
200
+ </button>
190
201
  </div>
191
202
  </form>
192
203
  </template>
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { ref } from 'vue'
3
3
  import type { PublishResult, TokenConfig } from '../lib/types.js'
4
- import AppNotice from './AppNotice.vue'
4
+ import { UiNotice } from '@meddleware/ui'
5
5
  import { buildPackageFiles } from '../lib/generatePackage.js'
6
6
  import { fetchLicenseText } from '../lib/licenses.js'
7
7
  import { createRepoAndPush } from '../lib/github.js'
@@ -91,10 +91,10 @@ async function push(): Promise<void> {
91
91
  <span v-if="progress" class="hint" aria-live="polite">{{ progress }}</span>
92
92
  </div>
93
93
 
94
- <AppNotice v-if="error" type="error">{{ error }}</AppNotice>
95
- <AppNotice v-if="doneUrl" type="ok">
94
+ <UiNotice v-if="error" type="error">{{ error }}</UiNotice>
95
+ <UiNotice v-if="doneUrl" type="ok">
96
96
  Pushed: <a :href="doneUrl" target="_blank" rel="noopener noreferrer">{{ doneUrl }}</a>
97
- </AppNotice>
97
+ </UiNotice>
98
98
  </div>
99
99
  </div>
100
100
  </template>