@nixxie-cms/core 1.0.0 → 1.0.2

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 (187) hide show
  1. package/README.md +2 -2
  2. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.cjs.js +4 -4
  3. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.esm.js +4 -4
  4. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.cjs.js +2 -2
  5. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.esm.js +2 -2
  6. package/context/dist/nixxie-cms-core-context.cjs.js +2 -2
  7. package/context/dist/nixxie-cms-core-context.esm.js +2 -2
  8. package/dist/{CreateItemDialog-33335548.esm.js → CreateItemDialog-7008b050.esm.js} +1 -1
  9. package/dist/{CreateItemDialog-56cf59b7.cjs.js → CreateItemDialog-a0cab315.cjs.js} +1 -1
  10. package/dist/{PageContainer-7db73317.esm.js → PageContainer-5ae731cc.esm.js} +25 -18
  11. package/dist/{PageContainer-27c27f10.cjs.js → PageContainer-abd7159f.cjs.js} +25 -18
  12. package/dist/{admin-meta-graphql-6f7f5331.esm.js → admin-meta-graphql-0e6e606e.esm.js} +1 -1
  13. package/dist/{admin-meta-graphql-c8f926e9.cjs.js → admin-meta-graphql-306c224a.cjs.js} +1 -1
  14. package/dist/{context-3132c3ed.esm.js → context-af9957ed.esm.js} +2 -2
  15. package/dist/{context-e7a45152.cjs.js → context-b5204629.cjs.js} +2 -2
  16. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  17. package/dist/declarations/src/admin-ui/components/PageContainer.d.ts.map +1 -1
  18. package/dist/declarations/src/helpers.d.ts.map +1 -1
  19. package/dist/declarations/src/index.d.ts +1 -0
  20. package/dist/declarations/src/index.d.ts.map +1 -1
  21. package/dist/declarations/src/internal-unstable/admin-ui/id-field-view.d.ts.map +1 -0
  22. package/dist/declarations/src/internal-unstable/admin-ui/pages/App/index.d.ts.map +1 -0
  23. package/dist/declarations/src/internal-unstable/admin-ui/pages/CreateItemPage/index.d.ts.map +1 -0
  24. package/dist/declarations/src/internal-unstable/admin-ui/pages/HomePage/index.d.ts.map +1 -0
  25. package/dist/declarations/src/internal-unstable/admin-ui/pages/ItemPage/index.d.ts.map +1 -0
  26. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -0
  27. package/dist/declarations/src/internal-unstable/admin-ui/pages/NoAccessPage/index.d.ts.map +1 -0
  28. package/dist/declarations/src/internal-unstable/artifacts.d.ts.map +1 -0
  29. package/dist/declarations/src/lib/core/initialise-lists.d.ts +1 -1
  30. package/dist/declarations/src/schema.d.ts.map +1 -1
  31. package/dist/declarations/src/types/config/index.d.ts +60 -1
  32. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  33. package/dist/declarations/src/types/config/lists.d.ts +4 -4
  34. package/dist/declarations/src/types/context.d.ts +150 -0
  35. package/dist/declarations/src/types/context.d.ts.map +1 -1
  36. package/dist/declarations/src/types/next-fields.d.ts +1 -1
  37. package/dist/{express-e9ed9a7d.cjs.js → express-455ae20c.cjs.js} +1 -1
  38. package/dist/{express-6743b918.esm.js → express-7559ca2d.esm.js} +1 -1
  39. package/dist/{index-ac01583b.cjs.js → index-89635494.cjs.js} +4 -4
  40. package/dist/{index-24b78415.esm.js → index-baa799e0.esm.js} +4 -4
  41. package/dist/nixxie-cms-core.cjs.js +104 -77
  42. package/dist/nixxie-cms-core.esm.js +104 -77
  43. package/dist/{non-null-graphql-5315718c.esm.js → non-null-graphql-a84ed64d.esm.js} +1 -1
  44. package/dist/{non-null-graphql-17b83ddc.cjs.js → non-null-graphql-add6bb3d.cjs.js} +1 -1
  45. package/dist/{resolve-hooks-66fe8a8e.cjs.js → resolve-hooks-165a9ce2.cjs.js} +1 -1
  46. package/dist/{resolve-hooks-17aafd37.esm.js → resolve-hooks-6813a045.esm.js} +2 -2
  47. package/dist/{system-dfec2f0a.esm.js → system-03e49e4f.esm.js} +8 -4
  48. package/dist/{system-48c5f6df.cjs.js → system-a321642d.cjs.js} +8 -4
  49. package/dist/{useFilter-0b5a1ee6.esm.js → useFilter-9b6db1f9.esm.js} +1 -1
  50. package/dist/{useFilter-1a4e6900.cjs.js → useFilter-acc9d413.cjs.js} +1 -1
  51. package/fields/dist/nixxie-cms-core-fields.cjs.js +16 -16
  52. package/fields/dist/nixxie-cms-core-fields.esm.js +17 -17
  53. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +3 -3
  54. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +3 -3
  55. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.cjs.js +1 -1
  56. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.esm.js +1 -1
  57. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +3 -3
  58. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +3 -3
  59. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.cjs.js +4 -4
  60. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.esm.js +4 -4
  61. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.cjs.js +1 -1
  62. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.esm.js +1 -1
  63. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.cjs.js +1 -1
  64. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.esm.js +1 -1
  65. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.d.ts +2 -0
  66. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.js +244 -0
  67. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.esm.js +235 -0
  68. package/internal-unstable/admin-ui/id-field-view/package.json +4 -0
  69. package/internal-unstable/admin-ui/next-config/package.json +4 -0
  70. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.d.ts +2 -0
  71. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.js +59 -0
  72. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.esm.js +55 -0
  73. package/internal-unstable/admin-ui/pages/App/package.json +4 -0
  74. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.d.ts +2 -0
  75. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.js +116 -0
  76. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.esm.js +112 -0
  77. package/internal-unstable/admin-ui/pages/CreateItemPage/package.json +4 -0
  78. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.d.ts +2 -0
  79. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.js +336 -0
  80. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.esm.js +332 -0
  81. package/internal-unstable/admin-ui/pages/HomePage/package.json +4 -0
  82. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.d.ts +2 -0
  83. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.js +463 -0
  84. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.esm.js +455 -0
  85. package/internal-unstable/admin-ui/pages/ItemPage/package.json +4 -0
  86. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.d.ts +2 -0
  87. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.js +1195 -0
  88. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.esm.js +1187 -0
  89. package/internal-unstable/admin-ui/pages/ListPage/package.json +4 -0
  90. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.d.ts +2 -0
  91. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.js +40 -0
  92. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.esm.js +35 -0
  93. package/internal-unstable/admin-ui/pages/NoAccessPage/package.json +4 -0
  94. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.d.ts +2 -0
  95. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +51 -0
  96. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +38 -0
  97. package/internal-unstable/artifacts/package.json +4 -0
  98. package/package.json +44 -44
  99. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +44 -15
  100. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +44 -15
  101. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +3 -3
  102. package/scripts/dist/nixxie-cms-core-scripts.esm.js +3 -3
  103. package/src/admin-ui/admin-meta-graphql.ts +168 -168
  104. package/src/admin-ui/components/CommandPalette.tsx +433 -431
  105. package/src/admin-ui/components/Navigation.tsx +389 -385
  106. package/src/admin-ui/components/PageContainer.tsx +311 -310
  107. package/src/admin-ui/components/WelcomeDialog.tsx +1 -1
  108. package/src/admin-ui/context.tsx +338 -338
  109. package/src/admin-ui/templates/app.ts +60 -60
  110. package/src/admin-ui/templates/create-item.ts +5 -5
  111. package/src/admin-ui/templates/home.ts +2 -2
  112. package/src/admin-ui/templates/item.tsx +5 -5
  113. package/src/admin-ui/templates/list.tsx +5 -5
  114. package/src/admin-ui/templates/next-config.ts +29 -0
  115. package/src/admin-ui/templates/no-access.ts +7 -7
  116. package/src/fields/types/bigInt/index.ts +181 -181
  117. package/src/fields/types/bytes/index.ts +275 -275
  118. package/src/fields/types/calendarDay/index.ts +194 -194
  119. package/src/fields/types/checkbox/index.ts +76 -76
  120. package/src/fields/types/decimal/index.ts +182 -182
  121. package/src/fields/types/file/index.ts +168 -168
  122. package/src/fields/types/float/index.ts +133 -133
  123. package/src/fields/types/image/index.ts +244 -244
  124. package/src/fields/types/integer/index.ts +156 -156
  125. package/src/fields/types/json/index.ts +77 -77
  126. package/src/fields/types/multiselect/index.ts +212 -212
  127. package/src/fields/types/password/index.ts +241 -241
  128. package/src/fields/types/relationship/index.ts +381 -381
  129. package/src/fields/types/relationship/views/RelationshipTable.tsx +190 -190
  130. package/src/fields/types/select/index.ts +226 -226
  131. package/src/fields/types/text/index.ts +207 -207
  132. package/src/fields/types/timestamp/index.ts +116 -116
  133. package/src/fields/types/virtual/index.ts +108 -108
  134. package/src/helpers.ts +342 -316
  135. package/src/index.ts +4 -0
  136. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.tsx +167 -167
  137. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.tsx +22 -22
  138. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.tsx +71 -71
  139. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.tsx +333 -333
  140. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/common.tsx +358 -358
  141. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.tsx +483 -483
  142. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/FilterAdd.tsx +221 -221
  143. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/PaginationControls.tsx +170 -170
  144. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/Tag.tsx +72 -72
  145. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.tsx +1006 -1006
  146. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.tsx +24 -24
  147. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.ts +5 -5
  148. package/src/lib/context/createContext.ts +165 -161
  149. package/src/lib/core/initialise-lists.ts +1097 -1097
  150. package/src/lib/id-field.ts +214 -214
  151. package/src/lib/telemetry.ts +342 -342
  152. package/src/schema.ts +237 -233
  153. package/src/scripts/telemetry.ts +1 -1
  154. package/src/types/config/index.ts +400 -333
  155. package/src/types/config/lists.ts +4 -4
  156. package/src/types/context.ts +700 -530
  157. package/src/types/next-fields.ts +499 -499
  158. package/src/types/telemetry.ts +51 -51
  159. package/tests/telemetry.test.ts +361 -361
  160. package/CHANGELOG.md +0 -3158
  161. package/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view/package.json +0 -4
  162. package/___internal-do-not-use-will-break-in-patch/admin-ui/next-config/package.json +0 -4
  163. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/package.json +0 -4
  164. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/package.json +0 -4
  165. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/package.json +0 -4
  166. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/package.json +0 -4
  167. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/package.json +0 -4
  168. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/package.json +0 -4
  169. package/___internal-do-not-use-will-break-in-patch/artifacts/package.json +0 -4
  170. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.d.ts.map +0 -1
  171. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/index.d.ts.map +0 -1
  172. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/index.d.ts.map +0 -1
  173. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/index.d.ts.map +0 -1
  174. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.d.ts.map +0 -1
  175. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.d.ts.map +0 -1
  176. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/index.d.ts.map +0 -1
  177. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/artifacts.d.ts.map +0 -1
  178. /package/dist/{common-1a350e11.cjs.js → common-5933f758.cjs.js} +0 -0
  179. /package/dist/{common-29fc82e6.esm.js → common-ea5c441a.esm.js} +0 -0
  180. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.d.ts +0 -0
  181. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.d.ts +0 -0
  182. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.d.ts +0 -0
  183. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.d.ts +0 -0
  184. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.d.ts +0 -0
  185. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.d.ts +0 -0
  186. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.d.ts +0 -0
  187. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.d.ts +0 -0
package/src/index.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export { list, config, group, action } from './schema'
2
2
  export type { ListConfig, BaseFields } from './types'
3
+ // Re-export the runtime-service & context types (NixxieContext, NixxieStorageService,
4
+ // NixxieSearchService, NixxieEmail*, NixxieCacheService, etc.) so the first-party service packages
5
+ // (@nixxie-cms/storage, /search, /email, …) can import them from the package root.
6
+ export type * from './types/context'
3
7
  export {
4
8
  defineList,
5
9
  defineConfig,
@@ -1,167 +1,167 @@
1
- import { useCallback, useState } from 'react'
2
- import { useListFormatter } from '@react-aria/i18n'
3
- import copyToClipboard from 'clipboard-copy'
4
-
5
- import { ActionButton } from '@keystar/ui/button'
6
- import { Icon } from '@keystar/ui/icon'
7
- import { clipboardIcon } from '@keystar/ui/icon/icons/clipboardIcon'
8
- import { Grid } from '@keystar/ui/layout'
9
- import { TooltipTrigger, Tooltip } from '@keystar/ui/tooltip'
10
- import { TextField } from '@keystar/ui/text-field'
11
-
12
- import type { FieldController, FieldControllerConfig, FieldProps, IdFieldConfig } from '../../types'
13
- import type { InferValueFromInputType } from '@graphql-ts/schema'
14
- import type { filters } from '../../fields/filters'
15
- import { entriesTyped } from '../../lib/core/utils'
16
-
17
- const COPY_TOOLTIP_CONTENT = {
18
- neutral: 'Copy ID',
19
- positive: 'Copied to clipboard',
20
- critical: 'Unable to copy',
21
- }
22
- type TooltipState = { isOpen?: boolean; tone: keyof typeof COPY_TOOLTIP_CONTENT }
23
-
24
- export function Field({ value }: FieldProps<typeof controller>) {
25
- const [tooltipState, setTooltipState] = useState<TooltipState>({ tone: 'neutral' })
26
-
27
- const onCopy = useCallback(async () => {
28
- try {
29
- if (value) await copyToClipboard(value)
30
- setTooltipState({ isOpen: true, tone: 'positive' })
31
- } catch (err: any) {
32
- setTooltipState({ isOpen: true, tone: 'critical' })
33
- }
34
-
35
- // close, then reset the tooltip state after a delay
36
- setTimeout(() => {
37
- setTooltipState(state => ({ ...state, isOpen: false }))
38
- }, 2000)
39
- setTimeout(() => {
40
- setTooltipState({ isOpen: undefined, tone: 'neutral' })
41
- }, 2300)
42
- }, [value])
43
-
44
- return (
45
- <Grid gap="regular" columns="1fr auto" alignItems="end">
46
- <TextField
47
- label="Item ID"
48
- value={value ?? ''}
49
- isReadOnly
50
- onFocus={({ target }) => {
51
- if (target instanceof HTMLInputElement) target.select()
52
- }}
53
- />
54
- <TooltipTrigger isOpen={tooltipState.isOpen} placement="top end">
55
- <ActionButton aria-label="copy id" onPress={onCopy}>
56
- <Icon src={clipboardIcon} />
57
- </ActionButton>
58
- <Tooltip tone={tooltipState.tone}>{COPY_TOOLTIP_CONTENT[tooltipState.tone]}</Tooltip>
59
- </TooltipTrigger>
60
- </Grid>
61
- )
62
- }
63
-
64
- export function controller(
65
- config: FieldControllerConfig<IdFieldConfig>
66
- ): FieldController<
67
- string | null,
68
- string | string[],
69
- InferValueFromInputType<(typeof filters)['mysql']['String']['required']>
70
- > {
71
- return {
72
- fieldKey: config.fieldKey,
73
- label: config.label,
74
- description: config.description,
75
- defaultValue: null,
76
- deserialize: data => data[config.fieldKey],
77
- serialize: value => ({ [config.fieldKey]: value }),
78
- graphqlSelection: config.fieldKey,
79
- filter: {
80
- Filter(props) {
81
- const { autoFocus, context, onChange, type, typeLabel, value, ...otherProps } = props
82
- const labelProps =
83
- context === 'add' ? { label: config.label, description: typeLabel } : { label: typeLabel }
84
- const [inputValue, setInputValue] = useState(() =>
85
- Array.isArray(value) ? value.join(', ') : value
86
- )
87
- const [lastSeenValue, setLastSeenValue] = useState(value)
88
- if (value !== lastSeenValue && Array.isArray(value)) {
89
- setInputValue(value.join(', '))
90
- setLastSeenValue(value)
91
- }
92
-
93
- return (
94
- <TextField
95
- {...otherProps}
96
- {...labelProps}
97
- autoFocus={autoFocus}
98
- onChange={newVal => {
99
- if (Array.isArray(value)) {
100
- setInputValue(newVal)
101
- } else {
102
- onChange(newVal)
103
- }
104
- }}
105
- onBlur={() => {
106
- if (Array.isArray(value)) {
107
- onChange(inputValue.split(',').map(val => val.trim()))
108
- }
109
- }}
110
- value={Array.isArray(value) ? inputValue : value}
111
- />
112
- )
113
- },
114
-
115
- Label({ label, value, type }) {
116
- const listFormatter = useListFormatter({
117
- style: 'short',
118
- type: 'disjunction',
119
- })
120
-
121
- if (['in', 'not_in'].includes(type)) {
122
- return `${label.toLowerCase()} (${listFormatter.format(value)})`
123
- }
124
- return `${label.toLowerCase()} ${value}`
125
- },
126
- graphql: ({ type, value }) => {
127
- const valueWithoutWhitespace = Array.isArray(value)
128
- ? value.map(val => val.replace(/\s/g, ''))
129
- : value.replace(/\s/g, '')
130
- if (type === 'not')
131
- return { [config.fieldKey]: { not: { equals: valueWithoutWhitespace } } }
132
- const key = type === 'is' ? 'equals' : type === 'not_in' ? 'notIn' : type
133
-
134
- return {
135
- [config.fieldKey]: {
136
- [key]: valueWithoutWhitespace,
137
- },
138
- }
139
- },
140
- parseGraphQL: value => {
141
- return entriesTyped(value).flatMap(([type, value]) => {
142
- if (!value) return []
143
- if (type === 'equals') return { type: 'is', value }
144
- if (type === 'notIn') return { type: 'not_in', value }
145
- if (type === 'in') return { type: 'in', value }
146
- if (type === 'not' && value?.equals) {
147
- return { type: 'not', value: value?.equals }
148
- }
149
- if (type === 'gt' || type === 'gte' || type === 'lt' || type === 'lte') {
150
- return { type, value }
151
- }
152
- return []
153
- })
154
- },
155
- types: {
156
- is: { label: 'Is exactly', initialValue: '' },
157
- not: { label: 'Is not exactly', initialValue: '' },
158
- gt: { label: 'Is greater than', initialValue: '' },
159
- lt: { label: 'Is less than', initialValue: '' },
160
- gte: { label: 'Is greater than or equal to', initialValue: '' },
161
- lte: { label: 'Is less than or equal to', initialValue: '' },
162
- in: { label: 'Is one of', initialValue: [] },
163
- not_in: { label: 'Is not one of', initialValue: [] },
164
- },
165
- },
166
- }
167
- }
1
+ import { useCallback, useState } from 'react'
2
+ import { useListFormatter } from '@react-aria/i18n'
3
+ import copyToClipboard from 'clipboard-copy'
4
+
5
+ import { ActionButton } from '@keystar/ui/button'
6
+ import { Icon } from '@keystar/ui/icon'
7
+ import { clipboardIcon } from '@keystar/ui/icon/icons/clipboardIcon'
8
+ import { Grid } from '@keystar/ui/layout'
9
+ import { TooltipTrigger, Tooltip } from '@keystar/ui/tooltip'
10
+ import { TextField } from '@keystar/ui/text-field'
11
+
12
+ import type { FieldController, FieldControllerConfig, FieldProps, IdFieldConfig } from '../../types'
13
+ import type { InferValueFromInputType } from '@graphql-ts/schema'
14
+ import type { filters } from '../../fields/filters'
15
+ import { entriesTyped } from '../../lib/core/utils'
16
+
17
+ const COPY_TOOLTIP_CONTENT = {
18
+ neutral: 'Copy ID',
19
+ positive: 'Copied to clipboard',
20
+ critical: 'Unable to copy',
21
+ }
22
+ type TooltipState = { isOpen?: boolean; tone: keyof typeof COPY_TOOLTIP_CONTENT }
23
+
24
+ export function Field({ value }: FieldProps<typeof controller>) {
25
+ const [tooltipState, setTooltipState] = useState<TooltipState>({ tone: 'neutral' })
26
+
27
+ const onCopy = useCallback(async () => {
28
+ try {
29
+ if (value) await copyToClipboard(value)
30
+ setTooltipState({ isOpen: true, tone: 'positive' })
31
+ } catch (err: any) {
32
+ setTooltipState({ isOpen: true, tone: 'critical' })
33
+ }
34
+
35
+ // close, then reset the tooltip state after a delay
36
+ setTimeout(() => {
37
+ setTooltipState(state => ({ ...state, isOpen: false }))
38
+ }, 2000)
39
+ setTimeout(() => {
40
+ setTooltipState({ isOpen: undefined, tone: 'neutral' })
41
+ }, 2300)
42
+ }, [value])
43
+
44
+ return (
45
+ <Grid gap="regular" columns="1fr auto" alignItems="end">
46
+ <TextField
47
+ label="Item ID"
48
+ value={value ?? ''}
49
+ isReadOnly
50
+ onFocus={({ target }) => {
51
+ if (target instanceof HTMLInputElement) target.select()
52
+ }}
53
+ />
54
+ <TooltipTrigger isOpen={tooltipState.isOpen} placement="top end">
55
+ <ActionButton aria-label="copy id" onPress={onCopy}>
56
+ <Icon src={clipboardIcon} />
57
+ </ActionButton>
58
+ <Tooltip tone={tooltipState.tone}>{COPY_TOOLTIP_CONTENT[tooltipState.tone]}</Tooltip>
59
+ </TooltipTrigger>
60
+ </Grid>
61
+ )
62
+ }
63
+
64
+ export function controller(
65
+ config: FieldControllerConfig<IdFieldConfig>
66
+ ): FieldController<
67
+ string | null,
68
+ string | string[],
69
+ InferValueFromInputType<(typeof filters)['mysql']['String']['required']>
70
+ > {
71
+ return {
72
+ fieldKey: config.fieldKey,
73
+ label: config.label,
74
+ description: config.description,
75
+ defaultValue: null,
76
+ deserialize: data => data[config.fieldKey],
77
+ serialize: value => ({ [config.fieldKey]: value }),
78
+ graphqlSelection: config.fieldKey,
79
+ filter: {
80
+ Filter(props) {
81
+ const { autoFocus, context, onChange, type, typeLabel, value, ...otherProps } = props
82
+ const labelProps =
83
+ context === 'add' ? { label: config.label, description: typeLabel } : { label: typeLabel }
84
+ const [inputValue, setInputValue] = useState(() =>
85
+ Array.isArray(value) ? value.join(', ') : value
86
+ )
87
+ const [lastSeenValue, setLastSeenValue] = useState(value)
88
+ if (value !== lastSeenValue && Array.isArray(value)) {
89
+ setInputValue(value.join(', '))
90
+ setLastSeenValue(value)
91
+ }
92
+
93
+ return (
94
+ <TextField
95
+ {...otherProps}
96
+ {...labelProps}
97
+ autoFocus={autoFocus}
98
+ onChange={newVal => {
99
+ if (Array.isArray(value)) {
100
+ setInputValue(newVal)
101
+ } else {
102
+ onChange(newVal)
103
+ }
104
+ }}
105
+ onBlur={() => {
106
+ if (Array.isArray(value)) {
107
+ onChange(inputValue.split(',').map(val => val.trim()))
108
+ }
109
+ }}
110
+ value={Array.isArray(value) ? inputValue : value}
111
+ />
112
+ )
113
+ },
114
+
115
+ Label({ label, value, type }) {
116
+ const listFormatter = useListFormatter({
117
+ style: 'short',
118
+ type: 'disjunction',
119
+ })
120
+
121
+ if (['in', 'not_in'].includes(type)) {
122
+ return `${label.toLowerCase()} (${listFormatter.format(value)})`
123
+ }
124
+ return `${label.toLowerCase()} ${value}`
125
+ },
126
+ graphql: ({ type, value }) => {
127
+ const valueWithoutWhitespace = Array.isArray(value)
128
+ ? value.map(val => val.replace(/\s/g, ''))
129
+ : value.replace(/\s/g, '')
130
+ if (type === 'not')
131
+ return { [config.fieldKey]: { not: { equals: valueWithoutWhitespace } } }
132
+ const key = type === 'is' ? 'equals' : type === 'not_in' ? 'notIn' : type
133
+
134
+ return {
135
+ [config.fieldKey]: {
136
+ [key]: valueWithoutWhitespace,
137
+ },
138
+ }
139
+ },
140
+ parseGraphQL: value => {
141
+ return entriesTyped(value).flatMap(([type, value]) => {
142
+ if (!value) return []
143
+ if (type === 'equals') return { type: 'is', value }
144
+ if (type === 'notIn') return { type: 'not_in', value }
145
+ if (type === 'in') return { type: 'in', value }
146
+ if (type === 'not' && value?.equals) {
147
+ return { type: 'not', value: value?.equals }
148
+ }
149
+ if (type === 'gt' || type === 'gte' || type === 'lt' || type === 'lte') {
150
+ return { type, value }
151
+ }
152
+ return []
153
+ })
154
+ },
155
+ types: {
156
+ is: { label: 'Is exactly', initialValue: '' },
157
+ not: { label: 'Is not exactly', initialValue: '' },
158
+ gt: { label: 'Is greater than', initialValue: '' },
159
+ lt: { label: 'Is less than', initialValue: '' },
160
+ gte: { label: 'Is greater than or equal to', initialValue: '' },
161
+ lte: { label: 'Is less than or equal to', initialValue: '' },
162
+ in: { label: 'Is one of', initialValue: [] },
163
+ not_in: { label: 'Is not one of', initialValue: [] },
164
+ },
165
+ },
166
+ }
167
+ }
@@ -1,22 +1,22 @@
1
- import type { AppProps } from 'next/app'
2
- import type { AdminConfig, FieldViews } from '../../../../types'
3
- import { ErrorBoundary } from '../../../../admin-ui/components'
4
- import { NixxieProvider } from '../../../../admin-ui/context'
5
-
6
- type AppConfig = {
7
- adminConfig: AdminConfig
8
- fieldViews: FieldViews
9
- apiPath: string
10
- }
11
-
12
- export const getApp =
13
- (props: AppConfig) =>
14
- ({ Component, pageProps }: AppProps) => {
15
- return (
16
- <NixxieProvider {...props}>
17
- <ErrorBoundary>
18
- <Component {...pageProps} />
19
- </ErrorBoundary>
20
- </NixxieProvider>
21
- )
22
- }
1
+ import type { AppProps } from 'next/app'
2
+ import type { AdminConfig, FieldViews } from '../../../../types'
3
+ import { ErrorBoundary } from '../../../../admin-ui/components'
4
+ import { NixxieProvider } from '../../../../admin-ui/context'
5
+
6
+ type AppConfig = {
7
+ adminConfig: AdminConfig
8
+ fieldViews: FieldViews
9
+ apiPath: string
10
+ }
11
+
12
+ export const getApp =
13
+ (props: AppConfig) =>
14
+ ({ Component, pageProps }: AppProps) => {
15
+ return (
16
+ <NixxieProvider {...props}>
17
+ <ErrorBoundary>
18
+ <Component {...pageProps} />
19
+ </ErrorBoundary>
20
+ </NixxieProvider>
21
+ )
22
+ }
@@ -1,71 +1,71 @@
1
- import { useRouter } from 'next/router'
2
-
3
- import { Button } from '@keystar/ui/button'
4
- import { VStack } from '@keystar/ui/layout'
5
-
6
- import { useList } from '../../../../admin-ui'
7
- import { GraphQLErrorNotice } from '../../../../admin-ui/components'
8
- import { PageContainer } from '../../../../admin-ui/components/PageContainer'
9
- import { Fields } from '../../../../admin-ui/utils'
10
- import { useCreateItem } from '../../../../admin-ui/utils/useCreateItem'
11
- import { BaseToolbar, ColumnLayout, ItemPageHeader } from '../ItemPage/common'
12
-
13
- export const getCreateItemPage = (props: Parameters<typeof CreateItemPage>[0]) => () => (
14
- <CreateItemPage {...props} />
15
- )
16
-
17
- function CreateItemPage({ listKey }: { listKey: string }) {
18
- const list = useList(listKey)
19
- const createItem = useCreateItem(list)
20
- const router = useRouter()
21
-
22
- return (
23
- <PageContainer
24
- title={`Create ${list.singular}`}
25
- header={
26
- <ItemPageHeader
27
- list={list}
28
- actions={[]}
29
- value={null}
30
- label="Create"
31
- title={`Create ${list.singular}`}
32
- item={null}
33
- initialValue={null}
34
- onAction={null}
35
- />
36
- }
37
- >
38
- <ColumnLayout>
39
- <form
40
- onSubmit={async e => {
41
- if (e.target !== e.currentTarget) return
42
- e.preventDefault()
43
-
44
- const item = await createItem.create()
45
- if (!item) return
46
-
47
- router.push(`/${list.path}/${item.id}`)
48
- }}
49
- style={{ display: 'contents' }}
50
- >
51
- {/*
52
- Workaround for react-aria "bug" where pressing enter in a form field
53
- moves focus to the submit button.
54
- See: https://github.com/adobe/react-spectrum/issues/5940
55
- */}
56
- <button type="submit" style={{ display: 'none' }} />
57
- <VStack gap="large" gridArea="main" marginTop="xlarge" minWidth={0}>
58
- <GraphQLErrorNotice errors={[createItem.error]} />
59
- <Fields {...createItem.props} />
60
- </VStack>
61
-
62
- <BaseToolbar>
63
- <Button isPending={createItem.state === 'loading'} prominence="high" type="submit">
64
- Create
65
- </Button>
66
- </BaseToolbar>
67
- </form>
68
- </ColumnLayout>
69
- </PageContainer>
70
- )
71
- }
1
+ import { useRouter } from 'next/router'
2
+
3
+ import { Button } from '@keystar/ui/button'
4
+ import { VStack } from '@keystar/ui/layout'
5
+
6
+ import { useList } from '../../../../admin-ui'
7
+ import { GraphQLErrorNotice } from '../../../../admin-ui/components'
8
+ import { PageContainer } from '../../../../admin-ui/components/PageContainer'
9
+ import { Fields } from '../../../../admin-ui/utils'
10
+ import { useCreateItem } from '../../../../admin-ui/utils/useCreateItem'
11
+ import { BaseToolbar, ColumnLayout, ItemPageHeader } from '../ItemPage/common'
12
+
13
+ export const getCreateItemPage = (props: Parameters<typeof CreateItemPage>[0]) => () => (
14
+ <CreateItemPage {...props} />
15
+ )
16
+
17
+ function CreateItemPage({ listKey }: { listKey: string }) {
18
+ const list = useList(listKey)
19
+ const createItem = useCreateItem(list)
20
+ const router = useRouter()
21
+
22
+ return (
23
+ <PageContainer
24
+ title={`Create ${list.singular}`}
25
+ header={
26
+ <ItemPageHeader
27
+ list={list}
28
+ actions={[]}
29
+ value={null}
30
+ label="Create"
31
+ title={`Create ${list.singular}`}
32
+ item={null}
33
+ initialValue={null}
34
+ onAction={null}
35
+ />
36
+ }
37
+ >
38
+ <ColumnLayout>
39
+ <form
40
+ onSubmit={async e => {
41
+ if (e.target !== e.currentTarget) return
42
+ e.preventDefault()
43
+
44
+ const item = await createItem.create()
45
+ if (!item) return
46
+
47
+ router.push(`/${list.path}/${item.id}`)
48
+ }}
49
+ style={{ display: 'contents' }}
50
+ >
51
+ {/*
52
+ Workaround for react-aria "bug" where pressing enter in a form field
53
+ moves focus to the submit button.
54
+ See: https://github.com/adobe/react-spectrum/issues/5940
55
+ */}
56
+ <button type="submit" style={{ display: 'none' }} />
57
+ <VStack gap="large" gridArea="main" marginTop="xlarge" minWidth={0}>
58
+ <GraphQLErrorNotice errors={[createItem.error]} />
59
+ <Fields {...createItem.props} />
60
+ </VStack>
61
+
62
+ <BaseToolbar>
63
+ <Button isPending={createItem.state === 'loading'} prominence="high" type="submit">
64
+ Create
65
+ </Button>
66
+ </BaseToolbar>
67
+ </form>
68
+ </ColumnLayout>
69
+ </PageContainer>
70
+ )
71
+ }