@nixxie-cms/core 2.0.0 → 2.2.0
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/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.cjs.js +7 -7
- package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.esm.js +7 -7
- package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.cjs.js +2 -2
- package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.esm.js +2 -2
- package/admin-ui/utils/dist/nixxie-cms-core-admin-ui-utils.cjs.js +5 -5
- package/admin-ui/utils/dist/nixxie-cms-core-admin-ui-utils.esm.js +3 -3
- package/dist/{CreateItemDialog-7008b050.esm.js → CreateItemDialog-66621fe8.esm.js} +3 -3
- package/dist/{CreateItemDialog-a0cab315.cjs.js → CreateItemDialog-96b044ce.cjs.js} +4 -4
- package/dist/{Field-47f85161.esm.js → Field-1820c4e6.esm.js} +1 -0
- package/dist/{Field-ed8d7627.cjs.js → Field-38d3cdf9.cjs.js} +1 -0
- package/dist/GraphQLErrorNotice-7594a9f8.esm.js +64 -0
- package/dist/GraphQLErrorNotice-c8890f80.cjs.js +66 -0
- package/dist/{PageContainer-5ae731cc.esm.js → PageContainer-355cfbfa.esm.js} +362 -156
- package/dist/{PageContainer-abd7159f.cjs.js → PageContainer-4095555a.cjs.js} +361 -155
- package/dist/{context-af9957ed.esm.js → context-2924eaaa.esm.js} +53 -58
- package/dist/{context-b5204629.cjs.js → context-2ce61d0b.cjs.js} +53 -58
- package/dist/declarations/src/admin-ui/components/GraphQLErrorNotice.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/components/PageContainer.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
- package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/bigInt/views/index.d.ts +2 -1
- package/dist/declarations/src/fields/types/bigInt/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/bytes/views/index.d.ts +2 -1
- package/dist/declarations/src/fields/types/bytes/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/calendarDay/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/decimal/views/index.d.ts +2 -1
- package/dist/declarations/src/fields/types/decimal/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/float/views/index.d.ts +2 -1
- package/dist/declarations/src/fields/types/float/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/integer/views/index.d.ts +2 -1
- package/dist/declarations/src/fields/types/integer/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/json/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/select/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/text/views/index.d.ts +2 -1
- package/dist/declarations/src/fields/types/text/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/timestamp/views/index.d.ts.map +1 -1
- package/dist/declarations/src/fields/types/virtual/views/index.d.ts.map +1 -1
- package/dist/declarations/src/internal-unstable/admin-ui/pages/HomePage/index.d.ts.map +1 -1
- package/dist/declarations/src/internal-unstable/admin-ui/pages/ItemPage/index.d.ts.map +1 -1
- package/dist/pick-4c785a54.esm.js +34 -0
- package/dist/pick-906341bb.cjs.js +37 -0
- package/dist/{useCreateItem-1f94d252.esm.js → useCreateItem-36a75f1c.esm.js} +26 -26
- package/dist/{useCreateItem-1be4987e.cjs.js → useCreateItem-acf06f77.cjs.js} +37 -37
- package/dist/{useFilter-acc9d413.cjs.js → useFilter-c29f17a8.cjs.js} +1 -1
- package/dist/{useFilter-9b6db1f9.esm.js → useFilter-f79b2abb.esm.js} +1 -1
- package/dist/{Fields-956d9a14.esm.js → usePreventNavigation-093389dd.esm.js} +28 -2
- package/dist/{Fields-e2c28056.cjs.js → usePreventNavigation-d4f9f4fa.cjs.js} +27 -0
- package/fields/types/bigInt/views/dist/nixxie-cms-core-fields-types-bigInt-views.cjs.js +8 -0
- package/fields/types/bigInt/views/dist/nixxie-cms-core-fields-types-bigInt-views.esm.js +8 -1
- package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.cjs.js +14 -3
- package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.esm.js +15 -5
- package/fields/types/calendarDay/views/dist/nixxie-cms-core-fields-types-calendarDay-views.cjs.js +2 -1
- package/fields/types/calendarDay/views/dist/nixxie-cms-core-fields-types-calendarDay-views.esm.js +2 -1
- package/fields/types/decimal/views/dist/nixxie-cms-core-fields-types-decimal-views.cjs.js +10 -1
- package/fields/types/decimal/views/dist/nixxie-cms-core-fields-types-decimal-views.esm.js +10 -2
- package/fields/types/file/views/dist/nixxie-cms-core-fields-types-file-views.cjs.js +1 -1
- package/fields/types/file/views/dist/nixxie-cms-core-fields-types-file-views.esm.js +2 -2
- package/fields/types/float/views/dist/nixxie-cms-core-fields-types-float-views.cjs.js +10 -1
- package/fields/types/float/views/dist/nixxie-cms-core-fields-types-float-views.esm.js +10 -2
- package/fields/types/image/views/dist/nixxie-cms-core-fields-types-image-views.cjs.js +2 -1
- package/fields/types/image/views/dist/nixxie-cms-core-fields-types-image-views.esm.js +2 -1
- package/fields/types/integer/views/dist/nixxie-cms-core-fields-types-integer-views.cjs.js +8 -0
- package/fields/types/integer/views/dist/nixxie-cms-core-fields-types-integer-views.esm.js +8 -1
- package/fields/types/json/views/dist/nixxie-cms-core-fields-types-json-views.cjs.js +19 -4
- package/fields/types/json/views/dist/nixxie-cms-core-fields-types-json-views.esm.js +19 -4
- package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +18 -3
- package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +18 -3
- package/fields/types/password/views/dist/nixxie-cms-core-fields-types-password-views.cjs.js +1 -1
- package/fields/types/password/views/dist/nixxie-cms-core-fields-types-password-views.esm.js +1 -1
- package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.cjs.js +9 -7
- package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.esm.js +9 -7
- package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.cjs.js +3 -2
- package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.esm.js +3 -2
- package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.cjs.js +14 -3
- package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.esm.js +15 -5
- package/fields/types/timestamp/views/dist/nixxie-cms-core-fields-types-timestamp-views.cjs.js +2 -1
- package/fields/types/timestamp/views/dist/nixxie-cms-core-fields-types-timestamp-views.esm.js +2 -1
- package/fields/types/virtual/views/dist/nixxie-cms-core-fields-types-virtual-views.cjs.js +13 -1
- package/fields/types/virtual/views/dist/nixxie-cms-core-fields-types-virtual-views.esm.js +14 -2
- package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.js +3 -3
- package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.esm.js +3 -3
- package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.js +7 -7
- package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.esm.js +6 -6
- package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.js +34 -33
- package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.esm.js +35 -34
- package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.js +53 -13
- package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.esm.js +52 -12
- package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.js +36 -25
- package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.esm.js +36 -25
- package/package.json +2 -2
- package/src/admin-ui/components/CommandPalette.tsx +134 -27
- package/src/admin-ui/components/CreateButtonLink.tsx +20 -46
- package/src/admin-ui/components/GraphQLErrorNotice.tsx +39 -33
- package/src/admin-ui/components/Logo.tsx +5 -5
- package/src/admin-ui/components/Navigation.tsx +41 -27
- package/src/admin-ui/components/PageContainer.tsx +171 -15
- package/src/admin-ui/components/WelcomeDialog.tsx +14 -14
- package/src/admin-ui/context.tsx +5 -2
- package/src/admin-ui/utils/useCreateItem.ts +21 -1
- package/src/fields/types/bigInt/views/index.tsx +10 -1
- package/src/fields/types/bytes/views/index.tsx +14 -1
- package/src/fields/types/calendarDay/views/index.tsx +2 -1
- package/src/fields/types/decimal/views/index.tsx +7 -1
- package/src/fields/types/file/views/Field.tsx +1 -1
- package/src/fields/types/float/views/index.tsx +7 -1
- package/src/fields/types/image/views/index.tsx +1 -1
- package/src/fields/types/integer/views/index.tsx +5 -0
- package/src/fields/types/json/views/index.tsx +20 -2
- package/src/fields/types/multiselect/views/index.tsx +7 -3
- package/src/fields/types/password/views/index.tsx +1 -1
- package/src/fields/types/relationship/views/index.tsx +1 -0
- package/src/fields/types/select/views/index.tsx +2 -1
- package/src/fields/types/text/views/index.tsx +14 -1
- package/src/fields/types/timestamp/views/__tests__/index.tsx +68 -68
- package/src/fields/types/timestamp/views/index.tsx +2 -1
- package/src/fields/types/virtual/views/index.tsx +17 -2
- package/src/internal-unstable/admin-ui/pages/HomePage/index.tsx +40 -31
- package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +36 -3
- package/src/internal-unstable/admin-ui/pages/ListPage/PaginationControls.tsx +20 -33
- package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +24 -16
- package/tests/conditional-filters.test.ts +333 -326
- package/dist/GraphQLErrorNotice-cd74180d.cjs.js +0 -57
- package/dist/GraphQLErrorNotice-d9f0931b.esm.js +0 -55
- package/dist/pick-5fe45878.cjs.js +0 -71
- package/dist/pick-b7ef3115.esm.js +0 -68
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode, type PropsWithChildren, useState, type ChangeEvent } from 'react'
|
|
2
2
|
import { useRouter } from 'next/router'
|
|
3
3
|
|
|
4
|
-
import { css } from '@keystar/ui/style'
|
|
4
|
+
import { css, tokenSchema } from '@keystar/ui/style'
|
|
5
5
|
|
|
6
6
|
import type { CollectionMeta } from '../../types'
|
|
7
7
|
import { useNixxie } from '../context'
|
|
@@ -27,10 +27,12 @@ type NavItemProps = {
|
|
|
27
27
|
|
|
28
28
|
export function NavItem({ href, children, onClick, indent }: NavItemProps) {
|
|
29
29
|
const router = useRouter()
|
|
30
|
-
|
|
30
|
+
// Active on an exact match, or when the current route is nested under `href`
|
|
31
|
+
// (prefix match on a path boundary). Dashboard ('/') is only active on an
|
|
32
|
+
// exact match so it doesn't light up for every nested route.
|
|
31
33
|
const isActive =
|
|
32
34
|
router.pathname === href ||
|
|
33
|
-
(
|
|
35
|
+
(href !== '/' && router.pathname.startsWith(href + '/'))
|
|
34
36
|
|
|
35
37
|
return (
|
|
36
38
|
<a
|
|
@@ -49,8 +51,8 @@ export function NavItem({ href, children, onClick, indent }: NavItemProps) {
|
|
|
49
51
|
paddingBlock: '7px',
|
|
50
52
|
fontSize: 13,
|
|
51
53
|
fontWeight: isActive ? 500 : 400,
|
|
52
|
-
color: isActive ?
|
|
53
|
-
backgroundColor: isActive ?
|
|
54
|
+
color: isActive ? tokenSchema.color.foreground.neutralEmphasis : tokenSchema.color.foreground.neutralSecondary,
|
|
55
|
+
backgroundColor: isActive ? tokenSchema.color.background.surfaceSecondary : 'transparent',
|
|
54
56
|
textDecoration: 'none',
|
|
55
57
|
borderRadius: '0 6px 6px 0',
|
|
56
58
|
marginRight: '12px',
|
|
@@ -60,8 +62,8 @@ export function NavItem({ href, children, onClick, indent }: NavItemProps) {
|
|
|
60
62
|
transition: 'color 120ms, background-color 120ms',
|
|
61
63
|
|
|
62
64
|
'&:hover': {
|
|
63
|
-
color:
|
|
64
|
-
backgroundColor:
|
|
65
|
+
color: tokenSchema.color.foreground.neutralEmphasis,
|
|
66
|
+
backgroundColor: tokenSchema.color.background.surfaceSecondary,
|
|
65
67
|
},
|
|
66
68
|
})}
|
|
67
69
|
>
|
|
@@ -75,7 +77,7 @@ export function NavItem({ href, children, onClick, indent }: NavItemProps) {
|
|
|
75
77
|
bottom: '20%',
|
|
76
78
|
width: 2,
|
|
77
79
|
borderRadius: '0 2px 2px 0',
|
|
78
|
-
backgroundColor:
|
|
80
|
+
backgroundColor: tokenSchema.color.scale.black,
|
|
79
81
|
})}
|
|
80
82
|
/>
|
|
81
83
|
)}
|
|
@@ -113,7 +115,7 @@ function SectionLabel({ children, action }: { children: ReactNode; action?: Reac
|
|
|
113
115
|
fontWeight: 600,
|
|
114
116
|
letterSpacing: '0.10em',
|
|
115
117
|
textTransform: 'uppercase',
|
|
116
|
-
color:
|
|
118
|
+
color: tokenSchema.color.foreground.neutralSecondary,
|
|
117
119
|
})}
|
|
118
120
|
>
|
|
119
121
|
{children}
|
|
@@ -134,7 +136,7 @@ function NavDivider() {
|
|
|
134
136
|
height: 1,
|
|
135
137
|
marginInline: '16px',
|
|
136
138
|
marginBlock: '6px',
|
|
137
|
-
backgroundColor:
|
|
139
|
+
backgroundColor: tokenSchema.color.border.muted,
|
|
138
140
|
})}
|
|
139
141
|
/>
|
|
140
142
|
)
|
|
@@ -158,21 +160,21 @@ function SearchTrigger({ onClick }: { onClick?: () => void }) {
|
|
|
158
160
|
paddingInline: '10px',
|
|
159
161
|
paddingBlock: '7px',
|
|
160
162
|
borderRadius: 6,
|
|
161
|
-
border:
|
|
162
|
-
backgroundColor:
|
|
163
|
-
color:
|
|
163
|
+
border: `1px solid ${tokenSchema.color.border.muted}`,
|
|
164
|
+
backgroundColor: tokenSchema.color.background.surfaceSecondary,
|
|
165
|
+
color: tokenSchema.color.foreground.neutralSecondary,
|
|
164
166
|
fontSize: 12.5,
|
|
165
167
|
cursor: 'pointer',
|
|
166
168
|
textAlign: 'left',
|
|
167
169
|
transition: 'border-color 140ms, background 140ms, color 140ms',
|
|
168
170
|
|
|
169
171
|
'&:hover': {
|
|
170
|
-
borderColor:
|
|
171
|
-
backgroundColor:
|
|
172
|
-
color:
|
|
172
|
+
borderColor: tokenSchema.color.border.emphasis,
|
|
173
|
+
backgroundColor: tokenSchema.color.background.surfaceSecondary,
|
|
174
|
+
color: tokenSchema.color.foreground.neutralSecondary,
|
|
173
175
|
},
|
|
174
176
|
'&:focus-visible': {
|
|
175
|
-
outline:
|
|
177
|
+
outline: `2px solid ${tokenSchema.color.scale.black}`,
|
|
176
178
|
outlineOffset: 2,
|
|
177
179
|
},
|
|
178
180
|
})}
|
|
@@ -185,7 +187,7 @@ function SearchTrigger({ onClick }: { onClick?: () => void }) {
|
|
|
185
187
|
<kbd
|
|
186
188
|
className={css({
|
|
187
189
|
fontSize: 10.5,
|
|
188
|
-
color:
|
|
190
|
+
color: tokenSchema.color.foreground.neutralSecondary,
|
|
189
191
|
fontFamily: 'inherit',
|
|
190
192
|
letterSpacing: '0.02em',
|
|
191
193
|
})}
|
|
@@ -213,15 +215,15 @@ function NavSearch({ value, onChange }: { value: string; onChange: (v: string) =
|
|
|
213
215
|
paddingInline: '10px',
|
|
214
216
|
paddingBlock: '5px',
|
|
215
217
|
fontSize: 12.5,
|
|
216
|
-
border:
|
|
218
|
+
border: `1px solid ${tokenSchema.color.border.muted}`,
|
|
217
219
|
borderRadius: 5,
|
|
218
|
-
backgroundColor:
|
|
219
|
-
color:
|
|
220
|
+
backgroundColor: tokenSchema.color.background.surfaceSecondary,
|
|
221
|
+
color: tokenSchema.color.foreground.neutralEmphasis,
|
|
220
222
|
outline: 'none',
|
|
221
223
|
boxSizing: 'border-box',
|
|
222
224
|
transition: 'border-color 140ms',
|
|
223
|
-
'&:focus': { borderColor:
|
|
224
|
-
'&::placeholder': { color:
|
|
225
|
+
'&:focus': { borderColor: tokenSchema.color.alias.borderHovered },
|
|
226
|
+
'&::placeholder': { color: tokenSchema.color.foreground.neutralTertiary },
|
|
225
227
|
'&::-webkit-search-cancel-button': { display: 'none' },
|
|
226
228
|
})}
|
|
227
229
|
/>
|
|
@@ -257,7 +259,13 @@ function CollectionsSection({
|
|
|
257
259
|
{showSearch && <NavSearch value={search} onChange={setSearch} />}
|
|
258
260
|
|
|
259
261
|
{filtered.length === 0 ? (
|
|
260
|
-
<p
|
|
262
|
+
<p
|
|
263
|
+
className={css({
|
|
264
|
+
margin: '4px 16px',
|
|
265
|
+
fontSize: 12.5,
|
|
266
|
+
color: tokenSchema.color.foreground.neutralSecondary,
|
|
267
|
+
})}
|
|
268
|
+
>
|
|
261
269
|
No results
|
|
262
270
|
</p>
|
|
263
271
|
) : (
|
|
@@ -270,7 +278,7 @@ function CollectionsSection({
|
|
|
270
278
|
marginLeft: 6,
|
|
271
279
|
fontSize: 10,
|
|
272
280
|
fontWeight: 500,
|
|
273
|
-
color:
|
|
281
|
+
color: tokenSchema.color.foreground.neutralSecondary,
|
|
274
282
|
letterSpacing: '0.04em',
|
|
275
283
|
textTransform: 'uppercase',
|
|
276
284
|
})}
|
|
@@ -326,7 +334,13 @@ export function Navigation({ onNavItemClick, onCmdK }: InternalNavigationProps)
|
|
|
326
334
|
{visibleLists.length > 0 ? (
|
|
327
335
|
<CollectionsSection lists={visibleLists} onNavItemClick={onNavItemClick} />
|
|
328
336
|
) : (
|
|
329
|
-
<p
|
|
337
|
+
<p
|
|
338
|
+
className={css({
|
|
339
|
+
margin: '8px 16px',
|
|
340
|
+
fontSize: 12.5,
|
|
341
|
+
color: tokenSchema.color.foreground.neutralSecondary,
|
|
342
|
+
})}
|
|
343
|
+
>
|
|
330
344
|
No collections
|
|
331
345
|
</p>
|
|
332
346
|
)}
|
|
@@ -374,7 +388,7 @@ export function NavFooter({ children }: PropsWithChildren) {
|
|
|
374
388
|
className={css({
|
|
375
389
|
paddingInline: '12px',
|
|
376
390
|
paddingBlock: '10px',
|
|
377
|
-
borderTop:
|
|
391
|
+
borderTop: `1px solid ${tokenSchema.color.border.muted}`,
|
|
378
392
|
marginTop: 'auto',
|
|
379
393
|
})}
|
|
380
394
|
>
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import NextHead from 'next/head'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type HTMLAttributes,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react'
|
|
3
9
|
|
|
4
|
-
import { css } from '@keystar/ui/style'
|
|
10
|
+
import { css, tokenSchema } from '@keystar/ui/style'
|
|
5
11
|
|
|
6
12
|
import { Logo } from './Logo'
|
|
7
13
|
import { Navigation } from './Navigation'
|
|
@@ -17,7 +23,7 @@ export function PageWrapper(props: HTMLAttributes<HTMLElement>) {
|
|
|
17
23
|
display: 'flex',
|
|
18
24
|
height: '100vh',
|
|
19
25
|
overflow: 'hidden',
|
|
20
|
-
backgroundColor:
|
|
26
|
+
backgroundColor: tokenSchema.color.background.surface,
|
|
21
27
|
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
22
28
|
})}
|
|
23
29
|
{...props}
|
|
@@ -25,6 +31,51 @@ export function PageWrapper(props: HTMLAttributes<HTMLElement>) {
|
|
|
25
31
|
)
|
|
26
32
|
}
|
|
27
33
|
|
|
34
|
+
// ---- Skip to content (WCAG 2.4.1 Bypass Blocks) ----
|
|
35
|
+
function SkipLink() {
|
|
36
|
+
return (
|
|
37
|
+
<a
|
|
38
|
+
href="#main-content"
|
|
39
|
+
className={css({
|
|
40
|
+
// Visually hidden, off-screen by default — kept out of the layout for mouse users.
|
|
41
|
+
position: 'absolute',
|
|
42
|
+
top: 0,
|
|
43
|
+
left: 0,
|
|
44
|
+
width: 1,
|
|
45
|
+
height: 1,
|
|
46
|
+
padding: 0,
|
|
47
|
+
margin: -1,
|
|
48
|
+
overflow: 'hidden',
|
|
49
|
+
clip: 'rect(0 0 0 0)',
|
|
50
|
+
whiteSpace: 'nowrap',
|
|
51
|
+
border: 0,
|
|
52
|
+
zIndex: 60,
|
|
53
|
+
|
|
54
|
+
// Reveal on keyboard focus.
|
|
55
|
+
'&:focus, &:focus-visible': {
|
|
56
|
+
width: 'auto',
|
|
57
|
+
height: 'auto',
|
|
58
|
+
padding: '10px 16px',
|
|
59
|
+
margin: 8,
|
|
60
|
+
clip: 'auto',
|
|
61
|
+
overflow: 'visible',
|
|
62
|
+
backgroundColor: tokenSchema.color.background.canvas,
|
|
63
|
+
color: tokenSchema.color.foreground.neutralEmphasis,
|
|
64
|
+
borderRadius: 6,
|
|
65
|
+
boxShadow: `0 2px 8px ${tokenSchema.color.shadow.regular}`,
|
|
66
|
+
outline: `2px solid ${tokenSchema.color.foreground.neutralEmphasis}`,
|
|
67
|
+
outlineOffset: 2,
|
|
68
|
+
textDecoration: 'none',
|
|
69
|
+
fontSize: 14,
|
|
70
|
+
fontWeight: 500,
|
|
71
|
+
},
|
|
72
|
+
})}
|
|
73
|
+
>
|
|
74
|
+
Skip to content
|
|
75
|
+
</a>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
28
79
|
// ---- Mobile overlay ----
|
|
29
80
|
function Backdrop({ isVisible, onClick }: { isVisible: boolean; onClick: () => void }) {
|
|
30
81
|
return (
|
|
@@ -34,7 +85,7 @@ function Backdrop({ isVisible, onClick }: { isVisible: boolean; onClick: () => v
|
|
|
34
85
|
className={css({
|
|
35
86
|
position: 'fixed',
|
|
36
87
|
inset: 0,
|
|
37
|
-
backgroundColor:
|
|
88
|
+
backgroundColor: tokenSchema.color.alias.blanket,
|
|
38
89
|
zIndex: 40,
|
|
39
90
|
transition: 'opacity 200ms',
|
|
40
91
|
opacity: isVisible ? 1 : 0,
|
|
@@ -47,6 +98,9 @@ function Backdrop({ isVisible, onClick }: { isVisible: boolean; onClick: () => v
|
|
|
47
98
|
}
|
|
48
99
|
|
|
49
100
|
// ---- Sidebar — pure white, precise ----
|
|
101
|
+
const FOCUSABLE_SELECTOR =
|
|
102
|
+
'a[href],button:not([disabled]),input:not([disabled]),[tabindex]:not([tabindex="-1"])'
|
|
103
|
+
|
|
50
104
|
function Sidebar({
|
|
51
105
|
isOpen,
|
|
52
106
|
onClose,
|
|
@@ -56,10 +110,93 @@ function Sidebar({
|
|
|
56
110
|
onClose: () => void
|
|
57
111
|
onCmdK: () => void
|
|
58
112
|
}) {
|
|
113
|
+
const asideRef = useRef<HTMLElement>(null)
|
|
114
|
+
// Element to restore focus to (the hamburger) once the drawer closes.
|
|
115
|
+
const lastFocusedRef = useRef<HTMLElement | null>(null)
|
|
116
|
+
|
|
117
|
+
// Mobile drawer a11y: focus trap, Escape-to-close, initial focus, and focus
|
|
118
|
+
// restore. `isOpen` is only ever true on mobile (the resize handler forces it
|
|
119
|
+
// false at >=768px), so gating on it doubles as a mobile gate.
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!isOpen) return
|
|
122
|
+
|
|
123
|
+
const aside = asideRef.current
|
|
124
|
+
if (!aside) return
|
|
125
|
+
|
|
126
|
+
// Save the currently focused element so we can restore it on close.
|
|
127
|
+
try {
|
|
128
|
+
const active = document.activeElement
|
|
129
|
+
lastFocusedRef.current = active instanceof HTMLElement ? active : null
|
|
130
|
+
} catch {
|
|
131
|
+
lastFocusedRef.current = null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const getFocusable = (): HTMLElement[] => {
|
|
135
|
+
try {
|
|
136
|
+
return Array.from(
|
|
137
|
+
aside.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)
|
|
138
|
+
).filter(el => el.offsetParent !== null || el === document.activeElement)
|
|
139
|
+
} catch {
|
|
140
|
+
return []
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Move initial focus into the drawer.
|
|
145
|
+
try {
|
|
146
|
+
getFocusable()[0]?.focus()
|
|
147
|
+
} catch {
|
|
148
|
+
/* noop */
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
152
|
+
if (e.key === 'Escape') {
|
|
153
|
+
e.preventDefault()
|
|
154
|
+
onClose()
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
if (e.key !== 'Tab') return
|
|
158
|
+
|
|
159
|
+
const focusable = getFocusable()
|
|
160
|
+
if (focusable.length === 0) {
|
|
161
|
+
// Nothing focusable inside — keep focus from escaping anyway.
|
|
162
|
+
e.preventDefault()
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const first = focusable[0]
|
|
167
|
+
const last = focusable[focusable.length - 1]
|
|
168
|
+
const current = document.activeElement
|
|
169
|
+
|
|
170
|
+
if (e.shiftKey) {
|
|
171
|
+
if (current === first || !aside?.contains(current)) {
|
|
172
|
+
e.preventDefault()
|
|
173
|
+
last.focus()
|
|
174
|
+
}
|
|
175
|
+
} else if (current === last || !aside?.contains(current)) {
|
|
176
|
+
e.preventDefault()
|
|
177
|
+
first.focus()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
document.addEventListener('keydown', onKeyDown, true)
|
|
182
|
+
|
|
183
|
+
return () => {
|
|
184
|
+
document.removeEventListener('keydown', onKeyDown, true)
|
|
185
|
+
// Restore focus to the trigger that opened the drawer.
|
|
186
|
+
try {
|
|
187
|
+
lastFocusedRef.current?.focus()
|
|
188
|
+
} catch {
|
|
189
|
+
/* noop */
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}, [isOpen, onClose])
|
|
193
|
+
|
|
59
194
|
return (
|
|
60
195
|
<>
|
|
61
196
|
<Backdrop isVisible={isOpen} onClick={onClose} />
|
|
62
197
|
<aside
|
|
198
|
+
id="nixxie-sidebar"
|
|
199
|
+
ref={asideRef}
|
|
63
200
|
className={css({
|
|
64
201
|
position: 'fixed',
|
|
65
202
|
top: 0,
|
|
@@ -69,14 +206,19 @@ function Sidebar({
|
|
|
69
206
|
zIndex: 50,
|
|
70
207
|
display: 'flex',
|
|
71
208
|
flexDirection: 'column',
|
|
72
|
-
backgroundColor:
|
|
73
|
-
borderRight:
|
|
209
|
+
backgroundColor: tokenSchema.color.background.canvas,
|
|
210
|
+
borderRight: `1px solid ${tokenSchema.color.border.muted}`,
|
|
74
211
|
transform: isOpen ? 'translateX(0)' : `translateX(-${SIDEBAR_WIDTH}px)`,
|
|
212
|
+
// Closed off-canvas drawer must leave the tab order / AT tree on
|
|
213
|
+
// mobile; `visibility:hidden` removes its descendants from both.
|
|
214
|
+
visibility: isOpen ? 'visible' : 'hidden',
|
|
75
215
|
transition: 'transform 220ms cubic-bezier(0.4,0,0.2,1)',
|
|
76
216
|
|
|
77
217
|
'@media (min-width: 768px)': {
|
|
78
218
|
position: 'relative',
|
|
79
219
|
transform: 'translateX(0)',
|
|
220
|
+
// Persistent desktop rail is always reachable.
|
|
221
|
+
visibility: 'visible',
|
|
80
222
|
flexShrink: 0,
|
|
81
223
|
},
|
|
82
224
|
})}
|
|
@@ -88,7 +230,7 @@ function Sidebar({
|
|
|
88
230
|
alignItems: 'center',
|
|
89
231
|
height: TOPBAR_HEIGHT,
|
|
90
232
|
paddingInline: '16px',
|
|
91
|
-
borderBottom:
|
|
233
|
+
borderBottom: `1px solid ${tokenSchema.color.border.muted}`,
|
|
92
234
|
flexShrink: 0,
|
|
93
235
|
})}
|
|
94
236
|
>
|
|
@@ -103,7 +245,10 @@ function Sidebar({
|
|
|
103
245
|
overflowX: 'hidden',
|
|
104
246
|
WebkitOverflowScrolling: 'touch',
|
|
105
247
|
'&::-webkit-scrollbar': { width: 3 },
|
|
106
|
-
'&::-webkit-scrollbar-thumb': {
|
|
248
|
+
'&::-webkit-scrollbar-thumb': {
|
|
249
|
+
background: tokenSchema.color.border.muted,
|
|
250
|
+
borderRadius: 3,
|
|
251
|
+
},
|
|
107
252
|
})}
|
|
108
253
|
>
|
|
109
254
|
<Navigation onNavItemClick={onClose} onCmdK={onCmdK} />
|
|
@@ -131,8 +276,8 @@ function TopBar({
|
|
|
131
276
|
height: TOPBAR_HEIGHT,
|
|
132
277
|
paddingInline: '24px',
|
|
133
278
|
gap: '14px',
|
|
134
|
-
backgroundColor:
|
|
135
|
-
borderBottom:
|
|
279
|
+
backgroundColor: tokenSchema.color.background.canvas,
|
|
280
|
+
borderBottom: `1px solid ${tokenSchema.color.border.muted}`,
|
|
136
281
|
flexShrink: 0,
|
|
137
282
|
zIndex: 30,
|
|
138
283
|
})}
|
|
@@ -142,6 +287,7 @@ function TopBar({
|
|
|
142
287
|
onClick={onMenuClick}
|
|
143
288
|
aria-label={isSidebarOpen ? 'Close menu' : 'Open menu'}
|
|
144
289
|
aria-expanded={isSidebarOpen}
|
|
290
|
+
aria-controls="nixxie-sidebar"
|
|
145
291
|
className={css({
|
|
146
292
|
display: 'inline-flex',
|
|
147
293
|
alignItems: 'center',
|
|
@@ -149,13 +295,16 @@ function TopBar({
|
|
|
149
295
|
width: 32,
|
|
150
296
|
height: 32,
|
|
151
297
|
borderRadius: 6,
|
|
152
|
-
border:
|
|
298
|
+
border: `1px solid ${tokenSchema.color.border.muted}`,
|
|
153
299
|
background: 'transparent',
|
|
154
300
|
cursor: 'pointer',
|
|
155
301
|
flexShrink: 0,
|
|
156
|
-
color:
|
|
302
|
+
color: tokenSchema.color.foreground.neutralTertiary,
|
|
157
303
|
transition: 'color 130ms, background 130ms',
|
|
158
|
-
'&:hover': {
|
|
304
|
+
'&:hover': {
|
|
305
|
+
background: tokenSchema.color.background.surfaceSecondary,
|
|
306
|
+
color: tokenSchema.color.foreground.neutralEmphasis,
|
|
307
|
+
},
|
|
159
308
|
'@media (min-width: 768px)': { display: 'none' },
|
|
160
309
|
})}
|
|
161
310
|
>
|
|
@@ -222,6 +371,8 @@ function TopBar({
|
|
|
222
371
|
function MainContent(props: HTMLAttributes<HTMLElement>) {
|
|
223
372
|
return (
|
|
224
373
|
<main
|
|
374
|
+
id="main-content"
|
|
375
|
+
tabIndex={-1}
|
|
225
376
|
className={css({
|
|
226
377
|
flex: 1,
|
|
227
378
|
display: 'flex',
|
|
@@ -245,8 +396,11 @@ function ContentScroller(props: HTMLAttributes<HTMLDivElement>) {
|
|
|
245
396
|
overflowX: 'hidden',
|
|
246
397
|
WebkitOverflowScrolling: 'touch',
|
|
247
398
|
'&::-webkit-scrollbar': { width: 5 },
|
|
248
|
-
'&::-webkit-scrollbar-thumb': {
|
|
249
|
-
|
|
399
|
+
'&::-webkit-scrollbar-thumb': {
|
|
400
|
+
background: tokenSchema.color.border.muted,
|
|
401
|
+
borderRadius: 4,
|
|
402
|
+
},
|
|
403
|
+
'&::-webkit-scrollbar-thumb:hover': { background: tokenSchema.color.border.emphasis },
|
|
250
404
|
})}
|
|
251
405
|
{...props}
|
|
252
406
|
/>
|
|
@@ -290,6 +444,8 @@ export function PageContainer({ children, header, title }: PageContainerProps) {
|
|
|
290
444
|
<title key="title">{title ? `Nixxie – ${title}` : 'Nixxie'}</title>
|
|
291
445
|
</NextHead>
|
|
292
446
|
|
|
447
|
+
<SkipLink />
|
|
448
|
+
|
|
293
449
|
<Sidebar
|
|
294
450
|
isOpen={isSidebarOpen}
|
|
295
451
|
onClose={() => setSidebarOpen(false)}
|
|
@@ -20,7 +20,7 @@ function validateEmail(value: string) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function WelcomeDialog() {
|
|
23
|
-
const [subscribe, setSubscribe] = useState([
|
|
23
|
+
const [subscribe, setSubscribe] = useState<string[]>([])
|
|
24
24
|
const [email, setEmail] = useState('')
|
|
25
25
|
const [error, setError] = useState<string | null>(null)
|
|
26
26
|
const [loading, setLoading] = useState(false)
|
|
@@ -33,25 +33,26 @@ export function WelcomeDialog() {
|
|
|
33
33
|
|
|
34
34
|
// Check if user wants to subscribe and a valid email address
|
|
35
35
|
if (subscribe.length) {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// Synchronous validation should not show pending state
|
|
38
37
|
if (!validateEmail(email)) {
|
|
39
38
|
setError(emailValidationMessage)
|
|
40
39
|
return
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
setLoading(true)
|
|
43
|
+
|
|
43
44
|
const tags = ['source:@nixxie-cms/auth']
|
|
44
45
|
subscribe.forEach(list => tags.push(`list:${list}`))
|
|
45
46
|
|
|
46
|
-
const res = await fetch(newsletterUrl, {
|
|
47
|
-
method: 'POST',
|
|
48
|
-
headers: {
|
|
49
|
-
'Content-Type': 'application/json',
|
|
50
|
-
},
|
|
51
|
-
body: JSON.stringify({ email, tags }),
|
|
52
|
-
})
|
|
53
|
-
|
|
54
47
|
try {
|
|
48
|
+
const res = await fetch(newsletterUrl, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({ email, tags }),
|
|
54
|
+
})
|
|
55
|
+
|
|
55
56
|
if (res.status !== 200) {
|
|
56
57
|
const { error } = await res.json()
|
|
57
58
|
setError(error)
|
|
@@ -61,9 +62,9 @@ export function WelcomeDialog() {
|
|
|
61
62
|
// network errors or failed parse
|
|
62
63
|
setError(e.message.toString())
|
|
63
64
|
return
|
|
65
|
+
} finally {
|
|
66
|
+
setLoading(false)
|
|
64
67
|
}
|
|
65
|
-
|
|
66
|
-
setLoading(false)
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
dialog.dismiss()
|
|
@@ -101,7 +102,6 @@ export function WelcomeDialog() {
|
|
|
101
102
|
/>
|
|
102
103
|
<CheckboxGroup onChange={setSubscribe} value={subscribe}>
|
|
103
104
|
<Checkbox value="nixxie">Nixxie news</Checkbox>
|
|
104
|
-
<Checkbox value="nixxie">Nixxie news</Checkbox>
|
|
105
105
|
</CheckboxGroup>
|
|
106
106
|
</VStack>
|
|
107
107
|
</form>
|
package/src/admin-ui/context.tsx
CHANGED
|
@@ -142,8 +142,7 @@ function InternalNixxieProvider({
|
|
|
142
142
|
useEffect(() => {
|
|
143
143
|
injectGlobal({
|
|
144
144
|
body: {
|
|
145
|
-
fontFamily:
|
|
146
|
-
"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
145
|
+
fontFamily: tokenSchema.typography.fontFamily.base,
|
|
147
146
|
WebkitFontSmoothing: 'antialiased',
|
|
148
147
|
MozOsxFontSmoothing: 'grayscale',
|
|
149
148
|
},
|
|
@@ -165,6 +164,10 @@ function InternalNixxieProvider({
|
|
|
165
164
|
if (loading) return null
|
|
166
165
|
// if (!meta) return null
|
|
167
166
|
return (
|
|
167
|
+
// The app is intentionally pinned to the light color scheme for now. Now
|
|
168
|
+
// that the rest of the chrome is token-based, enabling system/dark is a
|
|
169
|
+
// one-line change (drop `colorScheme="light"` here and the
|
|
170
|
+
// `[data-nixxie-content]` override below) pending a visual-QA pass.
|
|
168
171
|
<KeystarProvider router={keystarRouter} colorScheme="light">
|
|
169
172
|
<ClientSideOnlyDocumentElement bodyBackground="canvas" />
|
|
170
173
|
<NextHead>
|
|
@@ -14,6 +14,22 @@ import type { CollectionMeta } from '../../types'
|
|
|
14
14
|
import { type ErrorLike, gql, type TypedDocumentNode, useMutation } from '../apollo'
|
|
15
15
|
import { usePreventNavigation } from './usePreventNavigation'
|
|
16
16
|
|
|
17
|
+
// after forcing validation, move focus to the first invalid field so the user
|
|
18
|
+
// can see and fix it. runs on the next frame so the DOM reflects the new
|
|
19
|
+
// aria-invalid state, and is fully defensive so it can never throw.
|
|
20
|
+
function focusFirstInvalidField() {
|
|
21
|
+
requestAnimationFrame(() => {
|
|
22
|
+
try {
|
|
23
|
+
const el = document.querySelector<HTMLElement>('[aria-invalid="true"]')
|
|
24
|
+
if (!el) return
|
|
25
|
+
el.focus({ preventScroll: false })
|
|
26
|
+
el.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|
27
|
+
} catch {
|
|
28
|
+
// ignore — focusing is a best-effort enhancement
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
type CreateItemHookResult = {
|
|
18
34
|
state: 'editing' | 'loading' | 'created'
|
|
19
35
|
shouldPreventNavigation: boolean
|
|
@@ -81,7 +97,11 @@ export function useCreateItem(list: CollectionMeta): CreateItemHookResult {
|
|
|
81
97
|
const newForceValidation = invalidFields.size !== 0
|
|
82
98
|
setForceValidation(newForceValidation)
|
|
83
99
|
|
|
84
|
-
if (newForceValidation)
|
|
100
|
+
if (newForceValidation) {
|
|
101
|
+
toastQueue.critical('Please fix the highlighted field(s) before saving.')
|
|
102
|
+
focusFirstInvalidField()
|
|
103
|
+
return
|
|
104
|
+
}
|
|
85
105
|
|
|
86
106
|
let outputData: { item: { id: string; label: string | null } }
|
|
87
107
|
try {
|
|
@@ -6,7 +6,12 @@ import { TextField } from '@keystar/ui/text-field'
|
|
|
6
6
|
import { Heading, Text } from '@keystar/ui/typography'
|
|
7
7
|
|
|
8
8
|
import type { SimpleFieldTypeInfo } from '../../../../types'
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
CellComponent,
|
|
11
|
+
FieldController,
|
|
12
|
+
FieldControllerConfig,
|
|
13
|
+
FieldProps,
|
|
14
|
+
} from '../../../../types'
|
|
10
15
|
import { entriesTyped } from '../../../../lib/core/utils'
|
|
11
16
|
|
|
12
17
|
const TYPE_OPERATOR_MAP = {
|
|
@@ -252,3 +257,7 @@ export function Field({
|
|
|
252
257
|
/>
|
|
253
258
|
)
|
|
254
259
|
}
|
|
260
|
+
|
|
261
|
+
export const Cell: CellComponent<typeof controller> = ({ value }) => {
|
|
262
|
+
return value != null && value !== '' ? <Text>{value}</Text> : null
|
|
263
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { TextField } from '@keystar/ui/text-field'
|
|
2
|
+
import { Text } from '@keystar/ui/typography'
|
|
2
3
|
import { useState } from 'react'
|
|
3
4
|
|
|
4
5
|
import type { TextFieldMeta } from '..'
|
|
5
6
|
import { NullableFieldWrapper } from '../../../../admin-ui/components'
|
|
6
7
|
import { entriesTyped } from '../../../../lib/core/utils'
|
|
7
8
|
import type {
|
|
9
|
+
CellComponent,
|
|
8
10
|
FieldController,
|
|
9
11
|
FieldControllerConfig,
|
|
10
12
|
FieldProps,
|
|
@@ -44,7 +46,14 @@ export function Field(props: FieldProps<typeof controller>) {
|
|
|
44
46
|
label={field.label}
|
|
45
47
|
errorMessage={
|
|
46
48
|
!!validationMessages.length && (shouldShowErrors || forceValidation)
|
|
47
|
-
? validationMessages.
|
|
49
|
+
? validationMessages.length === 1
|
|
50
|
+
? validationMessages[0]
|
|
51
|
+
: validationMessages.map((message, i) => (
|
|
52
|
+
<Text key={i}>
|
|
53
|
+
{i > 0 && <br />}
|
|
54
|
+
{message}
|
|
55
|
+
</Text>
|
|
56
|
+
))
|
|
48
57
|
: undefined
|
|
49
58
|
}
|
|
50
59
|
isDisabled={isNull}
|
|
@@ -71,6 +80,10 @@ export function Field(props: FieldProps<typeof controller>) {
|
|
|
71
80
|
)
|
|
72
81
|
}
|
|
73
82
|
|
|
83
|
+
export const Cell: CellComponent<typeof controller> = ({ value }) => {
|
|
84
|
+
return value ? <Text truncate>{value}</Text> : null
|
|
85
|
+
}
|
|
86
|
+
|
|
74
87
|
type Config = FieldControllerConfig<TextFieldMeta>
|
|
75
88
|
|
|
76
89
|
function validate(value: TextValue, isRequired: boolean, fieldLabel: string): string[] {
|
|
@@ -49,12 +49,13 @@ export function Field(props: FieldProps<typeof controller>) {
|
|
|
49
49
|
description={field.description}
|
|
50
50
|
isDisabled={!parsedValue}
|
|
51
51
|
isReadOnly
|
|
52
|
+
placeholder="yyyy-mm-dd"
|
|
52
53
|
value={
|
|
53
54
|
parsedValue
|
|
54
55
|
? isReadonlyUTC
|
|
55
56
|
? parsedValue.toString()
|
|
56
57
|
: dateFormatter.format(parsedValue.toDate(getLocalTimeZone()))
|
|
57
|
-
: '
|
|
58
|
+
: ''
|
|
58
59
|
}
|
|
59
60
|
/>
|
|
60
61
|
{!!parsedValue && (
|