@graphcommerce/hygraph-dynamic-rows-ui 7.1.0-canary.54
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/CHANGELOG.md +7 -0
- package/components/PropertyPicker.tsx +124 -0
- package/components/Setup.tsx +103 -0
- package/components/index.ts +1 -0
- package/components/setup.module.css +58 -0
- package/index.ts +2 -0
- package/lib/createOptionsFromInterfaceObject.ts +42 -0
- package/lib/createRecursiveIntrospectionQuery.ts +13 -0
- package/lib/fetchGraphQLInterface.ts +14 -0
- package/lib/index.ts +4 -0
- package/lib/objectifyGraphQLInterface.ts +62 -0
- package/next-env.d.ts +5 -0
- package/package.json +39 -0
- package/pages/_app.tsx +5 -0
- package/pages/property-picker.tsx +71 -0
- package/pages/setup.tsx +29 -0
- package/tsconfig.json +5 -0
- package/types/index.ts +38 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @graphcommerce/hygraph-dynamic-rows-ui
|
|
2
|
+
|
|
3
|
+
## 7.1.0-canary.54
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2100](https://github.com/graphcommerce-org/graphcommerce/pull/2100) [`4df891a4c`](https://github.com/graphcommerce-org/graphcommerce/commit/4df891a4c18b29cc52447eab3a97c66948b6c18f) - Add Dynamic Row UI for property UI field through a custom Hygraph application ([@JoshuaS98](https://github.com/JoshuaS98))
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useFieldExtension } from '@hygraph/app-sdk-react'
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
3
|
+
import { TextField } from '@mui/material'
|
|
4
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
5
|
+
import { ApolloClient, InMemoryCache } from '@apollo/client'
|
|
6
|
+
import { fetchGraphQLInterface } from '../lib/fetchGraphQLInterface'
|
|
7
|
+
import { createOptionsFromInterfaceObject, objectifyGraphQLInterface } from '../lib'
|
|
8
|
+
|
|
9
|
+
export function PropertyPicker() {
|
|
10
|
+
const { value, onChange, field, extension } = useFieldExtension()
|
|
11
|
+
const [localValue, setLocalValue] = useState<string | undefined | null>(
|
|
12
|
+
typeof value === 'string' ? value : undefined,
|
|
13
|
+
)
|
|
14
|
+
const [fields, setFields] = useState<any>(null)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
onChange(localValue).catch((err) => console.log(err))
|
|
18
|
+
}, [localValue, onChange])
|
|
19
|
+
|
|
20
|
+
const client = new ApolloClient({
|
|
21
|
+
uri:
|
|
22
|
+
typeof extension.config.backend === 'string'
|
|
23
|
+
? extension.config.backend
|
|
24
|
+
: 'https://graphcommerce.vercel.app/api/graphql', // fallback on the standard GraphCommerce Schema
|
|
25
|
+
cache: new InMemoryCache(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const graphQLInterfaceQuery = useMemo(() => fetchGraphQLInterface(client), [client])
|
|
29
|
+
|
|
30
|
+
// Prepare options
|
|
31
|
+
const numberOptions = useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
createOptionsFromInterfaceObject(
|
|
34
|
+
objectifyGraphQLInterface(fields, 'number', ['ProductInterface']),
|
|
35
|
+
),
|
|
36
|
+
[fields],
|
|
37
|
+
)
|
|
38
|
+
const textOptions = useMemo(
|
|
39
|
+
() =>
|
|
40
|
+
createOptionsFromInterfaceObject(
|
|
41
|
+
objectifyGraphQLInterface(fields, 'text', ['ProductInterface']),
|
|
42
|
+
),
|
|
43
|
+
[fields],
|
|
44
|
+
)
|
|
45
|
+
const allOptions = useMemo(
|
|
46
|
+
() => ({
|
|
47
|
+
text: [...textOptions, { label: 'url', id: 'url' }].sort((a, b) => {
|
|
48
|
+
if (!a.label.includes('.') && !b.label.includes('.')) {
|
|
49
|
+
return a.label.localeCompare(b.label)
|
|
50
|
+
}
|
|
51
|
+
if (a.label.includes('.')) {
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
return -1
|
|
55
|
+
}),
|
|
56
|
+
number: [...numberOptions, { label: 'url', id: 'url' }],
|
|
57
|
+
}),
|
|
58
|
+
[numberOptions, textOptions],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
62
|
+
// @ts-ignore - outdated types from @hygraph/app-sdk-react
|
|
63
|
+
const fieldType = field.parent.apiId ?? 'ConditionText'
|
|
64
|
+
const options = fieldType === 'ConditionNumber' ? allOptions.number : allOptions.text
|
|
65
|
+
|
|
66
|
+
if (!fields) {
|
|
67
|
+
Promise.resolve(graphQLInterfaceQuery).then((res) => {
|
|
68
|
+
const fields = res?.data.__type?.fields
|
|
69
|
+
|
|
70
|
+
setFields(fields)
|
|
71
|
+
})
|
|
72
|
+
return <div>Loading fields...</div>
|
|
73
|
+
}
|
|
74
|
+
if (options.length < 1) return <div>No properties available</div>
|
|
75
|
+
if (options.length > 10000) return <div>Too many properties to display</div>
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<TextField
|
|
79
|
+
id='property-selector'
|
|
80
|
+
select
|
|
81
|
+
SelectProps={{
|
|
82
|
+
native: true,
|
|
83
|
+
variant: 'outlined',
|
|
84
|
+
}}
|
|
85
|
+
value={localValue}
|
|
86
|
+
onChange={(v) => {
|
|
87
|
+
const val = v.target.value
|
|
88
|
+
setLocalValue(val)
|
|
89
|
+
}}
|
|
90
|
+
fullWidth
|
|
91
|
+
sx={{
|
|
92
|
+
mt: '4px',
|
|
93
|
+
'& .MuiInputBase-root': {
|
|
94
|
+
borderRadius: { xs: '2px!important' },
|
|
95
|
+
},
|
|
96
|
+
'& .MuiOutlinedInput-root': {
|
|
97
|
+
'& fieldset.MuiOutlinedInput-notchedOutline': {
|
|
98
|
+
borderColor: { xs: 'rgb(208, 213, 231)' },
|
|
99
|
+
transition: 'border-color 0.25s ease 0s',
|
|
100
|
+
},
|
|
101
|
+
'&:hover': {
|
|
102
|
+
'& fieldset.MuiOutlinedInput-notchedOutline': {
|
|
103
|
+
borderColor: { xs: 'rgb(208, 213, 231)' },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
'&.Mui-focused': {
|
|
107
|
+
'& fieldset.MuiOutlinedInput-notchedOutline': {
|
|
108
|
+
borderColor: { xs: 'rgb(90, 92, 236)' },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
'& .MuiInputLabel-root.Mui-focused': {
|
|
113
|
+
color: { xs: 'rgb(90, 92, 236)' },
|
|
114
|
+
},
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{options.map((o) => (
|
|
118
|
+
<option key={o.id} value={o.id}>
|
|
119
|
+
{o.label}
|
|
120
|
+
</option>
|
|
121
|
+
))}
|
|
122
|
+
</TextField>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useApp, Wrapper } from '@hygraph/app-sdk-react'
|
|
2
|
+
import styles from './setup.module.css'
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
function Install() {
|
|
6
|
+
// @ts-ignore - outdated types from @hygraph/app-sdk-react
|
|
7
|
+
const { updateInstallation, installation, showToast, extension } = useApp()
|
|
8
|
+
const installed = installation.status === 'COMPLETED'
|
|
9
|
+
const [gqlUri, setGqlUri] = useState('')
|
|
10
|
+
|
|
11
|
+
const saveOnClick = () => {
|
|
12
|
+
updateInstallation({
|
|
13
|
+
config: { backend: gqlUri },
|
|
14
|
+
status: 'COMPLETED',
|
|
15
|
+
}).then(() =>
|
|
16
|
+
showToast({
|
|
17
|
+
title: 'New GraphQL URI saved',
|
|
18
|
+
description: `${gqlUri} is now the GraphQL URI for this application.}`,
|
|
19
|
+
duration: 5000,
|
|
20
|
+
isClosable: true,
|
|
21
|
+
position: 'top-left',
|
|
22
|
+
variantColor: 'success',
|
|
23
|
+
}).catch((err) => console.log(err)),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const changedUri = extension.config.backend !== gqlUri
|
|
28
|
+
|
|
29
|
+
const installOnClick = () =>
|
|
30
|
+
updateInstallation({
|
|
31
|
+
config: { backend: gqlUri },
|
|
32
|
+
status: 'COMPLETED',
|
|
33
|
+
}).then(() =>
|
|
34
|
+
showToast({
|
|
35
|
+
title: 'Application enabled',
|
|
36
|
+
description: 'You can now use the Dynamic Row Property Selector field in your schema.',
|
|
37
|
+
duration: 5000,
|
|
38
|
+
isClosable: true,
|
|
39
|
+
position: 'top-left',
|
|
40
|
+
variantColor: 'success',
|
|
41
|
+
}).catch((err) => console.log(err)),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const uninstallOnClick = async () => {
|
|
45
|
+
updateInstallation({
|
|
46
|
+
config: {},
|
|
47
|
+
status: 'DISABLED',
|
|
48
|
+
})
|
|
49
|
+
.then(() => {
|
|
50
|
+
showToast({
|
|
51
|
+
title: 'Application disabled',
|
|
52
|
+
description: 'You can re-enable the application from the application configuration page.',
|
|
53
|
+
duration: 5000,
|
|
54
|
+
isClosable: true,
|
|
55
|
+
position: 'top-left',
|
|
56
|
+
variantColor: 'success',
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
.catch((error) => {
|
|
60
|
+
console.error('Error updating installation', error)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<>
|
|
69
|
+
<span>GraphQL API URI</span>
|
|
70
|
+
<input
|
|
71
|
+
name='gql-uri'
|
|
72
|
+
defaultValue={extension.config.backend}
|
|
73
|
+
onChange={(e) => setGqlUri(e.target.value)}
|
|
74
|
+
/>
|
|
75
|
+
</>
|
|
76
|
+
|
|
77
|
+
<button
|
|
78
|
+
type='button'
|
|
79
|
+
className={styles.button}
|
|
80
|
+
onClick={changedUri ? saveOnClick : installed ? uninstallOnClick : installOnClick}
|
|
81
|
+
>
|
|
82
|
+
{changedUri ? 'Save' : installed ? 'Disable app' : 'Enable app'}
|
|
83
|
+
</button>
|
|
84
|
+
</>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function Page() {
|
|
89
|
+
return (
|
|
90
|
+
<div className={styles.container}>
|
|
91
|
+
<h1 className={styles.title}>Dynamic Rows Property Selector</h1>
|
|
92
|
+
<p className={styles.description}>
|
|
93
|
+
Enhance your content management experience with Dynamic Rows, specifically designed to
|
|
94
|
+
integrate seamlessly with our Dynamic Row module. It features an intuitive property picker
|
|
95
|
+
field, allowing for effortless selection and organization of properties to customize your
|
|
96
|
+
content layout. Press install to get started!
|
|
97
|
+
</p>
|
|
98
|
+
<Wrapper>
|
|
99
|
+
<Install />
|
|
100
|
+
</Wrapper>
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PropertyPicker'
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
height: 100%;
|
|
7
|
+
max-width: 1200px;
|
|
8
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.title {
|
|
12
|
+
font-size: 24px;
|
|
13
|
+
font-weight: 600;
|
|
14
|
+
line-height: 32px;
|
|
15
|
+
margin-bottom: 16px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.desciption {
|
|
19
|
+
font-size: 14px;
|
|
20
|
+
font-weight: 300;
|
|
21
|
+
line-height: 20px;
|
|
22
|
+
margin-bottom: 16px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.input {
|
|
26
|
+
display: inline;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.button {
|
|
30
|
+
user-select: none;
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
appearance: none;
|
|
33
|
+
position: relative;
|
|
34
|
+
display: inline-flex;
|
|
35
|
+
-webkit-box-align: center;
|
|
36
|
+
align-items: center;
|
|
37
|
+
text-align: center;
|
|
38
|
+
vertical-align: middle;
|
|
39
|
+
align-self: center;
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
font-weight: 500;
|
|
42
|
+
border: 0px;
|
|
43
|
+
margin: 16px 0px 0px;
|
|
44
|
+
border-radius: 4px;
|
|
45
|
+
font-size: 12px;
|
|
46
|
+
line-height: 16px;
|
|
47
|
+
height: 24px;
|
|
48
|
+
min-width: 24px;
|
|
49
|
+
padding-left: 8px;
|
|
50
|
+
padding-right: 8px;
|
|
51
|
+
color: rgb(255, 255, 255);
|
|
52
|
+
background-color: rgb(90, 92, 236);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.button:hover {
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
background-color: rgb(58, 48, 166);
|
|
58
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ProductProperty } from '../types'
|
|
2
|
+
|
|
3
|
+
export const createOptionsFromInterfaceObject = (
|
|
4
|
+
obj: object,
|
|
5
|
+
path = '',
|
|
6
|
+
inputs: ProductProperty[] = [],
|
|
7
|
+
parent = '',
|
|
8
|
+
): ProductProperty[] => {
|
|
9
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
10
|
+
/** Keep count of the current path and parent */
|
|
11
|
+
const currentPath: string = path ? `${path}.${key}` : key
|
|
12
|
+
const currentParent: string = parent ? `${parent}/` : ''
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* If the value is a string, number or boolean, add it to the inputs array. If the value is an
|
|
16
|
+
* array, recurse on the first item. If the value is an object, recurse on all it's keys.
|
|
17
|
+
*/
|
|
18
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
19
|
+
inputs.push({
|
|
20
|
+
label: currentPath,
|
|
21
|
+
id: currentPath,
|
|
22
|
+
})
|
|
23
|
+
} else if (Array.isArray(value) && value.length > 0) {
|
|
24
|
+
createOptionsFromInterfaceObject(
|
|
25
|
+
value[0] as object,
|
|
26
|
+
`${currentPath}[0]`,
|
|
27
|
+
inputs,
|
|
28
|
+
`${currentParent}${key}`,
|
|
29
|
+
)
|
|
30
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
32
|
+
createOptionsFromInterfaceObject(
|
|
33
|
+
value as object,
|
|
34
|
+
currentPath,
|
|
35
|
+
inputs,
|
|
36
|
+
`${currentParent}${key}`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return inputs
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const createRecursiveIntrospectionQuery = (type: string, depth: number) => {
|
|
2
|
+
let baseQuery = `__type(name: "${type}") { name fields { name `
|
|
3
|
+
let endQuery = ' } }'
|
|
4
|
+
|
|
5
|
+
for (let i = 0; i < depth; i++) {
|
|
6
|
+
baseQuery += 'type { name ofType { name fields { name isDeprecated '
|
|
7
|
+
endQuery += ' } } }'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const result = baseQuery + endQuery
|
|
11
|
+
|
|
12
|
+
return result
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ApolloClient, NormalizedCacheObject, gql } from '@apollo/client'
|
|
2
|
+
import { createRecursiveIntrospectionQuery } from './createRecursiveIntrospectionQuery'
|
|
3
|
+
|
|
4
|
+
export const fetchGraphQLInterface = (client: ApolloClient<NormalizedCacheObject>) => {
|
|
5
|
+
const introspectionQuery = createRecursiveIntrospectionQuery('ProductInterface', 4)
|
|
6
|
+
|
|
7
|
+
return client.query({
|
|
8
|
+
query: gql`
|
|
9
|
+
query getSchema {
|
|
10
|
+
${introspectionQuery}
|
|
11
|
+
}
|
|
12
|
+
`,
|
|
13
|
+
})
|
|
14
|
+
}
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { __Field } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In this function we create an object from the GraphQL interface.
|
|
5
|
+
* We need this so we can map out the properties of an interface that is needed
|
|
6
|
+
* for the Dynamic Rows Autocomplete.
|
|
7
|
+
* @param fields - The GraphQL interface object that is read from the schema.
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export const objectifyGraphQLInterface = (
|
|
11
|
+
fields: __Field[] | null,
|
|
12
|
+
conditionType: 'text' | 'number' | 'all',
|
|
13
|
+
skip: string[],
|
|
14
|
+
): object => {
|
|
15
|
+
let objectifiedInterface: object = {}
|
|
16
|
+
|
|
17
|
+
if (!fields) return objectifiedInterface
|
|
18
|
+
|
|
19
|
+
for (const [, value] of Object.entries(fields)) {
|
|
20
|
+
const nestedFields = value?.type?.ofType?.fields
|
|
21
|
+
const { isDeprecated } = value
|
|
22
|
+
const typeOf = value?.type?.name
|
|
23
|
+
const typeName = value?.type?.ofType?.name ?? ''
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* With typevalue we can know of which type a property is, so we for example can determine to to hide string values in ConditionNumbers.
|
|
27
|
+
*/
|
|
28
|
+
let typeValue: 'number' | 'text' | 'boolean'
|
|
29
|
+
switch (typeOf) {
|
|
30
|
+
case 'Float' || 'Int':
|
|
31
|
+
typeValue = 'number'
|
|
32
|
+
break
|
|
33
|
+
case 'Boolean':
|
|
34
|
+
typeValue = 'text' // Seperate booleans are not supported yet.
|
|
35
|
+
break
|
|
36
|
+
default:
|
|
37
|
+
typeValue = 'text'
|
|
38
|
+
break
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (skip.includes(typeName) || isDeprecated || !value?.name) {
|
|
42
|
+
// do nothing
|
|
43
|
+
} else if (nestedFields) {
|
|
44
|
+
objectifiedInterface = {
|
|
45
|
+
...objectifiedInterface,
|
|
46
|
+
[value.name]: objectifyGraphQLInterface(nestedFields, conditionType, [...skip, typeName]),
|
|
47
|
+
}
|
|
48
|
+
} else if (typeOf && conditionType === 'all') {
|
|
49
|
+
objectifiedInterface = {
|
|
50
|
+
...objectifiedInterface,
|
|
51
|
+
[value.name]: typeValue,
|
|
52
|
+
}
|
|
53
|
+
} else if (conditionType === typeValue) {
|
|
54
|
+
objectifiedInterface = {
|
|
55
|
+
...objectifiedInterface,
|
|
56
|
+
[value.name]: typeValue,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return objectifiedInterface
|
|
62
|
+
}
|
package/next-env.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@graphcommerce/hygraph-dynamic-rows-ui",
|
|
3
|
+
"homepage": "https://www.graphcommerce.org/",
|
|
4
|
+
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
+
"version": "7.1.0-canary.54",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
9
|
+
"eslintConfig": {
|
|
10
|
+
"extends": "@graphcommerce/eslint-config-pwa",
|
|
11
|
+
"parserOptions": {
|
|
12
|
+
"project": "./tsconfig.json"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "next dev"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@apollo/client": "~3.8.7",
|
|
20
|
+
"@graphcommerce/next-config": "7.1.0-canary.54",
|
|
21
|
+
"@hygraph/app-sdk-react": "^0.0.2",
|
|
22
|
+
"@mui/material": "5.14.7",
|
|
23
|
+
"cross-env": "^7.0.3",
|
|
24
|
+
"dotenv": "16.3.1",
|
|
25
|
+
"graphql": "^16.8.1",
|
|
26
|
+
"next": "^13.2.0",
|
|
27
|
+
"react": "^18.2.0",
|
|
28
|
+
"react-dom": "^18.2.0",
|
|
29
|
+
"webpack": "5.88.2"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@graphcommerce/eslint-config-pwa": "7.1.0-canary.54",
|
|
33
|
+
"@graphcommerce/prettier-config-pwa": "7.1.0-canary.54",
|
|
34
|
+
"@graphcommerce/typescript-config-pwa": "7.1.0-canary.54",
|
|
35
|
+
"@types/react-is": "^18.2.0",
|
|
36
|
+
"eslint": "8.53.0",
|
|
37
|
+
"typescript": "5.1.3"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/pages/_app.tsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
|
|
2
|
+
import { loadConfig } from '@graphcommerce/next-config'
|
|
3
|
+
import { Wrapper } from '@hygraph/app-sdk-react'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { PropertyPicker } from '..'
|
|
6
|
+
import {
|
|
7
|
+
createOptionsFromInterfaceObject,
|
|
8
|
+
objectifyGraphQLInterface,
|
|
9
|
+
fetchGraphQLInterface,
|
|
10
|
+
} from '../lib'
|
|
11
|
+
import { Interface } from '../types'
|
|
12
|
+
|
|
13
|
+
type PropertyPickerProps = Interface
|
|
14
|
+
|
|
15
|
+
export default function DRPropertyPicker(props: PropertyPickerProps) {
|
|
16
|
+
const fieldContainer = React.useRef<HTMLDivElement | null>(null)
|
|
17
|
+
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
/**
|
|
20
|
+
* Some styling is being undone here to resolve conflicts between Hygraph App SDK and CssAndFramerMotionProvider.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const frameBox1 = fieldContainer?.current?.parentElement
|
|
24
|
+
if (frameBox1) {
|
|
25
|
+
frameBox1.style.position = 'static'
|
|
26
|
+
frameBox1.style.minHeight = 'unset'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const frameBox2 = frameBox1?.previousSibling as HTMLDivElement | null
|
|
30
|
+
if (frameBox2) {
|
|
31
|
+
frameBox2.style.minHeight = 'unset'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const body = frameBox1?.parentElement
|
|
35
|
+
if (body) {
|
|
36
|
+
body.style.margin = '0'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const html = body?.parentElement
|
|
40
|
+
if (html) {
|
|
41
|
+
html.style.background = 'transparent'
|
|
42
|
+
html.style.overflow = 'hidden'
|
|
43
|
+
}
|
|
44
|
+
}, [fieldContainer])
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div ref={fieldContainer}>
|
|
48
|
+
<Wrapper>
|
|
49
|
+
<PropertyPicker />
|
|
50
|
+
</Wrapper>
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const getStaticProps = async () => {
|
|
56
|
+
const config = loadConfig(process.cwd())
|
|
57
|
+
const staticClient = new ApolloClient({
|
|
58
|
+
link: new HttpLink({
|
|
59
|
+
uri: config.magentoEndpoint,
|
|
60
|
+
fetch,
|
|
61
|
+
}),
|
|
62
|
+
cache: new InMemoryCache(),
|
|
63
|
+
})
|
|
64
|
+
const graphQLInterface = fetchGraphQLInterface(staticClient)
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
props: {
|
|
68
|
+
...(await graphQLInterface).data,
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
}
|
package/pages/setup.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React, { MutableRefObject, ReactNode, RefObject } from 'react'
|
|
2
|
+
import { Page } from '..'
|
|
3
|
+
|
|
4
|
+
export default function Setup() {
|
|
5
|
+
const appContainer = React.useRef<HTMLDivElement | null>(null)
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This is a hack to fix the height of the iframe, which was malfunctioning because of a conflict
|
|
9
|
+
* with FramerNextPages
|
|
10
|
+
*/
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
const framerParent = appContainer?.current?.parentElement
|
|
13
|
+
if (framerParent) {
|
|
14
|
+
framerParent.style.position = 'static'
|
|
15
|
+
framerParent.style.minHeight = 'unset'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const framerParent2 = framerParent?.previousSibling as HTMLDivElement | null
|
|
19
|
+
if (framerParent2) {
|
|
20
|
+
framerParent2.style.minHeight = 'unset'
|
|
21
|
+
}
|
|
22
|
+
}, [appContainer])
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div ref={appContainer}>
|
|
26
|
+
<Page />
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
package/tsconfig.json
ADDED
package/types/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type ProductProperty = {
|
|
2
|
+
label: string
|
|
3
|
+
id: string
|
|
4
|
+
type?: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type Interface = {
|
|
8
|
+
__type: __Type
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type __Type = {
|
|
12
|
+
kind?: __TypeKind
|
|
13
|
+
name?: string
|
|
14
|
+
description?: string
|
|
15
|
+
fields: __Field[]
|
|
16
|
+
ofType?: { name?: string; fields: __Field[] }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type __TypeKind =
|
|
20
|
+
| 'SCALAR'
|
|
21
|
+
| 'OBJECT'
|
|
22
|
+
| 'INTERFACE'
|
|
23
|
+
| 'UNION'
|
|
24
|
+
| 'ENUM'
|
|
25
|
+
| 'INPUT_OBJECT'
|
|
26
|
+
| 'LIST'
|
|
27
|
+
| 'NON_NULL'
|
|
28
|
+
|
|
29
|
+
export type __Field = {
|
|
30
|
+
name: string
|
|
31
|
+
type: __Type
|
|
32
|
+
isDeprecated: boolean
|
|
33
|
+
description?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type Option = { id: string; label: string }
|
|
37
|
+
|
|
38
|
+
export type Options = { text: Option[]; number: Option[] }
|