@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.
Files changed (128) hide show
  1. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.cjs.js +7 -7
  2. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.esm.js +7 -7
  3. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.cjs.js +2 -2
  4. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.esm.js +2 -2
  5. package/admin-ui/utils/dist/nixxie-cms-core-admin-ui-utils.cjs.js +5 -5
  6. package/admin-ui/utils/dist/nixxie-cms-core-admin-ui-utils.esm.js +3 -3
  7. package/dist/{CreateItemDialog-7008b050.esm.js → CreateItemDialog-66621fe8.esm.js} +3 -3
  8. package/dist/{CreateItemDialog-a0cab315.cjs.js → CreateItemDialog-96b044ce.cjs.js} +4 -4
  9. package/dist/{Field-47f85161.esm.js → Field-1820c4e6.esm.js} +1 -0
  10. package/dist/{Field-ed8d7627.cjs.js → Field-38d3cdf9.cjs.js} +1 -0
  11. package/dist/GraphQLErrorNotice-7594a9f8.esm.js +64 -0
  12. package/dist/GraphQLErrorNotice-c8890f80.cjs.js +66 -0
  13. package/dist/{PageContainer-5ae731cc.esm.js → PageContainer-355cfbfa.esm.js} +362 -156
  14. package/dist/{PageContainer-abd7159f.cjs.js → PageContainer-4095555a.cjs.js} +361 -155
  15. package/dist/{context-af9957ed.esm.js → context-2924eaaa.esm.js} +53 -58
  16. package/dist/{context-b5204629.cjs.js → context-2ce61d0b.cjs.js} +53 -58
  17. package/dist/declarations/src/admin-ui/components/GraphQLErrorNotice.d.ts.map +1 -1
  18. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  19. package/dist/declarations/src/admin-ui/components/PageContainer.d.ts.map +1 -1
  20. package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
  21. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
  22. package/dist/declarations/src/fields/types/bigInt/views/index.d.ts +2 -1
  23. package/dist/declarations/src/fields/types/bigInt/views/index.d.ts.map +1 -1
  24. package/dist/declarations/src/fields/types/bytes/views/index.d.ts +2 -1
  25. package/dist/declarations/src/fields/types/bytes/views/index.d.ts.map +1 -1
  26. package/dist/declarations/src/fields/types/calendarDay/views/index.d.ts.map +1 -1
  27. package/dist/declarations/src/fields/types/decimal/views/index.d.ts +2 -1
  28. package/dist/declarations/src/fields/types/decimal/views/index.d.ts.map +1 -1
  29. package/dist/declarations/src/fields/types/float/views/index.d.ts +2 -1
  30. package/dist/declarations/src/fields/types/float/views/index.d.ts.map +1 -1
  31. package/dist/declarations/src/fields/types/integer/views/index.d.ts +2 -1
  32. package/dist/declarations/src/fields/types/integer/views/index.d.ts.map +1 -1
  33. package/dist/declarations/src/fields/types/json/views/index.d.ts.map +1 -1
  34. package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
  35. package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
  36. package/dist/declarations/src/fields/types/select/views/index.d.ts.map +1 -1
  37. package/dist/declarations/src/fields/types/text/views/index.d.ts +2 -1
  38. package/dist/declarations/src/fields/types/text/views/index.d.ts.map +1 -1
  39. package/dist/declarations/src/fields/types/timestamp/views/index.d.ts.map +1 -1
  40. package/dist/declarations/src/fields/types/virtual/views/index.d.ts.map +1 -1
  41. package/dist/declarations/src/internal-unstable/admin-ui/pages/HomePage/index.d.ts.map +1 -1
  42. package/dist/declarations/src/internal-unstable/admin-ui/pages/ItemPage/index.d.ts.map +1 -1
  43. package/dist/pick-4c785a54.esm.js +34 -0
  44. package/dist/pick-906341bb.cjs.js +37 -0
  45. package/dist/{useCreateItem-1f94d252.esm.js → useCreateItem-36a75f1c.esm.js} +26 -26
  46. package/dist/{useCreateItem-1be4987e.cjs.js → useCreateItem-acf06f77.cjs.js} +37 -37
  47. package/dist/{useFilter-acc9d413.cjs.js → useFilter-c29f17a8.cjs.js} +1 -1
  48. package/dist/{useFilter-9b6db1f9.esm.js → useFilter-f79b2abb.esm.js} +1 -1
  49. package/dist/{Fields-956d9a14.esm.js → usePreventNavigation-093389dd.esm.js} +28 -2
  50. package/dist/{Fields-e2c28056.cjs.js → usePreventNavigation-d4f9f4fa.cjs.js} +27 -0
  51. package/fields/types/bigInt/views/dist/nixxie-cms-core-fields-types-bigInt-views.cjs.js +8 -0
  52. package/fields/types/bigInt/views/dist/nixxie-cms-core-fields-types-bigInt-views.esm.js +8 -1
  53. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.cjs.js +14 -3
  54. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.esm.js +15 -5
  55. package/fields/types/calendarDay/views/dist/nixxie-cms-core-fields-types-calendarDay-views.cjs.js +2 -1
  56. package/fields/types/calendarDay/views/dist/nixxie-cms-core-fields-types-calendarDay-views.esm.js +2 -1
  57. package/fields/types/decimal/views/dist/nixxie-cms-core-fields-types-decimal-views.cjs.js +10 -1
  58. package/fields/types/decimal/views/dist/nixxie-cms-core-fields-types-decimal-views.esm.js +10 -2
  59. package/fields/types/file/views/dist/nixxie-cms-core-fields-types-file-views.cjs.js +1 -1
  60. package/fields/types/file/views/dist/nixxie-cms-core-fields-types-file-views.esm.js +2 -2
  61. package/fields/types/float/views/dist/nixxie-cms-core-fields-types-float-views.cjs.js +10 -1
  62. package/fields/types/float/views/dist/nixxie-cms-core-fields-types-float-views.esm.js +10 -2
  63. package/fields/types/image/views/dist/nixxie-cms-core-fields-types-image-views.cjs.js +2 -1
  64. package/fields/types/image/views/dist/nixxie-cms-core-fields-types-image-views.esm.js +2 -1
  65. package/fields/types/integer/views/dist/nixxie-cms-core-fields-types-integer-views.cjs.js +8 -0
  66. package/fields/types/integer/views/dist/nixxie-cms-core-fields-types-integer-views.esm.js +8 -1
  67. package/fields/types/json/views/dist/nixxie-cms-core-fields-types-json-views.cjs.js +19 -4
  68. package/fields/types/json/views/dist/nixxie-cms-core-fields-types-json-views.esm.js +19 -4
  69. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +18 -3
  70. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +18 -3
  71. package/fields/types/password/views/dist/nixxie-cms-core-fields-types-password-views.cjs.js +1 -1
  72. package/fields/types/password/views/dist/nixxie-cms-core-fields-types-password-views.esm.js +1 -1
  73. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.cjs.js +9 -7
  74. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.esm.js +9 -7
  75. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.cjs.js +3 -2
  76. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.esm.js +3 -2
  77. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.cjs.js +14 -3
  78. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.esm.js +15 -5
  79. package/fields/types/timestamp/views/dist/nixxie-cms-core-fields-types-timestamp-views.cjs.js +2 -1
  80. package/fields/types/timestamp/views/dist/nixxie-cms-core-fields-types-timestamp-views.esm.js +2 -1
  81. package/fields/types/virtual/views/dist/nixxie-cms-core-fields-types-virtual-views.cjs.js +13 -1
  82. package/fields/types/virtual/views/dist/nixxie-cms-core-fields-types-virtual-views.esm.js +14 -2
  83. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.js +3 -3
  84. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.esm.js +3 -3
  85. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.js +7 -7
  86. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.esm.js +6 -6
  87. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.js +34 -33
  88. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.esm.js +35 -34
  89. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.js +53 -13
  90. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.esm.js +52 -12
  91. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.js +36 -25
  92. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.esm.js +36 -25
  93. package/package.json +2 -2
  94. package/src/admin-ui/components/CommandPalette.tsx +134 -27
  95. package/src/admin-ui/components/CreateButtonLink.tsx +20 -46
  96. package/src/admin-ui/components/GraphQLErrorNotice.tsx +39 -33
  97. package/src/admin-ui/components/Logo.tsx +5 -5
  98. package/src/admin-ui/components/Navigation.tsx +41 -27
  99. package/src/admin-ui/components/PageContainer.tsx +171 -15
  100. package/src/admin-ui/components/WelcomeDialog.tsx +14 -14
  101. package/src/admin-ui/context.tsx +5 -2
  102. package/src/admin-ui/utils/useCreateItem.ts +21 -1
  103. package/src/fields/types/bigInt/views/index.tsx +10 -1
  104. package/src/fields/types/bytes/views/index.tsx +14 -1
  105. package/src/fields/types/calendarDay/views/index.tsx +2 -1
  106. package/src/fields/types/decimal/views/index.tsx +7 -1
  107. package/src/fields/types/file/views/Field.tsx +1 -1
  108. package/src/fields/types/float/views/index.tsx +7 -1
  109. package/src/fields/types/image/views/index.tsx +1 -1
  110. package/src/fields/types/integer/views/index.tsx +5 -0
  111. package/src/fields/types/json/views/index.tsx +20 -2
  112. package/src/fields/types/multiselect/views/index.tsx +7 -3
  113. package/src/fields/types/password/views/index.tsx +1 -1
  114. package/src/fields/types/relationship/views/index.tsx +1 -0
  115. package/src/fields/types/select/views/index.tsx +2 -1
  116. package/src/fields/types/text/views/index.tsx +14 -1
  117. package/src/fields/types/timestamp/views/__tests__/index.tsx +68 -68
  118. package/src/fields/types/timestamp/views/index.tsx +2 -1
  119. package/src/fields/types/virtual/views/index.tsx +17 -2
  120. package/src/internal-unstable/admin-ui/pages/HomePage/index.tsx +40 -31
  121. package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +36 -3
  122. package/src/internal-unstable/admin-ui/pages/ListPage/PaginationControls.tsx +20 -33
  123. package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +24 -16
  124. package/tests/conditional-filters.test.ts +333 -326
  125. package/dist/GraphQLErrorNotice-cd74180d.cjs.js +0 -57
  126. package/dist/GraphQLErrorNotice-d9f0931b.esm.js +0 -55
  127. package/dist/pick-5fe45878.cjs.js +0 -71
  128. package/dist/pick-b7ef3115.esm.js +0 -68
@@ -1,8 +1,10 @@
1
1
  import { useState } from 'react'
2
2
 
3
3
  import { TextField } from '@keystar/ui/text-field'
4
+ import { Text } from '@keystar/ui/typography'
4
5
 
5
6
  import {
7
+ type CellComponent,
6
8
  type FieldController,
7
9
  type FieldControllerConfig,
8
10
  type FieldProps,
@@ -205,7 +207,7 @@ export function Field({
205
207
  errorMessage={(forceValidation || isDirty) && validate(value)}
206
208
  isReadOnly={isReadOnly}
207
209
  isRequired={isRequired}
208
- inputMode="numeric"
210
+ inputMode="decimal"
209
211
  width="alias.singleLineWidth"
210
212
  onBlur={() => setDirty(true)}
211
213
  onChange={x => onChange?.({ ...value, value: x === '' ? null : x })}
@@ -213,3 +215,7 @@ export function Field({
213
215
  />
214
216
  )
215
217
  }
218
+
219
+ export const Cell: CellComponent<typeof controller> = ({ value }) => {
220
+ return value != null && value !== '' ? <Text>{value}</Text> : null
221
+ }
@@ -164,7 +164,7 @@ function FileDetails(props: PropsWithChildren<FileData>) {
164
164
  gap="medium"
165
165
  padding="medium"
166
166
  >
167
- <a href={props.href} download={props.name}>
167
+ <a href={props.href} download={props.name} aria-label={`Download ${props.name}`}>
168
168
  <HStack
169
169
  alignItems="center"
170
170
  backgroundColor="surfaceTertiary"
@@ -1,8 +1,10 @@
1
1
  import { useState } from 'react'
2
2
 
3
3
  import { TextField } from '@keystar/ui/text-field'
4
+ import { Text } from '@keystar/ui/typography'
4
5
 
5
6
  import type {
7
+ CellComponent,
6
8
  FieldController,
7
9
  FieldControllerConfig,
8
10
  FieldProps,
@@ -205,7 +207,7 @@ export function Field({
205
207
  errorMessage={(forceValidation || isDirty) && validate(value)}
206
208
  isReadOnly={isReadOnly}
207
209
  isRequired={isRequired}
208
- inputMode="numeric"
210
+ inputMode="decimal"
209
211
  width="alias.singleLineWidth"
210
212
  onBlur={() => setDirty(true)}
211
213
  onChange={x => onChange?.({ ...value, value: x === '' ? null : x })}
@@ -213,3 +215,7 @@ export function Field({
213
215
  />
214
216
  )
215
217
  }
218
+
219
+ export const Cell: CellComponent<typeof controller> = ({ value }) => {
220
+ return value != null && value !== '' ? <Text>{value}</Text> : null
221
+ }
@@ -15,7 +15,7 @@ export const Cell: CellComponent<typeof controller> = ({ value }) => {
15
15
  width: 24,
16
16
  }}
17
17
  >
18
- <img style={{ maxHeight: '100%', maxWidth: '100%' }} src={value.url} />
18
+ <img alt="" style={{ maxHeight: '100%', maxWidth: '100%' }} src={value.url} />
19
19
  </div>
20
20
  )
21
21
  }
@@ -7,6 +7,7 @@ import { NumberField } from '@keystar/ui/number-field'
7
7
  import { Heading, Text } from '@keystar/ui/typography'
8
8
 
9
9
  import type {
10
+ CellComponent,
10
11
  FieldController,
11
12
  FieldControllerConfig,
12
13
  FieldProps,
@@ -253,3 +254,7 @@ export function Field({
253
254
  />
254
255
  )
255
256
  }
257
+
258
+ export const Cell: CellComponent<typeof controller> = ({ value }) => {
259
+ return typeof value === 'number' && Number.isFinite(value) ? <Text>{value}</Text> : null
260
+ }
@@ -1,3 +1,5 @@
1
+ import { useState } from 'react'
2
+
1
3
  import { css, tokenSchema } from '@keystar/ui/style'
2
4
  import { TextArea } from '@keystar/ui/text-field'
3
5
  import { Text } from '@keystar/ui/typography'
@@ -12,7 +14,9 @@ import type {
12
14
 
13
15
  export const Field = (props: FieldProps<typeof controller>) => {
14
16
  const { autoFocus, field, forceValidation, onChange, value } = props
15
- const errorMessage = forceValidation ? 'Invalid JSON' : undefined
17
+ const [isDirty, setDirty] = useState(false)
18
+ const errorMessage =
19
+ (isDirty || forceValidation) && !isValidJSON(value) ? 'Invalid JSON' : undefined
16
20
 
17
21
  return (
18
22
  <TextArea
@@ -22,6 +26,7 @@ export const Field = (props: FieldProps<typeof controller>) => {
22
26
  isReadOnly={onChange === undefined}
23
27
  label={field.label}
24
28
  onChange={onChange}
29
+ onBlur={() => setDirty(true)}
25
30
  value={value}
26
31
  UNSAFE_className={css({
27
32
  textarea: {
@@ -33,8 +38,21 @@ export const Field = (props: FieldProps<typeof controller>) => {
33
38
  )
34
39
  }
35
40
 
41
+ function isValidJSON(value: string) {
42
+ if (!value) return true
43
+ try {
44
+ JSON.parse(value)
45
+ return true
46
+ } catch {
47
+ return false
48
+ }
49
+ }
50
+
36
51
  export const Cell: CellComponent<typeof controller> = ({ value }) => {
37
- return value ? <Text>{JSON.stringify(value)}</Text> : null
52
+ if (value == null) return null
53
+ const stringified = JSON.stringify(value)
54
+ const truncated = stringified.length > 100 ? `${stringified.slice(0, 100)}…` : stringified
55
+ return <Text>{truncated}</Text>
38
56
  }
39
57
 
40
58
  type Config = FieldControllerConfig<{ defaultValue: JSONValue }>
@@ -89,7 +89,7 @@ function CheckboxesModeField(props: FieldProps<typeof controller>) {
89
89
 
90
90
  export const Cell: CellComponent<typeof controller> = ({ value = [], field }) => {
91
91
  const listFormatter = useListFormatter({ style: 'short', type: 'conjunction' })
92
- const labels = (value as string[]).map(x => field.valuesToOptionsWithStringValues[x].label)
92
+ const labels = (value as string[]).map(x => field.valuesToOptionsWithStringValues[x]?.label ?? x)
93
93
 
94
94
  const cellContent =
95
95
  value.length > 3
@@ -135,12 +135,16 @@ export function controller(config: Config): FieldController<Value, Option[]> & {
135
135
  type: config.fieldMeta.type,
136
136
  options: optionsWithStringValues,
137
137
  valuesToOptionsWithStringValues,
138
- defaultValue: config.fieldMeta.defaultValue.map(x => valuesToOptionsWithStringValues[x]),
138
+ defaultValue: config.fieldMeta.defaultValue.map(
139
+ x => valuesToOptionsWithStringValues[x] ?? { label: x.toString(), value: x.toString() }
140
+ ),
139
141
  deserialize: data => {
140
142
  // if we get null from the GraphQL API (which will only happen if field read access control failed)
141
143
  // we'll just show it as nothing being selected for now.
142
144
  const values: readonly string[] | readonly number[] = data[config.fieldKey] ?? []
143
- const selectedOptions = values.map(x => valuesToOptionsWithStringValues[x])
145
+ const selectedOptions = values.map(
146
+ x => valuesToOptionsWithStringValues[x] ?? { label: x.toString(), value: x.toString() }
147
+ )
144
148
  return selectedOptions
145
149
  },
146
150
  serialize: value => ({ [config.fieldKey]: value.map(x => parseValue(x.value)) }),
@@ -181,7 +181,7 @@ export function Field(props: FieldProps<typeof controller>) {
181
181
 
182
182
  <Flex gap="regular">
183
183
  <ToggleButton
184
- aria-label="show"
184
+ aria-label={secureTextEntry ? 'Show password' : 'Hide password'}
185
185
  isSelected={!secureTextEntry}
186
186
  onPress={() => setSecureTextEntry(bool => !bool)}
187
187
  >
@@ -58,6 +58,7 @@ export function Field(props: FieldProps<typeof controller>) {
58
58
  <HStack gap="small" alignItems="end">
59
59
  {textField}
60
60
  <ActionButton
61
+ aria-label={`Go to ${field.label}`}
61
62
  href={`/${foreignList.path}?${buildQueryForRelationshipFieldWithForeignField(foreignList, field.refFieldKey, value.id)}`}
62
63
  >
63
64
  <Icon src={arrowUpRightIcon} />
@@ -148,8 +148,9 @@ export function Field(props: FieldProps<typeof controller>) {
148
148
  }
149
149
 
150
150
  export const Cell: CellComponent<typeof controller> = ({ value, field }) => {
151
+ if (value == null) return null
151
152
  const label = field.options.find(x => x.value === value)?.label
152
- return <Text>{label}</Text>
153
+ return <Text>{label ?? String(value)}</Text>
153
154
  }
154
155
 
155
156
  export type AdminSelectFieldMeta = {
@@ -1,10 +1,12 @@
1
1
  import { useState } from 'react'
2
2
  import { TextArea, TextField } from '@keystar/ui/text-field'
3
+ import { Text } from '@keystar/ui/typography'
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,
@@ -46,7 +48,14 @@ export function Field(props: FieldProps<typeof controller>) {
46
48
  label={field.label}
47
49
  errorMessage={
48
50
  !!validationMessages.length && (shouldShowErrors || forceValidation)
49
- ? validationMessages.join('. ')
51
+ ? validationMessages.length === 1
52
+ ? validationMessages[0]
53
+ : validationMessages.map((message, i) => (
54
+ <Text key={i}>
55
+ {i > 0 && <br />}
56
+ {message}
57
+ </Text>
58
+ ))
50
59
  : undefined
51
60
  }
52
61
  isDisabled={isNull}
@@ -73,6 +82,10 @@ export function Field(props: FieldProps<typeof controller>) {
73
82
  )
74
83
  }
75
84
 
85
+ export const Cell: CellComponent<typeof controller> = ({ value }) => {
86
+ return value ? <Text truncate>{value}</Text> : null
87
+ }
88
+
76
89
  type Config = FieldControllerConfig<TextFieldMeta>
77
90
 
78
91
  type Validation = {
@@ -1,68 +1,68 @@
1
- import { controller } from '../index'
2
-
3
- const STUBCONFIG = {
4
- listKey: 'timestamp',
5
- fieldKey: 'timestamp',
6
- label: 'foo',
7
- description: '',
8
- customViews: {},
9
- } as const
10
-
11
- describe('controller', () => {
12
- describe('validate', () => {
13
- it('null is OK if not required', () => {
14
- const { validate } = controller({
15
- ...STUBCONFIG,
16
- fieldMeta: {
17
- defaultValue: null,
18
- updatedAt: false,
19
- },
20
- })
21
- expect(
22
- validate!(
23
- {
24
- kind: 'create',
25
- value: null,
26
- },
27
- { isRequired: false }
28
- )
29
- ).toBe(true)
30
- })
31
- it('isRequired enforces required (null)', () => {
32
- const { validate } = controller({
33
- ...STUBCONFIG,
34
- fieldMeta: {
35
- defaultValue: null,
36
- updatedAt: false,
37
- },
38
- })
39
- expect(
40
- validate!(
41
- {
42
- kind: 'create',
43
- value: null,
44
- },
45
- { isRequired: true }
46
- )
47
- ).toBe('foo is required')
48
- })
49
- it('isRequired enforces required (value)', () => {
50
- const { validate } = controller({
51
- ...STUBCONFIG,
52
- fieldMeta: {
53
- defaultValue: null,
54
- updatedAt: false,
55
- },
56
- })
57
- expect(
58
- validate!(
59
- {
60
- kind: 'create',
61
- value: new Date().toJSON(),
62
- },
63
- { isRequired: true }
64
- )
65
- ).toBe(true)
66
- })
67
- })
68
- })
1
+ import { controller } from '../index'
2
+
3
+ const STUBCONFIG = {
4
+ listKey: 'timestamp',
5
+ fieldKey: 'timestamp',
6
+ label: 'foo',
7
+ description: '',
8
+ customViews: {},
9
+ } as const
10
+
11
+ describe('controller', () => {
12
+ describe('validate', () => {
13
+ it('null is OK if not required', () => {
14
+ const { validate } = controller({
15
+ ...STUBCONFIG,
16
+ fieldMeta: {
17
+ defaultValue: null,
18
+ updatedAt: false,
19
+ },
20
+ })
21
+ expect(
22
+ validate!(
23
+ {
24
+ kind: 'create',
25
+ value: null,
26
+ },
27
+ { isRequired: false }
28
+ )
29
+ ).toBe(true)
30
+ })
31
+ it('isRequired enforces required (null)', () => {
32
+ const { validate } = controller({
33
+ ...STUBCONFIG,
34
+ fieldMeta: {
35
+ defaultValue: null,
36
+ updatedAt: false,
37
+ },
38
+ })
39
+ expect(
40
+ validate!(
41
+ {
42
+ kind: 'create',
43
+ value: null,
44
+ },
45
+ { isRequired: true }
46
+ )
47
+ ).toBe(false)
48
+ })
49
+ it('isRequired enforces required (value)', () => {
50
+ const { validate } = controller({
51
+ ...STUBCONFIG,
52
+ fieldMeta: {
53
+ defaultValue: null,
54
+ updatedAt: false,
55
+ },
56
+ })
57
+ expect(
58
+ validate!(
59
+ {
60
+ kind: 'create',
61
+ value: new Date().toJSON(),
62
+ },
63
+ { isRequired: true }
64
+ )
65
+ ).toBe(true)
66
+ })
67
+ })
68
+ })
@@ -44,12 +44,13 @@ export function Field(props: FieldProps<typeof controller>) {
44
44
  description={field.description}
45
45
  isDisabled={!parsedValue}
46
46
  isReadOnly
47
+ placeholder="yyyy-mm-dd --:--:--"
47
48
  value={
48
49
  parsedValue
49
50
  ? isReadonlyUTC
50
51
  ? parsedValue.toAbsoluteString()
51
52
  : dateFormatter.format(parsedValue.toDate())
52
- : 'yyyy-mm-dd --:--:--'
53
+ : ''
53
54
  }
54
55
  />
55
56
  {!!parsedValue && (
@@ -1,4 +1,4 @@
1
- import { TextField } from '@keystar/ui/text-field'
1
+ import { TextArea, TextField } from '@keystar/ui/text-field'
2
2
  import { Text } from '@keystar/ui/typography'
3
3
  import type {
4
4
  CellComponent,
@@ -21,13 +21,28 @@ export function Field(props: FieldProps<typeof controller>) {
21
21
  const { autoFocus, field, value } = props
22
22
  if (value === createViewValue) return null
23
23
 
24
+ const stringified = stringify(value)
25
+ // multi-line values (e.g. pretty-printed JSON) get clipped in a single-line
26
+ // TextField, so render them in a read-only TextArea instead
27
+ if (stringified.includes('\n')) {
28
+ return (
29
+ <TextArea
30
+ autoFocus={autoFocus}
31
+ description={field.description}
32
+ label={field.label}
33
+ isReadOnly={true}
34
+ value={stringified}
35
+ />
36
+ )
37
+ }
38
+
24
39
  return (
25
40
  <TextField
26
41
  autoFocus={autoFocus}
27
42
  description={field.description}
28
43
  label={field.label}
29
44
  isReadOnly={true}
30
- value={stringify(value)}
45
+ value={stringified}
31
46
  />
32
47
  )
33
48
  }
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from 'react'
2
2
 
3
- import { css } from '@keystar/ui/style'
3
+ import { css, tokenSchema } from '@keystar/ui/style'
4
4
  import { Heading } from '@keystar/ui/typography'
5
5
 
6
6
  import { gql, type TypedDocumentNode, useQuery } from '../../../../admin-ui/apollo'
@@ -24,14 +24,14 @@ function CollectionCard({ listKey, count, href, createHref }: CardProps) {
24
24
  <div
25
25
  className={css({
26
26
  position: 'relative',
27
- backgroundColor: '#ffffff',
28
- border: '1px solid #ebebeb',
27
+ backgroundColor: tokenSchema.color.background.canvas,
28
+ border: `1px solid ${tokenSchema.color.border.muted}`,
29
29
  borderRadius: 8,
30
30
  overflow: 'hidden',
31
31
  transition: 'box-shadow 180ms, border-color 180ms',
32
32
  '&:hover': {
33
- boxShadow: '0 2px 12px rgba(0,0,0,0.06)',
34
- borderColor: '#d8d8d8',
33
+ boxShadow: `0 2px 12px ${tokenSchema.color.shadow.muted}`,
34
+ borderColor: tokenSchema.color.border.emphasis,
35
35
  },
36
36
  })}
37
37
  >
@@ -44,7 +44,7 @@ function CollectionCard({ listKey, count, href, createHref }: CardProps) {
44
44
  fontWeight: 600,
45
45
  letterSpacing: '0.09em',
46
46
  textTransform: 'uppercase',
47
- color: '#a3a3a3',
47
+ color: tokenSchema.color.foreground.neutralSecondary,
48
48
  display: 'flex',
49
49
  alignItems: 'center',
50
50
  justifyContent: 'space-between',
@@ -69,11 +69,11 @@ function CollectionCard({ listKey, count, href, createHref }: CardProps) {
69
69
  display: 'inline-flex',
70
70
  alignItems: 'center',
71
71
  justifyContent: 'center',
72
- width: 22,
73
- height: 22,
72
+ width: 28,
73
+ height: 28,
74
74
  borderRadius: 5,
75
- border: '1px solid #ebebeb',
76
- color: '#a3a3a3',
75
+ border: `1px solid ${tokenSchema.color.border.muted}`,
76
+ color: tokenSchema.color.foreground.neutralSecondary,
77
77
  textDecoration: 'none',
78
78
  fontSize: 14,
79
79
  lineHeight: 1,
@@ -81,9 +81,9 @@ function CollectionCard({ listKey, count, href, createHref }: CardProps) {
81
81
  marginLeft: 6,
82
82
  transition: 'border-color 130ms, color 130ms, background 130ms',
83
83
  '&:hover': {
84
- borderColor: '#000000',
85
- color: '#000000',
86
- background: '#f5f5f5',
84
+ borderColor: tokenSchema.color.scale.black,
85
+ color: tokenSchema.color.foreground.neutralEmphasis,
86
+ background: tokenSchema.color.background.surfaceSecondary,
87
87
  },
88
88
  })}
89
89
  >
@@ -111,7 +111,10 @@ function CollectionCard({ listKey, count, href, createHref }: CardProps) {
111
111
  fontSize: 34,
112
112
  fontWeight: 700,
113
113
  letterSpacing: '-0.05em',
114
- color: count === null ? '#e8e8e8' : '#000000',
114
+ color:
115
+ count === null
116
+ ? tokenSchema.color.border.muted
117
+ : tokenSchema.color.foreground.neutralEmphasis,
115
118
  lineHeight: 1,
116
119
  fontVariantNumeric: 'tabular-nums',
117
120
  })}
@@ -123,12 +126,12 @@ function CollectionCard({ listKey, count, href, createHref }: CardProps) {
123
126
  className={css({
124
127
  margin: 0,
125
128
  fontSize: 12,
126
- color: '#c8c8c8',
129
+ color: tokenSchema.color.foreground.neutralSecondary,
127
130
  display: 'flex',
128
131
  alignItems: 'center',
129
132
  gap: 4,
130
133
  transition: 'color 130ms',
131
- 'a:hover &': { color: '#636363' },
134
+ 'a:hover &': { color: tokenSchema.color.foreground.neutralEmphasis },
132
135
  })}
133
136
  >
134
137
  {list.isSingleton ? 'Open' : 'View all'}
@@ -167,7 +170,7 @@ function QuickCreate() {
167
170
  fontWeight: 600,
168
171
  letterSpacing: '0.10em',
169
172
  textTransform: 'uppercase',
170
- color: '#b8b8b8',
173
+ color: tokenSchema.color.foreground.neutralSecondary,
171
174
  })}
172
175
  >
173
176
  Quick Create
@@ -184,18 +187,18 @@ function QuickCreate() {
184
187
  paddingInline: '12px',
185
188
  paddingBlock: '6px',
186
189
  borderRadius: 6,
187
- border: '1px solid #e8e8e8',
188
- backgroundColor: '#ffffff',
190
+ border: `1px solid ${tokenSchema.color.border.muted}`,
191
+ backgroundColor: tokenSchema.color.background.canvas,
189
192
  fontSize: 12.5,
190
193
  fontWeight: 500,
191
- color: '#525252',
194
+ color: tokenSchema.color.foreground.neutral,
192
195
  textDecoration: 'none',
193
196
  transition: 'border-color 130ms, color 130ms, background 130ms',
194
197
 
195
198
  '&:hover': {
196
- borderColor: '#000000',
197
- color: '#000000',
198
- background: '#fafafa',
199
+ borderColor: tokenSchema.color.scale.black,
200
+ color: tokenSchema.color.foreground.neutralEmphasis,
201
+ background: tokenSchema.color.background.surface,
199
202
  },
200
203
  })}
201
204
  >
@@ -260,18 +263,24 @@ export function HomePage() {
260
263
 
261
264
  {/* Page title */}
262
265
  <div className={css({ marginBottom: 28 })}>
263
- <h1
266
+ <h2
264
267
  className={css({
265
268
  margin: '0 0 4px',
266
269
  fontSize: 20,
267
270
  fontWeight: 700,
268
271
  letterSpacing: '-0.03em',
269
- color: '#0a0a0a',
272
+ color: tokenSchema.color.foreground.neutralEmphasis,
270
273
  })}
271
274
  >
272
275
  Overview
273
- </h1>
274
- <p className={css({ margin: 0, fontSize: 13, color: '#a3a3a3' })}>
276
+ </h2>
277
+ <p
278
+ className={css({
279
+ margin: 0,
280
+ fontSize: 13,
281
+ color: tokenSchema.color.foreground.neutralSecondary,
282
+ })}
283
+ >
275
284
  {visibleLists.length} collection{visibleLists.length !== 1 ? 's' : ''}
276
285
  </p>
277
286
  </div>
@@ -305,7 +314,7 @@ export function HomePage() {
305
314
  className={css({
306
315
  marginTop: 48,
307
316
  fontSize: 11.5,
308
- color: '#d4d4d4',
317
+ color: tokenSchema.color.foreground.neutralSecondary,
309
318
  display: 'flex',
310
319
  alignItems: 'center',
311
320
  gap: 6,
@@ -315,12 +324,12 @@ export function HomePage() {
315
324
  className={css({
316
325
  display: 'inline-flex',
317
326
  padding: '1px 6px',
318
- background: '#ffffff',
319
- border: '1px solid #ebebeb',
327
+ background: tokenSchema.color.background.canvas,
328
+ border: `1px solid ${tokenSchema.color.border.muted}`,
320
329
  borderRadius: 4,
321
330
  fontSize: 10.5,
322
331
  fontFamily: 'inherit',
323
- color: '#a3a3a3',
332
+ color: tokenSchema.color.foreground.neutralSecondary,
324
333
  })}
325
334
  >
326
335
  ⌘K