nucleus-core-ts 0.9.841 → 0.9.842

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.
@@ -18,8 +18,16 @@ import { integrationsPageTheme as theme } from '../theme';
18
18
  */ export function ConnectionSwitcher() {
19
19
  const store = useIntegrationsStore();
20
20
  const labels = useLabels();
21
+ /**
22
+ * The way back does not depend on finding anything.
23
+ *
24
+ * `store.sources` is the rail's CURRENT PAGE, and the rail is unmounted the
25
+ * moment a connection is chosen — so a selected connection that is not on
26
+ * that page rendered nothing at all here: no name, and no way back to the
27
+ * list short of reloading the page. The name is what needs looking up; the
28
+ * button is not.
29
+ */ if (!store.selectedSourceId) return null;
21
30
  const source = store.sources.find((candidate)=>candidate.id === store.selectedSourceId);
22
- if (!source) return null;
23
31
  return /*#__PURE__*/ _jsxs("div", {
24
32
  className: cn('flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200/80 bg-white px-4 py-3 dark:border-slate-800 dark:bg-slate-900'),
25
33
  children: [
@@ -32,12 +40,12 @@ import { integrationsPageTheme as theme } from '../theme';
32
40
  }),
33
41
  /*#__PURE__*/ _jsx("span", {
34
42
  className: "truncate font-semibold text-base text-slate-900 dark:text-slate-100",
35
- children: source.name
43
+ children: source?.name ?? labels.connections.heading
36
44
  }),
37
- /*#__PURE__*/ _jsx("span", {
45
+ source?.baseUrl ? /*#__PURE__*/ _jsx("span", {
38
46
  className: cn(theme.list.itemMeta, 'mt-0.5'),
39
47
  children: source.baseUrl
40
- })
48
+ }) : null
41
49
  ]
42
50
  }),
43
51
  /*#__PURE__*/ _jsx("button", {
@@ -6,7 +6,7 @@ import { useLabels } from '../text/context';
6
6
  import { integrationsPageTheme } from '../theme';
7
7
  import { FieldMappingRow } from './FieldMappingRow';
8
8
  import { EMPTY_FIELD_MAPPING, FIELD_MAPPING_COLUMNS, FIELD_MAPPING_GRID, PARENT_PREFIX } from './fieldMappingOptions';
9
- import { Notice } from './Primitives';
9
+ import { Notice, Section } from './Primitives';
10
10
  /**
11
11
  * The rows that say which incoming value becomes which column.
12
12
  *
@@ -33,28 +33,20 @@ export function FieldMappingRows({ rows, sourceFields, targetFields, disabled, o
33
33
  if (!moving || !displaced) return;
34
34
  onChange(rows.with(index, displaced).with(target, moving));
35
35
  };
36
- return /*#__PURE__*/ _jsxs("section", {
37
- className: "flex flex-col gap-3",
36
+ return /*#__PURE__*/ _jsxs(Section, {
37
+ action: /*#__PURE__*/ _jsx("button", {
38
+ className: theme.button.secondary,
39
+ disabled: disabled,
40
+ onClick: ()=>onChange([
41
+ ...rows,
42
+ EMPTY_FIELD_MAPPING
43
+ ]),
44
+ type: "button",
45
+ children: "Add field"
46
+ }),
47
+ purpose: "Which incoming value becomes which column. The order is the order they are applied in.",
48
+ title: labels.mappings.fieldsHeading,
38
49
  children: [
39
- /*#__PURE__*/ _jsxs("div", {
40
- className: "flex flex-wrap items-center justify-between gap-2",
41
- children: [
42
- /*#__PURE__*/ _jsx("h3", {
43
- className: theme.card.title,
44
- children: labels.mappings.fieldsHeading
45
- }),
46
- /*#__PURE__*/ _jsx("button", {
47
- className: theme.button.secondary,
48
- disabled: disabled,
49
- onClick: ()=>onChange([
50
- ...rows,
51
- EMPTY_FIELD_MAPPING
52
- ]),
53
- type: "button",
54
- children: "Add field"
55
- })
56
- ]
57
- }),
58
50
  sourceFields.length === 0 ? /*#__PURE__*/ _jsx(Notice, {
59
51
  title: labels.mappings.sampleFirstTitle,
60
52
  tone: "warning",
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useId } from 'react';
4
4
  import { integrationsPageTheme } from '../theme';
5
- import { Field, Notice } from './Primitives';
5
+ import { Field, Notice, Section } from './Primitives';
6
6
  /**
7
7
  * A login for every person this import brings in.
8
8
  *
@@ -45,300 +45,290 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
45
45
  passwordColumn: ''
46
46
  });
47
47
  };
48
- return /*#__PURE__*/ _jsxs("section", {
49
- className: "flex flex-col gap-3",
50
- children: [
51
- /*#__PURE__*/ _jsxs("div", {
52
- className: "flex flex-wrap items-center justify-between gap-2",
53
- children: [
54
- /*#__PURE__*/ _jsx("h3", {
55
- className: theme.card.title,
56
- children: "Also create a login for each new record"
57
- }),
58
- /*#__PURE__*/ _jsx("button", {
59
- className: theme.button.secondary,
60
- disabled: disabled || targetFields.length === 0,
61
- onClick: ()=>value ? onChange(null) : turnOn(),
62
- type: "button",
63
- children: value ? 'Turn off' : 'Turn on'
64
- })
65
- ]
66
- }),
67
- value ? /*#__PURE__*/ _jsxs(_Fragment, {
68
- children: [
69
- /*#__PURE__*/ _jsxs("div", {
70
- className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3",
71
- children: [
72
- /*#__PURE__*/ _jsx(Field, {
73
- htmlFor: `${uid}-entity`,
74
- label: "Accounts live in",
75
- children: /*#__PURE__*/ _jsxs("select", {
76
- className: theme.field.input,
77
- disabled: disabled,
78
- id: `${uid}-entity`,
79
- onChange: (event)=>patch({
80
- entity: event.target.value,
81
- passwordColumn: '',
82
- matchColumn: 'email'
83
- }),
84
- value: value.entity,
85
- children: [
86
- /*#__PURE__*/ _jsx("option", {
87
- value: "",
88
- children: "Not set"
89
- }),
90
- targets.map((target)=>/*#__PURE__*/ _jsx("option", {
91
- value: target.entity,
92
- children: target.label
93
- }, target.entity))
94
- ]
95
- })
96
- }),
97
- /*#__PURE__*/ _jsx(Field, {
98
- hint: "The column on the account that identifies it.",
99
- htmlFor: `${uid}-match-column`,
100
- label: "Identified by",
101
- children: /*#__PURE__*/ _jsxs("select", {
102
- className: theme.field.input,
103
- disabled: disabled || accountFields.length === 0,
104
- id: `${uid}-match-column`,
105
- onChange: (event)=>patch({
106
- matchColumn: event.target.value
107
- }),
108
- value: value.matchColumn,
109
- children: [
110
- /*#__PURE__*/ _jsx("option", {
111
- value: "",
112
- children: "Not set"
113
- }),
114
- accountFields.map((field)=>/*#__PURE__*/ _jsx("option", {
115
- value: field,
116
- children: field
117
- }, field))
118
- ]
119
- })
120
- }),
121
- /*#__PURE__*/ _jsx(Field, {
122
- hint: "Where the run's shared password is written.",
123
- htmlFor: `${uid}-password`,
124
- label: "Password goes into",
125
- children: /*#__PURE__*/ _jsxs("select", {
126
- className: theme.field.input,
127
- disabled: disabled || accountFields.length === 0,
128
- id: `${uid}-password`,
129
- onChange: (event)=>patch({
130
- passwordColumn: event.target.value
131
- }),
132
- value: value.passwordColumn,
133
- children: [
134
- /*#__PURE__*/ _jsx("option", {
135
- value: "",
136
- children: "Not set"
137
- }),
138
- accountFields.filter((field)=>PASSWORD_LIKE.test(field)).map((field)=>/*#__PURE__*/ _jsx("option", {
139
- value: field,
140
- children: field
141
- }, field)),
142
- accountFields.filter((field)=>!PASSWORD_LIKE.test(field)).length > 0 ? /*#__PURE__*/ _jsx("optgroup", {
143
- label: "Other columns",
144
- children: accountFields.filter((field)=>!PASSWORD_LIKE.test(field)).map((field)=>/*#__PURE__*/ _jsx("option", {
145
- value: field,
146
- children: field
147
- }, field))
148
- }) : null
149
- ]
150
- })
151
- }),
152
- /*#__PURE__*/ _jsx(Field, {
153
- hint: "The imported record's column carrying that same value.",
154
- htmlFor: `${uid}-match-from`,
155
- label: "Matched from",
156
- children: /*#__PURE__*/ _jsxs("select", {
157
- className: theme.field.input,
158
- disabled: disabled,
159
- id: `${uid}-match-from`,
160
- onChange: (event)=>patch({
161
- matchFrom: event.target.value
162
- }),
163
- value: value.matchFrom,
164
- children: [
165
- /*#__PURE__*/ _jsx("option", {
166
- value: "",
167
- children: "Not set"
168
- }),
169
- targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
48
+ return /*#__PURE__*/ _jsx(Section, {
49
+ action: /*#__PURE__*/ _jsx("button", {
50
+ className: theme.button.secondary,
51
+ disabled: disabled || targetFields.length === 0,
52
+ onClick: ()=>value ? onChange(null) : turnOn(),
53
+ type: "button",
54
+ children: value ? 'Turn off' : 'Turn on'
55
+ }),
56
+ purpose: "A directory carries people, not logins. Turned on, the account is created in the same import and linked to the record.",
57
+ title: "Also create a login for each new record",
58
+ children: value ? /*#__PURE__*/ _jsxs(_Fragment, {
59
+ children: [
60
+ /*#__PURE__*/ _jsxs("div", {
61
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3",
62
+ children: [
63
+ /*#__PURE__*/ _jsx(Field, {
64
+ htmlFor: `${uid}-entity`,
65
+ label: "Accounts live in",
66
+ children: /*#__PURE__*/ _jsxs("select", {
67
+ className: theme.field.input,
68
+ disabled: disabled,
69
+ id: `${uid}-entity`,
70
+ onChange: (event)=>patch({
71
+ entity: event.target.value,
72
+ passwordColumn: '',
73
+ matchColumn: 'email'
74
+ }),
75
+ value: value.entity,
76
+ children: [
77
+ /*#__PURE__*/ _jsx("option", {
78
+ value: "",
79
+ children: "Not set"
80
+ }),
81
+ targets.map((target)=>/*#__PURE__*/ _jsx("option", {
82
+ value: target.entity,
83
+ children: target.label
84
+ }, target.entity))
85
+ ]
86
+ })
87
+ }),
88
+ /*#__PURE__*/ _jsx(Field, {
89
+ hint: "The column on the account that identifies it.",
90
+ htmlFor: `${uid}-match-column`,
91
+ label: "Identified by",
92
+ children: /*#__PURE__*/ _jsxs("select", {
93
+ className: theme.field.input,
94
+ disabled: disabled || accountFields.length === 0,
95
+ id: `${uid}-match-column`,
96
+ onChange: (event)=>patch({
97
+ matchColumn: event.target.value
98
+ }),
99
+ value: value.matchColumn,
100
+ children: [
101
+ /*#__PURE__*/ _jsx("option", {
102
+ value: "",
103
+ children: "Not set"
104
+ }),
105
+ accountFields.map((field)=>/*#__PURE__*/ _jsx("option", {
106
+ value: field,
107
+ children: field
108
+ }, field))
109
+ ]
110
+ })
111
+ }),
112
+ /*#__PURE__*/ _jsx(Field, {
113
+ hint: "Where the run's shared password is written.",
114
+ htmlFor: `${uid}-password`,
115
+ label: "Password goes into",
116
+ children: /*#__PURE__*/ _jsxs("select", {
117
+ className: theme.field.input,
118
+ disabled: disabled || accountFields.length === 0,
119
+ id: `${uid}-password`,
120
+ onChange: (event)=>patch({
121
+ passwordColumn: event.target.value
122
+ }),
123
+ value: value.passwordColumn,
124
+ children: [
125
+ /*#__PURE__*/ _jsx("option", {
126
+ value: "",
127
+ children: "Not set"
128
+ }),
129
+ accountFields.filter((field)=>PASSWORD_LIKE.test(field)).map((field)=>/*#__PURE__*/ _jsx("option", {
130
+ value: field,
131
+ children: field
132
+ }, field)),
133
+ accountFields.filter((field)=>!PASSWORD_LIKE.test(field)).length > 0 ? /*#__PURE__*/ _jsx("optgroup", {
134
+ label: "Other columns",
135
+ children: accountFields.filter((field)=>!PASSWORD_LIKE.test(field)).map((field)=>/*#__PURE__*/ _jsx("option", {
170
136
  value: field,
171
137
  children: field
172
138
  }, field))
173
- ]
174
- })
175
- }),
176
- /*#__PURE__*/ _jsx(Field, {
177
- hint: "Filled with the new account's id, so the record and its login know each other.",
178
- htmlFor: `${uid}-id-column`,
179
- label: "Account id goes into",
180
- children: /*#__PURE__*/ _jsxs("select", {
181
- className: theme.field.input,
139
+ }) : null
140
+ ]
141
+ })
142
+ }),
143
+ /*#__PURE__*/ _jsx(Field, {
144
+ hint: "The imported record's column carrying that same value.",
145
+ htmlFor: `${uid}-match-from`,
146
+ label: "Matched from",
147
+ children: /*#__PURE__*/ _jsxs("select", {
148
+ className: theme.field.input,
149
+ disabled: disabled,
150
+ id: `${uid}-match-from`,
151
+ onChange: (event)=>patch({
152
+ matchFrom: event.target.value
153
+ }),
154
+ value: value.matchFrom,
155
+ children: [
156
+ /*#__PURE__*/ _jsx("option", {
157
+ value: "",
158
+ children: "Not set"
159
+ }),
160
+ targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
161
+ value: field,
162
+ children: field
163
+ }, field))
164
+ ]
165
+ })
166
+ }),
167
+ /*#__PURE__*/ _jsx(Field, {
168
+ hint: "Filled with the new account's id, so the record and its login know each other.",
169
+ htmlFor: `${uid}-id-column`,
170
+ label: "Account id goes into",
171
+ children: /*#__PURE__*/ _jsxs("select", {
172
+ className: theme.field.input,
173
+ disabled: disabled,
174
+ id: `${uid}-id-column`,
175
+ onChange: (event)=>patch({
176
+ idColumn: event.target.value
177
+ }),
178
+ value: value.idColumn,
179
+ children: [
180
+ /*#__PURE__*/ _jsx("option", {
181
+ value: "",
182
+ children: "Not set"
183
+ }),
184
+ targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
185
+ value: field,
186
+ children: field
187
+ }, field))
188
+ ]
189
+ })
190
+ })
191
+ ]
192
+ }),
193
+ /*#__PURE__*/ _jsxs("section", {
194
+ className: "flex flex-col gap-2",
195
+ children: [
196
+ /*#__PURE__*/ _jsxs("div", {
197
+ className: "flex flex-wrap items-center justify-between gap-2",
198
+ children: [
199
+ /*#__PURE__*/ _jsx("span", {
200
+ className: theme.field.label,
201
+ children: "What each new account is a member of"
202
+ }),
203
+ /*#__PURE__*/ _jsx("button", {
204
+ className: theme.button.ghost,
182
205
  disabled: disabled,
183
- id: `${uid}-id-column`,
184
- onChange: (event)=>patch({
185
- idColumn: event.target.value
186
- }),
187
- value: value.idColumn,
188
- children: [
189
- /*#__PURE__*/ _jsx("option", {
190
- value: "",
191
- children: "Not set"
206
+ onClick: ()=>patch({
207
+ joins: [
208
+ ...value.joins ?? [],
209
+ {
210
+ entity: '',
211
+ accountColumn: '',
212
+ values: {}
213
+ }
214
+ ]
192
215
  }),
193
- targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
194
- value: field,
195
- children: field
196
- }, field))
197
- ]
216
+ type: "button",
217
+ children: "Add a membership"
198
218
  })
199
- })
200
- ]
201
- }),
202
- /*#__PURE__*/ _jsxs("section", {
203
- className: "flex flex-col gap-2",
204
- children: [
205
- /*#__PURE__*/ _jsxs("div", {
206
- className: "flex flex-wrap items-center justify-between gap-2",
219
+ ]
220
+ }),
221
+ (value.joins ?? []).length === 0 ? /*#__PURE__*/ _jsx("p", {
222
+ className: theme.field.hint,
223
+ children: "Nothing. An account with no role can sign in and do nothing — every screen it reaches refuses it. A role usually lives in its own table, joined to the account, which is what this adds."
224
+ }) : null,
225
+ (value.joins ?? []).map((join, joinIndex)=>{
226
+ const joinEntity = targets.find((target)=>target.entity === join.entity);
227
+ const pair = Object.entries(join.values)[0] ?? [
228
+ '',
229
+ ''
230
+ ];
231
+ const setJoin = (next)=>patch({
232
+ joins: (value.joins ?? []).with(joinIndex, {
233
+ ...join,
234
+ ...next
235
+ })
236
+ });
237
+ return /*#__PURE__*/ _jsxs("div", {
238
+ className: "grid grid-cols-1 gap-2 sm:grid-cols-[1fr_1fr_1fr_1fr_auto]",
207
239
  children: [
208
- /*#__PURE__*/ _jsx("span", {
209
- className: theme.field.label,
210
- children: "What each new account is a member of"
240
+ /*#__PURE__*/ _jsxs("select", {
241
+ className: theme.field.input,
242
+ disabled: disabled,
243
+ onChange: (event)=>setJoin({
244
+ entity: event.target.value,
245
+ accountColumn: '',
246
+ values: {}
247
+ }),
248
+ value: join.entity,
249
+ children: [
250
+ /*#__PURE__*/ _jsx("option", {
251
+ value: "",
252
+ children: "Membership table…"
253
+ }),
254
+ targets.map((target)=>/*#__PURE__*/ _jsx("option", {
255
+ value: target.entity,
256
+ children: target.label
257
+ }, target.entity))
258
+ ]
259
+ }),
260
+ /*#__PURE__*/ _jsxs("select", {
261
+ className: theme.field.input,
262
+ disabled: disabled || !joinEntity,
263
+ onChange: (event)=>setJoin({
264
+ accountColumn: event.target.value
265
+ }),
266
+ value: join.accountColumn,
267
+ children: [
268
+ /*#__PURE__*/ _jsx("option", {
269
+ value: "",
270
+ children: "…account id column"
271
+ }),
272
+ (joinEntity?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
273
+ value: field,
274
+ children: field
275
+ }, field))
276
+ ]
277
+ }),
278
+ /*#__PURE__*/ _jsxs("select", {
279
+ className: theme.field.input,
280
+ disabled: disabled || !joinEntity,
281
+ onChange: (event)=>setJoin({
282
+ values: {
283
+ [event.target.value]: pair[1] ?? ''
284
+ }
285
+ }),
286
+ value: pair[0],
287
+ children: [
288
+ /*#__PURE__*/ _jsx("option", {
289
+ value: "",
290
+ children: "…and this column"
291
+ }),
292
+ (joinEntity?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
293
+ value: field,
294
+ children: field
295
+ }, field))
296
+ ]
297
+ }),
298
+ /*#__PURE__*/ _jsx("input", {
299
+ className: theme.field.input,
300
+ disabled: disabled || !pair[0],
301
+ onChange: (event)=>setJoin({
302
+ values: {
303
+ [pair[0]]: event.target.value
304
+ }
305
+ }),
306
+ placeholder: "…set to this value",
307
+ value: String(pair[1] ?? '')
211
308
  }),
212
309
  /*#__PURE__*/ _jsx("button", {
213
310
  className: theme.button.ghost,
214
311
  disabled: disabled,
215
312
  onClick: ()=>patch({
216
- joins: [
217
- ...value.joins ?? [],
218
- {
219
- entity: '',
220
- accountColumn: '',
221
- values: {}
222
- }
223
- ]
313
+ joins: (value.joins ?? []).toSpliced(joinIndex, 1)
224
314
  }),
225
315
  type: "button",
226
- children: "Add a membership"
316
+ children: "Remove"
227
317
  })
228
318
  ]
229
- }),
230
- (value.joins ?? []).length === 0 ? /*#__PURE__*/ _jsx("p", {
231
- className: theme.field.hint,
232
- children: "Nothing. An account with no role can sign in and do nothing — every screen it reaches refuses it. A role usually lives in its own table, joined to the account, which is what this adds."
233
- }) : null,
234
- (value.joins ?? []).map((join, joinIndex)=>{
235
- const joinEntity = targets.find((target)=>target.entity === join.entity);
236
- const pair = Object.entries(join.values)[0] ?? [
237
- '',
238
- ''
239
- ];
240
- const setJoin = (next)=>patch({
241
- joins: (value.joins ?? []).with(joinIndex, {
242
- ...join,
243
- ...next
244
- })
245
- });
246
- return /*#__PURE__*/ _jsxs("div", {
247
- className: "grid grid-cols-1 gap-2 sm:grid-cols-[1fr_1fr_1fr_1fr_auto]",
248
- children: [
249
- /*#__PURE__*/ _jsxs("select", {
250
- className: theme.field.input,
251
- disabled: disabled,
252
- onChange: (event)=>setJoin({
253
- entity: event.target.value,
254
- accountColumn: '',
255
- values: {}
256
- }),
257
- value: join.entity,
258
- children: [
259
- /*#__PURE__*/ _jsx("option", {
260
- value: "",
261
- children: "Membership table…"
262
- }),
263
- targets.map((target)=>/*#__PURE__*/ _jsx("option", {
264
- value: target.entity,
265
- children: target.label
266
- }, target.entity))
267
- ]
268
- }),
269
- /*#__PURE__*/ _jsxs("select", {
270
- className: theme.field.input,
271
- disabled: disabled || !joinEntity,
272
- onChange: (event)=>setJoin({
273
- accountColumn: event.target.value
274
- }),
275
- value: join.accountColumn,
276
- children: [
277
- /*#__PURE__*/ _jsx("option", {
278
- value: "",
279
- children: "…account id column"
280
- }),
281
- (joinEntity?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
282
- value: field,
283
- children: field
284
- }, field))
285
- ]
286
- }),
287
- /*#__PURE__*/ _jsxs("select", {
288
- className: theme.field.input,
289
- disabled: disabled || !joinEntity,
290
- onChange: (event)=>setJoin({
291
- values: {
292
- [event.target.value]: pair[1] ?? ''
293
- }
294
- }),
295
- value: pair[0],
296
- children: [
297
- /*#__PURE__*/ _jsx("option", {
298
- value: "",
299
- children: "…and this column"
300
- }),
301
- (joinEntity?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
302
- value: field,
303
- children: field
304
- }, field))
305
- ]
306
- }),
307
- /*#__PURE__*/ _jsx("input", {
308
- className: theme.field.input,
309
- disabled: disabled || !pair[0],
310
- onChange: (event)=>setJoin({
311
- values: {
312
- [pair[0]]: event.target.value
313
- }
314
- }),
315
- placeholder: "…set to this value",
316
- value: String(pair[1] ?? '')
317
- }),
318
- /*#__PURE__*/ _jsx("button", {
319
- className: theme.button.ghost,
320
- disabled: disabled,
321
- onClick: ()=>patch({
322
- joins: (value.joins ?? []).toSpliced(joinIndex, 1)
323
- }),
324
- type: "button",
325
- children: "Remove"
326
- })
327
- ]
328
- }, joinIndex);
329
- })
330
- ]
331
- }),
332
- /*#__PURE__*/ _jsx(Notice, {
333
- title: "One password, typed on the import screen",
334
- tone: "info",
335
- children: "Every account this run creates is given the same opening password — asked for once, when the import is started, and stored nowhere. People change it themselves afterwards. A record whose value already has an account is linked to that one instead, so running this import again does not create a second."
336
- })
337
- ]
338
- }) : /*#__PURE__*/ _jsx("p", {
339
- className: theme.list.empty,
340
- children: "Off. Records are imported without a login — useful for a directory, not for people who have to sign in."
341
- })
342
- ]
319
+ }, joinIndex);
320
+ })
321
+ ]
322
+ }),
323
+ /*#__PURE__*/ _jsx(Notice, {
324
+ title: "One password, typed on the import screen",
325
+ tone: "info",
326
+ children: "Every account this run creates is given the same opening password — asked for once, when the import is started, and stored nowhere. People change it themselves afterwards. A record whose value already has an account is linked to that one instead, so running this import again does not create a second."
327
+ })
328
+ ]
329
+ }) : /*#__PURE__*/ _jsx("p", {
330
+ className: theme.list.empty,
331
+ children: "Off. Records are imported without a login — useful for a directory, not for people who have to sign in."
332
+ })
343
333
  });
344
334
  }
@@ -10,7 +10,7 @@ import { MappingPasswordField } from './MappingPasswordField';
10
10
  import { MappingScheduleFields } from './MappingScheduleFields';
11
11
  import { MappingWriteModeFields } from './MappingWriteModeFields';
12
12
  import { mappingDraftIssues } from './mappingDraft';
13
- import { Field } from './Primitives';
13
+ import { Field, Section } from './Primitives';
14
14
  import { ReferenceCheckRows } from './ReferenceCheckRows';
15
15
  import { CardSkeleton } from './Skeletons';
16
16
  /**
@@ -76,117 +76,127 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
76
76
  /*#__PURE__*/ _jsxs("div", {
77
77
  className: theme.card.body,
78
78
  children: [
79
- /*#__PURE__*/ _jsx(Field, {
80
- hint: endpoints.length === 0 ? labels.mappings.noEndpointsBody : readsFrom?.path ?? labels.mappings.readsFromHint,
81
- htmlFor: `${uid}-endpoint`,
82
- label: labels.mappings.readsFrom,
83
- children: /*#__PURE__*/ _jsxs("select", {
84
- className: theme.field.input,
85
- disabled: busy || endpoints.length === 0,
86
- id: `${uid}-endpoint`,
87
- onChange: (event)=>patch({
88
- endpointId: event.target.value
89
- }),
90
- value: draft.endpointId,
91
- children: [
92
- /*#__PURE__*/ _jsx("option", {
93
- value: "",
94
- children: labels.mappings.notSet
95
- }),
96
- endpoints.map((endpoint)=>/*#__PURE__*/ _jsx("option", {
97
- value: endpoint.id,
98
- children: endpointLabel(endpoint)
99
- }, endpoint.id))
100
- ]
101
- })
102
- }),
103
- /*#__PURE__*/ _jsxs("div", {
104
- className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
79
+ /*#__PURE__*/ _jsxs(Section, {
80
+ purpose: "Which call this reads, what it is called, and which of your tables it writes into.",
81
+ title: "What this import is",
105
82
  children: [
106
83
  /*#__PURE__*/ _jsx(Field, {
107
- htmlFor: `${uid}-name`,
108
- label: labels.mappings.name,
109
- children: /*#__PURE__*/ _jsx("input", {
84
+ hint: endpoints.length === 0 ? labels.mappings.noEndpointsBody : readsFrom?.path ?? labels.mappings.readsFromHint,
85
+ htmlFor: `${uid}-endpoint`,
86
+ label: labels.mappings.readsFrom,
87
+ children: /*#__PURE__*/ _jsxs("select", {
110
88
  className: theme.field.input,
111
- disabled: busy,
112
- id: `${uid}-name`,
89
+ disabled: busy || endpoints.length === 0,
90
+ id: `${uid}-endpoint`,
113
91
  onChange: (event)=>patch({
114
- name: event.target.value
92
+ endpointId: event.target.value
115
93
  }),
116
- placeholder: labels.mappings.namePlaceholder,
117
- value: draft.name
94
+ value: draft.endpointId,
95
+ children: [
96
+ /*#__PURE__*/ _jsx("option", {
97
+ value: "",
98
+ children: labels.mappings.notSet
99
+ }),
100
+ endpoints.map((endpoint)=>/*#__PURE__*/ _jsx("option", {
101
+ value: endpoint.id,
102
+ children: endpointLabel(endpoint)
103
+ }, endpoint.id))
104
+ ]
118
105
  })
119
106
  }),
107
+ /*#__PURE__*/ _jsxs("div", {
108
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
109
+ children: [
110
+ /*#__PURE__*/ _jsx(Field, {
111
+ htmlFor: `${uid}-name`,
112
+ label: labels.mappings.name,
113
+ children: /*#__PURE__*/ _jsx("input", {
114
+ className: theme.field.input,
115
+ disabled: busy,
116
+ id: `${uid}-name`,
117
+ onChange: (event)=>patch({
118
+ name: event.target.value
119
+ }),
120
+ placeholder: labels.mappings.namePlaceholder,
121
+ value: draft.name
122
+ })
123
+ }),
124
+ /*#__PURE__*/ _jsx(Field, {
125
+ hint: targets.length === 0 ? labels.mappings.targetNone : undefined,
126
+ htmlFor: `${uid}-target`,
127
+ label: labels.mappings.target,
128
+ children: /*#__PURE__*/ _jsxs("select", {
129
+ className: theme.field.input,
130
+ disabled: busy || targets.length === 0,
131
+ id: `${uid}-target`,
132
+ onChange: (event)=>chooseTarget(event.target.value),
133
+ value: draft.targetEntity,
134
+ children: [
135
+ /*#__PURE__*/ _jsx("option", {
136
+ value: "",
137
+ children: labels.mappings.notSet
138
+ }),
139
+ targets.map((option)=>/*#__PURE__*/ _jsx("option", {
140
+ value: option.entity,
141
+ children: option.label
142
+ }, option.entity))
143
+ ]
144
+ })
145
+ })
146
+ ]
147
+ }),
120
148
  /*#__PURE__*/ _jsx(Field, {
121
- hint: targets.length === 0 ? labels.mappings.targetNone : undefined,
122
- htmlFor: `${uid}-target`,
123
- label: labels.mappings.target,
149
+ hint: targetFields.length === 0 ? labels.mappings.dedupHintNoTarget : labels.mappings.dedupHint,
150
+ htmlFor: `${uid}-dedup`,
151
+ label: labels.mappings.dedup,
124
152
  children: /*#__PURE__*/ _jsxs("select", {
153
+ "aria-describedby": `${uid}-dedup-hint`,
125
154
  className: theme.field.input,
126
- disabled: busy || targets.length === 0,
127
- id: `${uid}-target`,
128
- onChange: (event)=>chooseTarget(event.target.value),
129
- value: draft.targetEntity,
155
+ disabled: busy || targetFields.length === 0,
156
+ id: `${uid}-dedup`,
157
+ onChange: (event)=>patch({
158
+ dedupTargetField: event.target.value || null
159
+ }),
160
+ value: draft.dedupTargetField ?? '',
130
161
  children: [
131
162
  /*#__PURE__*/ _jsx("option", {
132
163
  value: "",
133
164
  children: labels.mappings.notSet
134
165
  }),
135
- targets.map((option)=>/*#__PURE__*/ _jsx("option", {
136
- value: option.entity,
137
- children: option.label
138
- }, option.entity))
166
+ targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
167
+ value: field,
168
+ children: field
169
+ }, field))
139
170
  ]
140
171
  })
141
172
  })
142
173
  ]
143
174
  }),
144
- /*#__PURE__*/ _jsx(Field, {
145
- hint: targetFields.length === 0 ? labels.mappings.dedupHintNoTarget : labels.mappings.dedupHint,
146
- htmlFor: `${uid}-dedup`,
147
- label: labels.mappings.dedup,
148
- children: /*#__PURE__*/ _jsxs("select", {
149
- "aria-describedby": `${uid}-dedup-hint`,
150
- className: theme.field.input,
151
- disabled: busy || targetFields.length === 0,
152
- id: `${uid}-dedup`,
153
- onChange: (event)=>patch({
154
- dedupTargetField: event.target.value || null
175
+ /*#__PURE__*/ _jsx(Section, {
176
+ purpose: "How a record already here is recognised, and what happens to the ones the source stops sending.",
177
+ title: "Matching and writing",
178
+ children: /*#__PURE__*/ _jsx(MappingWriteModeFields, {
179
+ dedupConfigured: (draft.dedupTargetField ?? '').trim() !== '',
180
+ disabled: busy,
181
+ idPrefix: uid,
182
+ missingRecordColumn: draft.missingRecordColumn ?? '',
183
+ missingRecordPolicy: draft.missingRecordPolicy,
184
+ missingRecordValue: draft.missingRecordValue ?? '',
185
+ onMissingRecordColumnChange: (value)=>patch({
186
+ missingRecordColumn: value
155
187
  }),
156
- value: draft.dedupTargetField ?? '',
157
- children: [
158
- /*#__PURE__*/ _jsx("option", {
159
- value: "",
160
- children: labels.mappings.notSet
188
+ onMissingRecordPolicyChange: (policy)=>patch({
189
+ missingRecordPolicy: policy
161
190
  }),
162
- targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
163
- value: field,
164
- children: field
165
- }, field))
166
- ]
191
+ onMissingRecordValueChange: (value)=>patch({
192
+ missingRecordValue: value
193
+ }),
194
+ onWriteModeChange: (mode)=>patch({
195
+ writeMode: mode
196
+ }),
197
+ writeMode: draft.writeMode
167
198
  })
168
199
  }),
169
- /*#__PURE__*/ _jsx(MappingWriteModeFields, {
170
- dedupConfigured: (draft.dedupTargetField ?? '').trim() !== '',
171
- disabled: busy,
172
- idPrefix: uid,
173
- missingRecordColumn: draft.missingRecordColumn ?? '',
174
- missingRecordPolicy: draft.missingRecordPolicy,
175
- missingRecordValue: draft.missingRecordValue ?? '',
176
- onMissingRecordColumnChange: (value)=>patch({
177
- missingRecordColumn: value
178
- }),
179
- onMissingRecordPolicyChange: (policy)=>patch({
180
- missingRecordPolicy: policy
181
- }),
182
- onMissingRecordValueChange: (value)=>patch({
183
- missingRecordValue: value
184
- }),
185
- onWriteModeChange: (mode)=>patch({
186
- writeMode: mode
187
- }),
188
- writeMode: draft.writeMode
189
- }),
190
200
  /*#__PURE__*/ _jsx(FieldMappingRows, {
191
201
  disabled: busy,
192
202
  onChange: (rows)=>patch({
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useId } from 'react';
4
4
  import { describeCron, parseCron } from '../../../../src/Shared/cron';
5
5
  import { integrationsPageTheme } from '../theme';
6
- import { Field, Notice } from './Primitives';
6
+ import { Field, Notice, Section } from './Primitives';
7
7
  /**
8
8
  * Running this import without anybody clicking anything.
9
9
  *
@@ -51,100 +51,90 @@ export function MappingScheduleFields({ enabled, cron, timezone, needsPassword,
51
51
  ...next
52
52
  });
53
53
  };
54
- return /*#__PURE__*/ _jsxs("section", {
55
- className: "flex flex-col gap-3",
56
- children: [
57
- /*#__PURE__*/ _jsxs("div", {
58
- className: "flex flex-wrap items-center justify-between gap-2",
59
- children: [
60
- /*#__PURE__*/ _jsx("h3", {
61
- className: theme.card.title,
62
- children: "Run this import on a schedule"
63
- }),
64
- /*#__PURE__*/ _jsxs("label", {
65
- className: "flex items-center gap-2 text-sm",
66
- children: [
67
- /*#__PURE__*/ _jsx("input", {
68
- checked: enabled,
54
+ return /*#__PURE__*/ _jsx(Section, {
55
+ action: /*#__PURE__*/ _jsxs("label", {
56
+ className: "flex items-center gap-2 text-sm",
57
+ children: [
58
+ /*#__PURE__*/ _jsx("input", {
59
+ checked: enabled,
60
+ disabled: disabled,
61
+ id: `${uid}-enabled`,
62
+ onChange: (event)=>patch({
63
+ enabled: event.target.checked
64
+ }),
65
+ type: "checkbox"
66
+ }),
67
+ /*#__PURE__*/ _jsx("span", {
68
+ className: theme.field.label,
69
+ children: "Enabled"
70
+ })
71
+ ]
72
+ }),
73
+ purpose: "Every server checks it and exactly one takes the import — the same lock that stops two people running it at once.",
74
+ title: "Run this import on a schedule",
75
+ children: enabled ? /*#__PURE__*/ _jsxs(_Fragment, {
76
+ children: [
77
+ /*#__PURE__*/ _jsxs("div", {
78
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
79
+ children: [
80
+ /*#__PURE__*/ _jsx(Field, {
81
+ error: invalid && !parsed.ok ? parsed.error : null,
82
+ hint: cron.trim() === '' ? 'Five parts: minute hour day-of-month month day-of-week.' : describeCron(cron, timezone),
83
+ htmlFor: `${uid}-cron`,
84
+ label: "When",
85
+ children: /*#__PURE__*/ _jsx("input", {
86
+ className: theme.field.input,
69
87
  disabled: disabled,
70
- id: `${uid}-enabled`,
88
+ id: `${uid}-cron`,
71
89
  onChange: (event)=>patch({
72
- enabled: event.target.checked
90
+ cron: event.target.value
73
91
  }),
74
- type: "checkbox"
75
- }),
76
- /*#__PURE__*/ _jsx("span", {
77
- className: theme.field.label,
78
- children: "Enabled"
92
+ placeholder: "0 2 * * *",
93
+ value: cron
79
94
  })
80
- ]
81
- })
82
- ]
83
- }),
84
- enabled ? /*#__PURE__*/ _jsxs(_Fragment, {
85
- children: [
86
- /*#__PURE__*/ _jsxs("div", {
87
- className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
88
- children: [
89
- /*#__PURE__*/ _jsx(Field, {
90
- error: invalid && !parsed.ok ? parsed.error : null,
91
- hint: cron.trim() === '' ? 'Five parts: minute hour day-of-month month day-of-week.' : describeCron(cron, timezone),
92
- htmlFor: `${uid}-cron`,
93
- label: "When",
94
- children: /*#__PURE__*/ _jsx("input", {
95
- className: theme.field.input,
96
- disabled: disabled,
97
- id: `${uid}-cron`,
98
- onChange: (event)=>patch({
99
- cron: event.target.value
100
- }),
101
- placeholder: "0 2 * * *",
102
- value: cron
103
- })
104
- }),
105
- /*#__PURE__*/ _jsx(Field, {
106
- hint: "The company's own clock, so 02:00 stays 02:00 across a daylight-saving change.",
107
- htmlFor: `${uid}-timezone`,
108
- label: "In this timezone",
109
- children: /*#__PURE__*/ _jsx("input", {
110
- className: theme.field.input,
111
- disabled: disabled,
112
- id: `${uid}-timezone`,
113
- onChange: (event)=>patch({
114
- timezone: event.target.value
115
- }),
116
- placeholder: "Europe/Istanbul",
117
- value: timezone
118
- })
119
- })
120
- ]
121
- }),
122
- /*#__PURE__*/ _jsx("div", {
123
- className: "flex flex-wrap gap-2",
124
- children: PRESETS.map((preset)=>/*#__PURE__*/ _jsx("button", {
125
- className: theme.button.ghost,
95
+ }),
96
+ /*#__PURE__*/ _jsx(Field, {
97
+ hint: "The company's own clock, so 02:00 stays 02:00 across a daylight-saving change.",
98
+ htmlFor: `${uid}-timezone`,
99
+ label: "In this timezone",
100
+ children: /*#__PURE__*/ _jsx("input", {
101
+ className: theme.field.input,
126
102
  disabled: disabled,
127
- onClick: ()=>patch({
128
- cron: preset.cron
103
+ id: `${uid}-timezone`,
104
+ onChange: (event)=>patch({
105
+ timezone: event.target.value
129
106
  }),
130
- type: "button",
131
- children: preset.label
132
- }, preset.cron))
133
- }),
134
- needsPassword ? /*#__PURE__*/ _jsx(Notice, {
135
- title: "This import cannot run on its own",
136
- tone: "warning",
137
- children: "It creates a login for every new record, and a login needs a password — which there is nobody to type at that hour. Either start this import by hand, or turn the login off and give people their accounts another way."
138
- }) : null,
139
- /*#__PURE__*/ _jsx("p", {
140
- className: theme.field.hint,
141
- children: "Every server running this app checks the schedule, and exactly one of them takes the import — the same lock that stops two people running it at once."
142
- })
143
- ]
144
- }) : /*#__PURE__*/ _jsx("p", {
145
- className: theme.list.empty,
146
- children: "Off. This import runs when somebody asks for it, and at no other time."
147
- })
148
- ]
107
+ placeholder: "Europe/Istanbul",
108
+ value: timezone
109
+ })
110
+ })
111
+ ]
112
+ }),
113
+ /*#__PURE__*/ _jsx("div", {
114
+ className: "flex flex-wrap gap-2",
115
+ children: PRESETS.map((preset)=>/*#__PURE__*/ _jsx("button", {
116
+ className: theme.button.ghost,
117
+ disabled: disabled,
118
+ onClick: ()=>patch({
119
+ cron: preset.cron
120
+ }),
121
+ type: "button",
122
+ children: preset.label
123
+ }, preset.cron))
124
+ }),
125
+ needsPassword ? /*#__PURE__*/ _jsx(Notice, {
126
+ title: "This import cannot run on its own",
127
+ tone: "warning",
128
+ children: "It creates a login for every new record, and a login needs a password — which there is nobody to type at that hour. Either start this import by hand, or turn the login off and give people their accounts another way."
129
+ }) : null,
130
+ /*#__PURE__*/ _jsx("p", {
131
+ className: theme.field.hint,
132
+ children: "Every server running this app checks the schedule, and exactly one of them takes the import — the same lock that stops two people running it at once."
133
+ })
134
+ ]
135
+ }) : /*#__PURE__*/ _jsx("p", {
136
+ className: theme.list.empty,
137
+ children: "Off. This import runs when somebody asks for it, and at no other time."
138
+ })
149
139
  });
150
140
  }
@@ -63,3 +63,20 @@ export type CodeBlockProps = {
63
63
  * copy a line break into a field where it will silently break the request.
64
64
  */
65
65
  export declare function CodeBlock({ children }: CodeBlockProps): import("react/jsx-runtime").JSX.Element;
66
+ export type SectionProps = {
67
+ title: string;
68
+ /** One line saying what this block is for. Absent when the title carries it. */
69
+ purpose?: string;
70
+ /** Sits on the title row — an "add" button, usually. */
71
+ action?: ReactNode;
72
+ children: ReactNode;
73
+ };
74
+ /**
75
+ * A titled block inside a long form.
76
+ *
77
+ * The mapping screen asks five separate questions and used to put them in one
78
+ * unbroken column of inputs and paragraphs. Named blocks let the eye skip the
79
+ * questions it does not need, which is the whole difference between a form and
80
+ * a wall.
81
+ */
82
+ export declare function Section({ title, purpose, action, children }: SectionProps): import("react/jsx-runtime").JSX.Element;
@@ -92,3 +92,40 @@ const theme = integrationsPageTheme;
92
92
  })
93
93
  });
94
94
  }
95
+ /**
96
+ * A titled block inside a long form.
97
+ *
98
+ * The mapping screen asks five separate questions and used to put them in one
99
+ * unbroken column of inputs and paragraphs. Named blocks let the eye skip the
100
+ * questions it does not need, which is the whole difference between a form and
101
+ * a wall.
102
+ */ export function Section({ title, purpose, action, children }) {
103
+ return /*#__PURE__*/ _jsxs("section", {
104
+ className: theme.section.wrapper,
105
+ children: [
106
+ /*#__PURE__*/ _jsxs("div", {
107
+ className: "mb-3 flex flex-wrap items-start justify-between gap-2",
108
+ children: [
109
+ /*#__PURE__*/ _jsxs("div", {
110
+ className: "flex min-w-0 flex-col gap-0.5",
111
+ children: [
112
+ /*#__PURE__*/ _jsx("h3", {
113
+ className: theme.section.title,
114
+ children: title
115
+ }),
116
+ purpose ? /*#__PURE__*/ _jsx("p", {
117
+ className: theme.section.purpose,
118
+ children: purpose
119
+ }) : null
120
+ ]
121
+ }),
122
+ action
123
+ ]
124
+ }),
125
+ /*#__PURE__*/ _jsx("div", {
126
+ className: theme.section.body,
127
+ children: children
128
+ })
129
+ ]
130
+ });
131
+ }
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useId } from 'react';
4
4
  import { integrationsPageTheme } from '../theme';
5
- import { Notice } from './Primitives';
5
+ import { Notice, Section } from './Primitives';
6
6
  /**
7
7
  * Columns whose value has to already exist somewhere else.
8
8
  *
@@ -47,28 +47,20 @@ export function ReferenceCheckRows({ rows, targetFields, sourceFields = [], targ
47
47
  ...next
48
48
  }));
49
49
  };
50
- return /*#__PURE__*/ _jsxs("section", {
51
- className: "flex flex-col gap-3",
50
+ return /*#__PURE__*/ _jsxs(Section, {
51
+ action: /*#__PURE__*/ _jsx("button", {
52
+ className: theme.button.secondary,
53
+ disabled: disabled || targetFields.length === 0,
54
+ onClick: ()=>onChange([
55
+ ...rows,
56
+ EMPTY
57
+ ]),
58
+ type: "button",
59
+ children: "Add a reference"
60
+ }),
61
+ purpose: "A record whose department or location is not here yet points at nothing. Declare it and the preview says so before anything is written.",
62
+ title: "Values that must already exist",
52
63
  children: [
53
- /*#__PURE__*/ _jsxs("div", {
54
- className: "flex flex-wrap items-center justify-between gap-2",
55
- children: [
56
- /*#__PURE__*/ _jsx("h3", {
57
- className: theme.card.title,
58
- children: "Values that must already exist"
59
- }),
60
- /*#__PURE__*/ _jsx("button", {
61
- className: theme.button.secondary,
62
- disabled: disabled || targetFields.length === 0,
63
- onClick: ()=>onChange([
64
- ...rows,
65
- EMPTY
66
- ]),
67
- type: "button",
68
- children: "Add a reference"
69
- })
70
- ]
71
- }),
72
64
  /*#__PURE__*/ _jsx("datalist", {
73
65
  id: fromListId,
74
66
  children: [
@@ -66,6 +66,14 @@ export type IntegrationsPageTheme = {
66
66
  hint: string;
67
67
  error: string;
68
68
  };
69
+ /** A titled block inside a long form, so it reads as parts rather than a column. */
70
+ section: {
71
+ wrapper: string;
72
+ header: string;
73
+ title: string;
74
+ purpose: string;
75
+ body: string;
76
+ };
69
77
  button: {
70
78
  primary: string;
71
79
  secondary: string;
@@ -76,6 +76,21 @@ export const integrationsPageTheme = {
76
76
  purpose: 'mt-1 text-slate-600 text-xs leading-relaxed dark:text-slate-400',
77
77
  evidence: 'mt-1.5 font-semibold text-[11px] text-emerald-700 dark:text-emerald-400'
78
78
  },
79
+ /**
80
+ * A form of twenty controls is not a form, it is a wall.
81
+ *
82
+ * The mapping screen asks five separate questions — what this reads, where it
83
+ * lands, what must already exist, whether it opens a login, when it runs — and
84
+ * they were one unbroken column of inputs and paragraphs. Sectioned, each
85
+ * question is a block with a name, and the eye can skip the ones it does not
86
+ * need.
87
+ */ section: {
88
+ wrapper: 'rounded-2xl border border-slate-200/70 bg-slate-50/50 p-4 sm:p-5 dark:border-slate-800 dark:bg-slate-950/30',
89
+ header: 'mb-3 flex flex-col gap-0.5',
90
+ title: 'font-semibold text-slate-900 text-sm tracking-tight dark:text-slate-100',
91
+ purpose: 'text-slate-500 text-xs leading-relaxed dark:text-slate-400',
92
+ body: 'flex flex-col gap-3.5'
93
+ },
79
94
  field: {
80
95
  wrapper: 'flex flex-col gap-1.5',
81
96
  label: `${LABEL} text-slate-500 dark:text-slate-400`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.9.841",
3
+ "version": "0.9.842",
4
4
  "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
5
  "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",