nucleus-core-ts 0.9.800 → 0.9.802
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.js +97 -86
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +29 -0
- package/dist/fe/components/IntegrationsPage/store/index.js +9 -0
- package/dist/fe/components/IntegrationsPage/store/state.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/text/index.d.ts +20 -0
- package/dist/fe/components/IntegrationsPage/text/index.js +19 -0
- package/dist/fe/components/IntegrationsPage/theme/index.js +69 -55
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from '../../../utils/cn';
|
|
4
|
+
import { useLabels } from '../text/context';
|
|
4
5
|
import { integrationsPageTheme } from '../theme';
|
|
5
6
|
import { describedBy } from './formSupport';
|
|
6
7
|
import { Field } from './Primitives';
|
|
@@ -9,52 +10,57 @@ import { Field } from './Primitives';
|
|
|
9
10
|
* decide whether it is used and how carefully.
|
|
10
11
|
*/ const theme = integrationsPageTheme;
|
|
11
12
|
const CHECKBOX = 'h-4 w-4 shrink-0 accent-sky-600 dark:accent-sky-500';
|
|
12
|
-
const BASE_URL_HINT = 'The scheme is free. https:// and plain http:// are both accepted and neither is rewritten — calling a service inside the same cluster over http, for example http://hr-service:8080, is a legitimate setup.';
|
|
13
13
|
export function ConnectionBasicFields({ draft, errors, disabled, onChange }) {
|
|
14
|
+
const labels = useLabels();
|
|
14
15
|
return /*#__PURE__*/ _jsxs("section", {
|
|
15
|
-
"aria-label":
|
|
16
|
-
className: "flex flex-col gap-
|
|
16
|
+
"aria-label": labels.connectionForm.sectionLabel,
|
|
17
|
+
className: "flex flex-col gap-4",
|
|
17
18
|
children: [
|
|
18
|
-
/*#__PURE__*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
19
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
20
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
23
|
+
error: errors.name,
|
|
24
|
+
htmlFor: "connection-name",
|
|
25
|
+
label: labels.connectionForm.name,
|
|
26
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
27
|
+
"aria-describedby": describedBy('connection-name', false, Boolean(errors.name)),
|
|
28
|
+
className: theme.field.input,
|
|
29
|
+
disabled: disabled,
|
|
30
|
+
id: "connection-name",
|
|
31
|
+
onChange: (event)=>onChange({
|
|
32
|
+
name: event.target.value
|
|
33
|
+
}),
|
|
34
|
+
placeholder: labels.connectionForm.namePlaceholder,
|
|
35
|
+
type: "text",
|
|
36
|
+
value: draft.name
|
|
37
|
+
})
|
|
38
|
+
}),
|
|
39
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
40
|
+
error: errors.slug,
|
|
41
|
+
hint: labels.connectionForm.slugHint,
|
|
42
|
+
htmlFor: "connection-slug",
|
|
43
|
+
label: labels.connectionForm.slug,
|
|
44
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
45
|
+
"aria-describedby": describedBy('connection-slug', true, Boolean(errors.slug)),
|
|
46
|
+
className: theme.field.input,
|
|
47
|
+
disabled: disabled,
|
|
48
|
+
id: "connection-slug",
|
|
49
|
+
onChange: (event)=>onChange({
|
|
50
|
+
slug: event.target.value
|
|
51
|
+
}),
|
|
52
|
+
placeholder: "hr-service",
|
|
53
|
+
type: "text",
|
|
54
|
+
value: draft.slug
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
]
|
|
52
58
|
}),
|
|
53
59
|
/*#__PURE__*/ _jsx(Field, {
|
|
54
60
|
error: errors.baseUrl,
|
|
55
|
-
hint:
|
|
61
|
+
hint: labels.connectionForm.baseUrlHint,
|
|
56
62
|
htmlFor: "connection-base-url",
|
|
57
|
-
label:
|
|
63
|
+
label: labels.connectionForm.baseUrl,
|
|
58
64
|
children: /*#__PURE__*/ _jsx("input", {
|
|
59
65
|
"aria-describedby": describedBy('connection-base-url', true, Boolean(errors.baseUrl)),
|
|
60
66
|
className: theme.field.input,
|
|
@@ -69,9 +75,9 @@ export function ConnectionBasicFields({ draft, errors, disabled, onChange }) {
|
|
|
69
75
|
})
|
|
70
76
|
}),
|
|
71
77
|
/*#__PURE__*/ _jsx(Field, {
|
|
72
|
-
hint:
|
|
78
|
+
hint: labels.connectionForm.descriptionHint,
|
|
73
79
|
htmlFor: "connection-description",
|
|
74
|
-
label:
|
|
80
|
+
label: labels.connectionForm.description,
|
|
75
81
|
children: /*#__PURE__*/ _jsx("textarea", {
|
|
76
82
|
"aria-describedby": describedBy('connection-description', true, false),
|
|
77
83
|
className: cn(theme.field.input, 'h-20 py-2 sm:h-20'),
|
|
@@ -80,71 +86,76 @@ export function ConnectionBasicFields({ draft, errors, disabled, onChange }) {
|
|
|
80
86
|
onChange: (event)=>onChange({
|
|
81
87
|
description: event.target.value
|
|
82
88
|
}),
|
|
83
|
-
placeholder:
|
|
89
|
+
placeholder: labels.connectionForm.descriptionPlaceholder,
|
|
84
90
|
value: draft.description
|
|
85
91
|
})
|
|
86
92
|
}),
|
|
87
93
|
/*#__PURE__*/ _jsxs("div", {
|
|
88
|
-
className:
|
|
94
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
89
95
|
children: [
|
|
90
|
-
/*#__PURE__*/ _jsxs("
|
|
91
|
-
className: cn(theme.field.
|
|
92
|
-
htmlFor: "connection-enabled",
|
|
96
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
97
|
+
className: cn(theme.field.wrapper, 'rounded-xl border border-slate-200 p-3 dark:border-slate-800'),
|
|
93
98
|
children: [
|
|
94
|
-
/*#__PURE__*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
100
|
+
className: cn(theme.field.label, 'flex items-center gap-2 normal-case tracking-normal text-slate-700 text-sm dark:text-slate-200'),
|
|
101
|
+
htmlFor: "connection-enabled",
|
|
102
|
+
children: [
|
|
103
|
+
/*#__PURE__*/ _jsx("input", {
|
|
104
|
+
"aria-describedby": "connection-enabled-hint",
|
|
105
|
+
checked: draft.enabled,
|
|
106
|
+
className: CHECKBOX,
|
|
107
|
+
disabled: disabled,
|
|
108
|
+
id: "connection-enabled",
|
|
109
|
+
onChange: (event)=>onChange({
|
|
110
|
+
enabled: event.target.checked
|
|
111
|
+
}),
|
|
112
|
+
type: "checkbox"
|
|
102
113
|
}),
|
|
103
|
-
|
|
114
|
+
labels.connectionForm.enabled
|
|
115
|
+
]
|
|
104
116
|
}),
|
|
105
|
-
"
|
|
117
|
+
/*#__PURE__*/ _jsx("span", {
|
|
118
|
+
className: theme.field.hint,
|
|
119
|
+
id: "connection-enabled-hint",
|
|
120
|
+
children: labels.connectionForm.enabledHint
|
|
121
|
+
})
|
|
106
122
|
]
|
|
107
123
|
}),
|
|
108
|
-
/*#__PURE__*/
|
|
109
|
-
className: theme.field.
|
|
110
|
-
id: "connection-enabled-hint",
|
|
111
|
-
children: "Turning this off stops every endpoint and import that goes through this connection. The settings are kept."
|
|
112
|
-
})
|
|
113
|
-
]
|
|
114
|
-
}),
|
|
115
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
116
|
-
className: theme.field.wrapper,
|
|
117
|
-
children: [
|
|
118
|
-
/*#__PURE__*/ _jsxs("label", {
|
|
119
|
-
className: cn(theme.field.label, 'flex items-center gap-2'),
|
|
120
|
-
htmlFor: "connection-tls-verify",
|
|
124
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
125
|
+
className: cn(theme.field.wrapper, 'rounded-xl border border-slate-200 p-3 dark:border-slate-800'),
|
|
121
126
|
children: [
|
|
122
|
-
/*#__PURE__*/
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
128
|
+
className: cn(theme.field.label, 'flex items-center gap-2 normal-case tracking-normal text-slate-700 text-sm dark:text-slate-200'),
|
|
129
|
+
htmlFor: "connection-tls-verify",
|
|
130
|
+
children: [
|
|
131
|
+
/*#__PURE__*/ _jsx("input", {
|
|
132
|
+
"aria-describedby": "connection-tls-verify-hint",
|
|
133
|
+
checked: draft.tlsVerify,
|
|
134
|
+
className: CHECKBOX,
|
|
135
|
+
disabled: disabled,
|
|
136
|
+
id: "connection-tls-verify",
|
|
137
|
+
onChange: (event)=>onChange({
|
|
138
|
+
tlsVerify: event.target.checked
|
|
139
|
+
}),
|
|
140
|
+
type: "checkbox"
|
|
130
141
|
}),
|
|
131
|
-
|
|
142
|
+
labels.connectionForm.tlsVerify
|
|
143
|
+
]
|
|
132
144
|
}),
|
|
133
|
-
"
|
|
145
|
+
/*#__PURE__*/ _jsx("span", {
|
|
146
|
+
className: theme.field.hint,
|
|
147
|
+
id: "connection-tls-verify-hint",
|
|
148
|
+
children: draft.tlsVerify ? labels.connectionForm.tlsVerifyOnHint : labels.connectionForm.tlsVerifyOffHint
|
|
149
|
+
})
|
|
134
150
|
]
|
|
135
|
-
}),
|
|
136
|
-
/*#__PURE__*/ _jsx("span", {
|
|
137
|
-
className: theme.field.hint,
|
|
138
|
-
id: "connection-tls-verify-hint",
|
|
139
|
-
children: draft.tlsVerify ? 'On. Unchecking it applies to requests made through THIS connection only — no other connection and nothing else in the app is affected.' : 'Off. Certificates are not checked for requests made through THIS connection only. Every other connection still verifies. Use it for an internal service with its own certificate authority.'
|
|
140
151
|
})
|
|
141
152
|
]
|
|
142
153
|
}),
|
|
143
154
|
/*#__PURE__*/ _jsx(Field, {
|
|
144
155
|
error: errors.timeoutMs,
|
|
145
|
-
hint:
|
|
156
|
+
hint: labels.connectionForm.timeoutHint,
|
|
146
157
|
htmlFor: "connection-timeout",
|
|
147
|
-
label:
|
|
158
|
+
label: labels.connectionForm.timeout,
|
|
148
159
|
children: /*#__PURE__*/ _jsx("input", {
|
|
149
160
|
"aria-describedby": describedBy('connection-timeout', true, Boolean(errors.timeoutMs)),
|
|
150
161
|
className: theme.field.input,
|
|
@@ -45,6 +45,35 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
45
45
|
const handleSourceSaved = ()=>{
|
|
46
46
|
setSourcesReloadToken((token)=>token + 1);
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* How far this connection has got, asked for directly.
|
|
50
|
+
*
|
|
51
|
+
* The step strip promises to say where you are, so it cannot depend on having
|
|
52
|
+
* VISITED the step that counts — on a fresh load every total was zero and the
|
|
53
|
+
* strip told an operator with five calls, three mappings and two finished
|
|
54
|
+
* imports to "finish the previous step first". Three counting requests, one
|
|
55
|
+
* row each, are the price of a strip that is telling the truth.
|
|
56
|
+
*/ const loadProgress = useEffectEvent((sourceId)=>{
|
|
57
|
+
const one = {
|
|
58
|
+
page: 1,
|
|
59
|
+
limit: 1
|
|
60
|
+
};
|
|
61
|
+
const total = (result)=>result.meta?.totalItems ?? 0;
|
|
62
|
+
actions.listEndpoints(sourceId, one).then((r)=>store.setEndpointsTotal(total(r))).catch(()=>{});
|
|
63
|
+
actions.listMappings(sourceId, one).then((r)=>store.setMappingsTotal(total(r))).catch(()=>{});
|
|
64
|
+
// Runs are deliberately NOT counted here. A run records the mapping it came
|
|
65
|
+
// from and nothing about the connection, so the only count available is
|
|
66
|
+
// every run of every connection — which on the second connection would be
|
|
67
|
+
// a number that looks like this one's and is not. The Runs tab sets the
|
|
68
|
+
// real figure for the mapping it is showing; until then the step says
|
|
69
|
+
// nothing rather than something wrong.
|
|
70
|
+
});
|
|
71
|
+
useEffect(()=>{
|
|
72
|
+
if (store.selectedSourceId) loadProgress(store.selectedSourceId);
|
|
73
|
+
}, [
|
|
74
|
+
store.selectedSourceId,
|
|
75
|
+
sourcesReloadToken
|
|
76
|
+
]);
|
|
48
77
|
// One measured entrance. Anything longer makes an operator wait to read counts
|
|
49
78
|
// they came here to check.
|
|
50
79
|
useGSAP(()=>{
|
|
@@ -49,6 +49,9 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
49
49
|
store.endpointsTotal = total;
|
|
50
50
|
store.endpointsLoading = false;
|
|
51
51
|
}),
|
|
52
|
+
setEndpointsTotal: (store)=>(total)=>{
|
|
53
|
+
store.endpointsTotal = total;
|
|
54
|
+
},
|
|
52
55
|
setEndpointsPage: (store)=>(page)=>{
|
|
53
56
|
store.endpointsPage = page;
|
|
54
57
|
},
|
|
@@ -70,6 +73,9 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
70
73
|
store.mappingsTotal = total;
|
|
71
74
|
store.mappingsLoading = false;
|
|
72
75
|
}),
|
|
76
|
+
setMappingsTotal: (store)=>(total)=>{
|
|
77
|
+
store.mappingsTotal = total;
|
|
78
|
+
},
|
|
73
79
|
setMappingsPage: (store)=>(page)=>{
|
|
74
80
|
store.mappingsPage = page;
|
|
75
81
|
},
|
|
@@ -97,6 +103,9 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
97
103
|
store.runsTotal = total;
|
|
98
104
|
store.runsLoading = false;
|
|
99
105
|
}),
|
|
106
|
+
setRunsTotal: (store)=>(total)=>{
|
|
107
|
+
store.runsTotal = total;
|
|
108
|
+
},
|
|
100
109
|
setRunsPage: (store)=>(page)=>{
|
|
101
110
|
store.runsPage = page;
|
|
102
111
|
},
|
|
@@ -57,16 +57,25 @@ export type Actions = {
|
|
|
57
57
|
selectSource: (id: string | null) => void;
|
|
58
58
|
setTab: (tab: ConnectionTab) => void;
|
|
59
59
|
setEndpoints: (items: IntegrationEndpoint[], total: number) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Just the count, without the rows.
|
|
62
|
+
*
|
|
63
|
+
* The step strip reports how far a connection has got, and it cannot wait for
|
|
64
|
+
* the tab that lists the rows to have been opened — so it counts directly.
|
|
65
|
+
*/
|
|
66
|
+
setEndpointsTotal: (total: number) => void;
|
|
60
67
|
setEndpointsPage: (page: number) => void;
|
|
61
68
|
setEndpointsSearch: (search: string) => void;
|
|
62
69
|
setEndpointsLoading: (loading: boolean) => void;
|
|
63
70
|
selectEndpoint: (id: string | null) => void;
|
|
64
71
|
setMappings: (items: IntegrationMapping[], total: number) => void;
|
|
72
|
+
setMappingsTotal: (total: number) => void;
|
|
65
73
|
setMappingsPage: (page: number) => void;
|
|
66
74
|
setMappingsSearch: (search: string) => void;
|
|
67
75
|
setMappingsLoading: (loading: boolean) => void;
|
|
68
76
|
selectMapping: (id: string | null) => void;
|
|
69
77
|
setRuns: (items: IntegrationRun[], total: number) => void;
|
|
78
|
+
setRunsTotal: (total: number) => void;
|
|
70
79
|
setRunsPage: (page: number) => void;
|
|
71
80
|
setRunsLoading: (loading: boolean) => void;
|
|
72
81
|
setSample: (sample: EndpointSampleValue | null) => void;
|
|
@@ -80,6 +80,26 @@ export type IntegrationsLabels = {
|
|
|
80
80
|
skipped: string;
|
|
81
81
|
dedupKey: string;
|
|
82
82
|
};
|
|
83
|
+
/** The connection form — the first screen anyone meets. */
|
|
84
|
+
connectionForm: {
|
|
85
|
+
sectionLabel: string;
|
|
86
|
+
name: string;
|
|
87
|
+
namePlaceholder: string;
|
|
88
|
+
slug: string;
|
|
89
|
+
slugHint: string;
|
|
90
|
+
baseUrl: string;
|
|
91
|
+
baseUrlHint: string;
|
|
92
|
+
description: string;
|
|
93
|
+
descriptionHint: string;
|
|
94
|
+
descriptionPlaceholder: string;
|
|
95
|
+
enabled: string;
|
|
96
|
+
enabledHint: string;
|
|
97
|
+
tlsVerify: string;
|
|
98
|
+
tlsVerifyOnHint: string;
|
|
99
|
+
tlsVerifyOffHint: string;
|
|
100
|
+
timeout: string;
|
|
101
|
+
timeoutHint: string;
|
|
102
|
+
};
|
|
83
103
|
writeMode: {
|
|
84
104
|
upsert: string;
|
|
85
105
|
append: string;
|
|
@@ -74,6 +74,25 @@
|
|
|
74
74
|
skipped: 'Skipped',
|
|
75
75
|
dedupKey: 'Dedup key'
|
|
76
76
|
},
|
|
77
|
+
connectionForm: {
|
|
78
|
+
sectionLabel: 'Connection details',
|
|
79
|
+
name: 'Name',
|
|
80
|
+
namePlaceholder: 'Directory service',
|
|
81
|
+
slug: 'Slug',
|
|
82
|
+
slugHint: 'Used to refer to this connection elsewhere. It does not change once things point at it.',
|
|
83
|
+
baseUrl: 'Base URL',
|
|
84
|
+
baseUrlHint: 'The scheme is free. https:// and plain http:// are both accepted and neither is rewritten — calling a service inside the same cluster over http, for example http://hr-service:8080, is a legitimate setup.',
|
|
85
|
+
description: 'Description',
|
|
86
|
+
descriptionHint: 'For whoever has to work out what this connection is, months from now.',
|
|
87
|
+
descriptionPlaceholder: 'Read-only employee directory used by the nightly import.',
|
|
88
|
+
enabled: 'Enabled',
|
|
89
|
+
enabledHint: 'Turning this off stops every endpoint and import that goes through this connection. The settings are kept.',
|
|
90
|
+
tlsVerify: 'Verify TLS certificate',
|
|
91
|
+
tlsVerifyOnHint: 'On. Unchecking it applies to requests made through THIS connection only — no other connection and nothing else in the app is affected.',
|
|
92
|
+
tlsVerifyOffHint: 'Off. Certificates are not checked for requests made through THIS connection only. Every other connection still verifies. Use it for an internal service with its own certificate authority.',
|
|
93
|
+
timeout: 'Timeout (ms)',
|
|
94
|
+
timeoutHint: 'How long one request may take before it is given up on. Empty means the server default.'
|
|
95
|
+
},
|
|
77
96
|
writeMode: {
|
|
78
97
|
upsert: 'update what matches, insert the rest',
|
|
79
98
|
append: 'always insert, never match',
|
|
@@ -7,106 +7,120 @@
|
|
|
7
7
|
*
|
|
8
8
|
* An app can replace any slice of this with {@link extendIntegrationsTheme}
|
|
9
9
|
* without forking the components.
|
|
10
|
-
*/ /** Shared so a control and its skeleton cannot drift apart in height. */ const CONTROL = 'h-
|
|
10
|
+
*/ /** Shared so a control and its skeleton cannot drift apart in height. */ const CONTROL = 'h-11 rounded-lg text-sm sm:h-10';
|
|
11
|
+
/**
|
|
12
|
+
* Micro-caps for field labels.
|
|
13
|
+
*
|
|
14
|
+
* A form of twenty identical `text-sm` labels reads as a wall. Making the label
|
|
15
|
+
* smaller and wider-tracked than its value gives every row an obvious top and
|
|
16
|
+
* bottom, which is what lets an eye skip the ones it does not need.
|
|
17
|
+
*/ const LABEL = 'font-semibold text-[11px] uppercase tracking-wider';
|
|
11
18
|
export const integrationsPageTheme = {
|
|
12
|
-
container: 'flex flex-col gap-
|
|
19
|
+
container: 'flex flex-col gap-5 text-slate-900 sm:gap-6 dark:text-slate-100',
|
|
13
20
|
header: {
|
|
14
|
-
wrapper: 'flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between',
|
|
15
|
-
title: 'font-
|
|
16
|
-
subtitle: 'text-slate-600 text-sm dark:text-slate-400',
|
|
21
|
+
wrapper: 'flex flex-col gap-2 border-slate-200 border-b pb-4 sm:flex-row sm:items-end sm:justify-between dark:border-slate-800',
|
|
22
|
+
title: 'font-bold text-slate-900 text-xl tracking-tight sm:text-2xl dark:text-slate-50',
|
|
23
|
+
subtitle: 'max-w-2xl text-slate-600 text-sm leading-relaxed dark:text-slate-400',
|
|
17
24
|
actions: 'flex flex-wrap items-center gap-2'
|
|
18
25
|
},
|
|
19
|
-
//
|
|
20
|
-
//
|
|
26
|
+
// The rail is a chooser, not the work. It gets the narrower column and the
|
|
27
|
+
// detail gets the rest — before this they were near enough equal, so a form
|
|
28
|
+
// of twenty fields was squeezed beside a list of one.
|
|
21
29
|
layout: {
|
|
22
|
-
split: 'grid grid-cols-1 gap-
|
|
30
|
+
split: 'grid grid-cols-1 gap-5 lg:grid-cols-[minmax(0,17rem)_minmax(0,1fr)] lg:gap-6',
|
|
23
31
|
aside: 'flex min-w-0 flex-col gap-3',
|
|
24
|
-
main: 'flex min-w-0 flex-col gap-4'
|
|
32
|
+
main: 'flex min-w-0 flex-col gap-4 sm:gap-5'
|
|
25
33
|
},
|
|
34
|
+
// Cards sit on a tinted page, so a white surface separates on its own instead
|
|
35
|
+
// of relying on a shadow that light mode barely renders.
|
|
26
36
|
card: {
|
|
27
|
-
wrapper: 'rounded-
|
|
28
|
-
header: 'flex flex-col gap-2 border-slate-200 border-b px-4 py-3 sm:flex-row sm:items-center sm:justify-between dark:border-slate-800',
|
|
29
|
-
title: 'font-
|
|
30
|
-
body: 'flex flex-col gap-
|
|
31
|
-
footer: 'flex flex-wrap items-center justify-end gap-2 border-slate-200 border-t px-4 py-3 dark:border-slate-800'
|
|
37
|
+
wrapper: 'overflow-hidden rounded-2xl border border-slate-200/80 bg-white shadow-slate-200/50 shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:shadow-none',
|
|
38
|
+
header: 'flex flex-col gap-2 border-slate-200/80 border-b bg-slate-50/80 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-5 dark:border-slate-800 dark:bg-slate-950/40',
|
|
39
|
+
title: 'font-semibold text-slate-900 text-sm dark:text-slate-100',
|
|
40
|
+
body: 'flex flex-col gap-4 p-4 sm:gap-5 sm:p-5',
|
|
41
|
+
footer: 'flex flex-wrap items-center justify-end gap-2 border-slate-200/80 border-t bg-slate-50/80 px-4 py-3 sm:px-5 dark:border-slate-800 dark:bg-slate-950/40'
|
|
32
42
|
},
|
|
33
43
|
list: {
|
|
34
44
|
wrapper: 'flex flex-col gap-1.5',
|
|
35
|
-
item: 'flex w-full flex-col gap-0.5 rounded-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
item: 'flex w-full flex-col gap-0.5 rounded-xl border border-transparent px-3 py-2.5 text-left transition-all hover:border-slate-200 hover:bg-white dark:hover:border-slate-700 dark:hover:bg-slate-900',
|
|
46
|
+
// A selected row is the only thing on the rail that carries the accent, so
|
|
47
|
+
// it is legible at a glance from across the screen.
|
|
48
|
+
itemSelected: 'border-sky-400 bg-white shadow-sky-100 shadow-sm hover:border-sky-400 hover:bg-white dark:border-sky-600 dark:bg-slate-900 dark:shadow-none',
|
|
49
|
+
itemTitle: 'truncate font-semibold text-slate-900 text-sm dark:text-slate-100',
|
|
38
50
|
itemMeta: 'truncate text-slate-500 text-xs dark:text-slate-400',
|
|
39
|
-
empty: 'rounded-
|
|
51
|
+
empty: 'rounded-xl border border-slate-300 border-dashed px-4 py-10 text-center text-slate-500 text-sm leading-relaxed dark:border-slate-700 dark:text-slate-400',
|
|
40
52
|
// Watched by the infinite scroller; the next page is requested when it appears.
|
|
41
53
|
sentinel: 'h-8 w-full shrink-0'
|
|
42
54
|
},
|
|
43
55
|
tabs: {
|
|
44
|
-
wrapper: 'flex gap-1 overflow-x-auto rounded-
|
|
45
|
-
button: 'shrink-0 rounded-
|
|
56
|
+
wrapper: 'flex gap-1 overflow-x-auto rounded-xl border border-slate-200 bg-slate-50 p-1 dark:border-slate-800 dark:bg-slate-900',
|
|
57
|
+
button: 'shrink-0 rounded-lg px-3 py-1.5 font-medium text-sm transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-sky-500 focus-visible:outline-offset-2',
|
|
46
58
|
active: 'bg-white text-slate-900 shadow-sm dark:bg-slate-800 dark:text-slate-100',
|
|
47
59
|
inactive: 'text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100'
|
|
48
60
|
},
|
|
49
61
|
// A step is a card, not a tab: it has to carry a name, a sentence saying what
|
|
50
62
|
// the step is for, and a line of evidence that it is done. Four of those do
|
|
51
|
-
// not fit on a phone side by side, so they stack and become a row at
|
|
63
|
+
// not fit on a phone side by side, so they stack and become a row at lg.
|
|
52
64
|
steps: {
|
|
53
65
|
intro: 'text-slate-600 text-sm leading-relaxed sm:max-w-3xl dark:text-slate-400',
|
|
54
|
-
wrapper: 'grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4',
|
|
55
|
-
item: 'flex min-w-0 items-start gap-
|
|
56
|
-
itemActive: 'border-sky-400 bg-sky-50 ring-1 ring-sky-400 hover:border-sky-400 dark:border-sky-600 dark:bg-sky-950 dark:ring-sky-600',
|
|
57
|
-
itemDone: 'border-emerald-200 dark:border-emerald-900',
|
|
58
|
-
itemLocked: 'opacity-
|
|
59
|
-
marker: 'flex size-
|
|
60
|
-
markerActive: 'border-sky-500 bg-sky-500 text-white dark:border-sky-500 dark:bg-sky-500',
|
|
66
|
+
wrapper: 'grid grid-cols-1 gap-2.5 sm:grid-cols-2 lg:grid-cols-4',
|
|
67
|
+
item: 'flex min-w-0 items-start gap-3 rounded-2xl border border-slate-200/80 bg-white px-3.5 py-3 text-left shadow-slate-200/40 shadow-sm transition-all hover:border-slate-300 hover:shadow dark:border-slate-800 dark:bg-slate-900 dark:shadow-none dark:hover:border-slate-700',
|
|
68
|
+
itemActive: 'border-sky-400 bg-sky-50/60 ring-1 ring-sky-400 hover:border-sky-400 dark:border-sky-600 dark:bg-sky-950/40 dark:ring-sky-600',
|
|
69
|
+
itemDone: 'border-emerald-200 dark:border-emerald-900/70',
|
|
70
|
+
itemLocked: 'opacity-60',
|
|
71
|
+
marker: 'flex size-7 shrink-0 items-center justify-center rounded-full border font-bold text-[11px] tabular-nums',
|
|
72
|
+
markerActive: 'border-sky-500 bg-sky-500 text-white shadow-sky-200 shadow-sm dark:border-sky-500 dark:bg-sky-500 dark:shadow-none',
|
|
61
73
|
markerDone: 'border-emerald-300 bg-emerald-100 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-400',
|
|
62
74
|
markerLocked: 'border-slate-200 bg-slate-100 text-slate-400 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-500',
|
|
63
|
-
name: 'font-semibold text-slate-900 text-sm dark:text-slate-100',
|
|
64
|
-
purpose: 'mt-
|
|
65
|
-
evidence: 'mt-1 font-
|
|
75
|
+
name: 'font-semibold text-slate-900 text-sm tracking-tight dark:text-slate-100',
|
|
76
|
+
purpose: 'mt-1 text-slate-600 text-xs leading-relaxed dark:text-slate-400',
|
|
77
|
+
evidence: 'mt-1.5 font-semibold text-[11px] text-emerald-700 dark:text-emerald-400'
|
|
66
78
|
},
|
|
67
79
|
field: {
|
|
68
80
|
wrapper: 'flex flex-col gap-1.5',
|
|
69
|
-
label:
|
|
70
|
-
input: `w-full border border-slate-300 bg-white px-3 text-slate-900 outline-none transition-
|
|
71
|
-
hint: 'text-slate-500 text-xs dark:text-slate-400',
|
|
72
|
-
error: 'text-rose-600 text-xs dark:text-rose-400'
|
|
81
|
+
label: `${LABEL} text-slate-500 dark:text-slate-400`,
|
|
82
|
+
input: `w-full border border-slate-300 bg-white px-3 text-slate-900 outline-none transition-all placeholder:text-slate-400 focus:border-sky-500 focus:ring-4 focus:ring-sky-500/15 disabled:opacity-60 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100 dark:placeholder:text-slate-500 ${CONTROL}`,
|
|
83
|
+
hint: 'text-slate-500 text-xs leading-relaxed dark:text-slate-400',
|
|
84
|
+
error: 'font-medium text-rose-600 text-xs dark:text-rose-400'
|
|
73
85
|
},
|
|
74
86
|
button: {
|
|
75
|
-
primary: `inline-flex items-center justify-center gap-1.5 bg-sky-600 px-
|
|
76
|
-
secondary: `inline-flex items-center justify-center gap-1.5 border border-slate-300 bg-white px-
|
|
77
|
-
ghost: `inline-flex items-center justify-center gap-1.5 px-
|
|
78
|
-
danger: `inline-flex items-center justify-center gap-1.5 bg-rose-600 px-
|
|
87
|
+
primary: `inline-flex items-center justify-center gap-1.5 rounded-lg bg-sky-600 px-4 font-semibold text-white shadow-sky-200 shadow-sm transition-all hover:bg-sky-700 hover:shadow disabled:opacity-50 disabled:shadow-none dark:bg-sky-500 dark:text-slate-950 dark:shadow-none dark:hover:bg-sky-400 ${CONTROL}`,
|
|
88
|
+
secondary: `inline-flex items-center justify-center gap-1.5 rounded-lg border border-slate-300 bg-white px-4 font-semibold text-slate-700 transition-colors hover:border-slate-400 hover:bg-slate-50 disabled:opacity-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800 ${CONTROL}`,
|
|
89
|
+
ghost: `inline-flex items-center justify-center gap-1.5 rounded-lg px-3 font-semibold text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900 disabled:opacity-50 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100 ${CONTROL}`,
|
|
90
|
+
danger: `inline-flex items-center justify-center gap-1.5 rounded-lg bg-rose-600 px-4 font-semibold text-white shadow-rose-200 shadow-sm transition-all hover:bg-rose-700 disabled:opacity-50 dark:bg-rose-600 dark:shadow-none dark:hover:bg-rose-500 ${CONTROL}`
|
|
79
91
|
},
|
|
80
92
|
badge: {
|
|
81
|
-
base: 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 font-
|
|
82
|
-
neutral: 'border-slate-200 bg-slate-100 text-slate-
|
|
93
|
+
base: 'inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 font-semibold text-[11px]',
|
|
94
|
+
neutral: 'border-slate-200 bg-slate-100 text-slate-600 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300',
|
|
83
95
|
success: 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-400',
|
|
84
96
|
warning: 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-400',
|
|
85
97
|
danger: 'border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-800 dark:bg-rose-950 dark:text-rose-400',
|
|
86
98
|
info: 'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-800 dark:bg-sky-950 dark:text-sky-400'
|
|
87
99
|
},
|
|
100
|
+
// The counts are the point of the preview, so they are the largest type on
|
|
101
|
+
// the screen — before this they were the same size as the labels beneath them.
|
|
88
102
|
stat: {
|
|
89
|
-
grid: 'grid grid-cols-2 gap-2 xs:grid-cols-3 sm:grid-cols-4 lg:grid-cols-6',
|
|
90
|
-
cell: 'flex flex-col items-center gap-
|
|
91
|
-
value: 'font-
|
|
92
|
-
label:
|
|
103
|
+
grid: 'grid grid-cols-2 gap-2.5 xs:grid-cols-3 sm:grid-cols-4 lg:grid-cols-6',
|
|
104
|
+
cell: 'flex flex-col items-center gap-1 rounded-xl border border-slate-200/80 bg-white px-2 py-3.5 text-center shadow-slate-200/40 shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:shadow-none',
|
|
105
|
+
value: 'font-bold text-2xl tabular-nums tracking-tight',
|
|
106
|
+
label: `${LABEL} text-slate-500 leading-tight dark:text-slate-400`
|
|
93
107
|
},
|
|
94
108
|
notice: {
|
|
95
|
-
info: 'rounded-
|
|
96
|
-
warning: 'rounded-
|
|
97
|
-
danger: 'rounded-
|
|
98
|
-
title: 'font-
|
|
99
|
-
body: 'mt-
|
|
109
|
+
info: 'rounded-xl border border-sky-200 bg-sky-50 p-3.5 text-sky-900 dark:border-sky-900 dark:bg-sky-950 dark:text-sky-100',
|
|
110
|
+
warning: 'rounded-xl border border-amber-200 bg-amber-50 p-3.5 text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-100',
|
|
111
|
+
danger: 'rounded-xl border border-rose-200 bg-rose-50 p-3.5 text-rose-900 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-100',
|
|
112
|
+
title: 'font-semibold text-sm',
|
|
113
|
+
body: 'mt-1 text-sm leading-relaxed opacity-90'
|
|
100
114
|
},
|
|
101
|
-
code: 'block overflow-
|
|
115
|
+
code: 'block max-h-80 overflow-auto rounded-xl border border-slate-200 bg-slate-50 p-3.5 font-mono text-[11px] text-slate-700 leading-relaxed sm:text-xs dark:border-slate-800 dark:bg-slate-950 dark:text-slate-300',
|
|
102
116
|
skeleton: {
|
|
103
|
-
line: 'h-3 animate-pulse rounded bg-slate-200 dark:bg-slate-800',
|
|
104
|
-
block: 'animate-pulse rounded-
|
|
117
|
+
line: 'h-3 animate-pulse rounded-full bg-slate-200 dark:bg-slate-800',
|
|
118
|
+
block: 'animate-pulse rounded-xl bg-slate-200 dark:bg-slate-800'
|
|
105
119
|
},
|
|
106
120
|
progress: {
|
|
107
|
-
track: 'h-
|
|
121
|
+
track: 'h-2 w-full overflow-hidden rounded-full bg-slate-200 dark:bg-slate-800',
|
|
108
122
|
bar: 'h-full rounded-full bg-sky-600 transition-[width] duration-300 ease-out dark:bg-sky-500',
|
|
109
|
-
label: 'text-slate-600 text-xs tabular-nums dark:text-slate-400'
|
|
123
|
+
label: 'font-medium text-slate-600 text-xs tabular-nums dark:text-slate-400'
|
|
110
124
|
}
|
|
111
125
|
};
|
|
112
126
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.802",
|
|
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",
|