@shopify/shop-minis-react 0.2.2 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/atoms/touchable.js +31 -28
- package/dist/components/atoms/touchable.js.map +1 -1
- package/eslint/rules/prefer-sdk-components.cjs +18 -87
- package/eslint/rules/validate-manifest.cjs +86 -2
- package/generated-hook-maps/component-scopes-map.json +18 -0
- package/package.json +1 -1
- package/src/components/atoms/touchable.tsx +7 -4
|
@@ -1,53 +1,56 @@
|
|
|
1
1
|
import { jsx as d } from "react/jsx-runtime";
|
|
2
2
|
import * as i from "react";
|
|
3
3
|
import { useAnimationControls as f } from "../../shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/animation/hooks/use-animation.js";
|
|
4
|
-
import { motion as
|
|
4
|
+
import { motion as y } from "../../shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/render/components/motion/proxy.js";
|
|
5
5
|
const L = ({
|
|
6
|
-
children:
|
|
6
|
+
children: u,
|
|
7
7
|
onClick: a,
|
|
8
|
-
stopPropagation:
|
|
9
|
-
...
|
|
8
|
+
stopPropagation: t = !1,
|
|
9
|
+
...l
|
|
10
10
|
}) => {
|
|
11
|
-
const r = i.useRef(null),
|
|
12
|
-
(
|
|
13
|
-
|
|
11
|
+
const r = i.useRef(null), o = f(), { ref: w, ...p } = l, m = i.useCallback(
|
|
12
|
+
(e) => {
|
|
13
|
+
t && e.stopPropagation(), a?.(e);
|
|
14
14
|
},
|
|
15
|
-
[
|
|
15
|
+
[t, a]
|
|
16
16
|
);
|
|
17
17
|
return i.useEffect(() => {
|
|
18
|
-
if (!
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
if (!t || !r.current) return;
|
|
19
|
+
const e = r.current, s = (n) => {
|
|
20
|
+
n.stopImmediatePropagation(), n.stopPropagation(), o.start({
|
|
21
|
+
opacity: 0.7,
|
|
22
|
+
transition: {
|
|
23
|
+
opacity: { type: "tween", duration: 0.08, ease: "linear" }
|
|
24
|
+
}
|
|
23
25
|
});
|
|
24
|
-
}, c = (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
}, c = (n) => {
|
|
27
|
+
n.stopImmediatePropagation(), n.stopPropagation(), o.start({
|
|
28
|
+
opacity: 1,
|
|
29
|
+
transition: {
|
|
30
|
+
opacity: { type: "tween", duration: 0.08, ease: "linear" }
|
|
31
|
+
}
|
|
28
32
|
});
|
|
29
33
|
};
|
|
30
|
-
return
|
|
31
|
-
|
|
34
|
+
return e.addEventListener("pointerdown", s, !0), e.addEventListener("pointerup", c, !0), () => {
|
|
35
|
+
e.removeEventListener("pointerdown", s, !0), e.removeEventListener("pointerup", c, !0);
|
|
32
36
|
};
|
|
33
|
-
}, [
|
|
34
|
-
|
|
37
|
+
}, [t, o]), /* @__PURE__ */ d(
|
|
38
|
+
y.div,
|
|
35
39
|
{
|
|
36
40
|
ref: r,
|
|
37
41
|
"data-touchable": "true",
|
|
38
42
|
className: "flex w-full",
|
|
39
|
-
animate:
|
|
40
|
-
whileTap:
|
|
43
|
+
animate: t ? o : void 0,
|
|
44
|
+
whileTap: t ? void 0 : { opacity: 0.7 },
|
|
41
45
|
transition: {
|
|
42
|
-
scale: { type: "tween", duration: 0.08, ease: "linear" },
|
|
43
46
|
opacity: { type: "tween", duration: 0.08, ease: "linear" }
|
|
44
47
|
},
|
|
45
|
-
onClick:
|
|
48
|
+
onClick: m,
|
|
46
49
|
style: {
|
|
47
|
-
touchAction:
|
|
50
|
+
touchAction: t ? "manipulation" : void 0
|
|
48
51
|
},
|
|
49
|
-
...
|
|
50
|
-
children:
|
|
52
|
+
...p,
|
|
53
|
+
children: u
|
|
51
54
|
}
|
|
52
55
|
);
|
|
53
56
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"touchable.js","sources":["../../../src/components/atoms/touchable.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport {motion, HTMLMotionProps, useAnimationControls} from 'motion/react'\n\nexport const Touchable = ({\n children,\n onClick,\n stopPropagation = false,\n ...props\n}: HTMLMotionProps<'div'> & {\n onClick?: React.MouseEventHandler<HTMLDivElement>\n stopPropagation?: boolean\n}) => {\n const ref = React.useRef<HTMLDivElement>(null)\n const controls = useAnimationControls()\n\n // Filter out props that shouldn't be passed to motion.div\n // Any other custom props that get added to the component interface should be filtered here\n const {ref: _, ...motionProps} = props\n\n const handleClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n if (stopPropagation) event.stopPropagation()\n onClick?.(event)\n },\n [stopPropagation, onClick]\n )\n\n // Handle animations manually when stopPropagation is true to prevent parent from receiving event\n React.useEffect(() => {\n if (!stopPropagation || !ref.current) return\n\n const element = ref.current\n\n const handlePointerDown = (event: PointerEvent) => {\n event.stopImmediatePropagation()\n event.stopPropagation()\n\n // Animate to pressed state\n controls.start({\n
|
|
1
|
+
{"version":3,"file":"touchable.js","sources":["../../../src/components/atoms/touchable.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport {motion, HTMLMotionProps, useAnimationControls} from 'motion/react'\n\nexport const Touchable = ({\n children,\n onClick,\n stopPropagation = false,\n ...props\n}: HTMLMotionProps<'div'> & {\n onClick?: React.MouseEventHandler<HTMLDivElement>\n stopPropagation?: boolean\n}) => {\n const ref = React.useRef<HTMLDivElement>(null)\n const controls = useAnimationControls()\n\n // Filter out props that shouldn't be passed to motion.div\n // Any other custom props that get added to the component interface should be filtered here\n const {ref: _, ...motionProps} = props\n\n const handleClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n if (stopPropagation) event.stopPropagation()\n onClick?.(event)\n },\n [stopPropagation, onClick]\n )\n\n // Handle animations manually when stopPropagation is true to prevent parent from receiving event\n React.useEffect(() => {\n if (!stopPropagation || !ref.current) return\n\n const element = ref.current\n\n const handlePointerDown = (event: PointerEvent) => {\n event.stopImmediatePropagation()\n event.stopPropagation()\n\n // Animate to pressed state\n controls.start({\n opacity: 0.7,\n transition: {\n opacity: {type: 'tween', duration: 0.08, ease: 'linear'},\n },\n })\n }\n\n const handlePointerUp = (event: PointerEvent) => {\n event.stopImmediatePropagation()\n event.stopPropagation()\n\n // Animate back to normal state\n controls.start({\n opacity: 1,\n transition: {\n opacity: {type: 'tween', duration: 0.08, ease: 'linear'},\n },\n })\n }\n\n // Capture pointer event before Motion\n element.addEventListener('pointerdown', handlePointerDown, true)\n element.addEventListener('pointerup', handlePointerUp, true)\n\n return () => {\n element.removeEventListener('pointerdown', handlePointerDown, true)\n element.removeEventListener('pointerup', handlePointerUp, true)\n }\n }, [stopPropagation, controls])\n\n return (\n <motion.div\n ref={ref}\n data-touchable=\"true\"\n className=\"flex w-full\"\n animate={stopPropagation ? controls : undefined}\n whileTap={stopPropagation ? undefined : {opacity: 0.7}}\n transition={{\n opacity: {type: 'tween', duration: 0.08, ease: 'linear'},\n }}\n onClick={handleClick}\n style={{\n touchAction: stopPropagation ? 'manipulation' : undefined,\n }}\n {...motionProps}\n >\n {children}\n </motion.div>\n )\n}\n"],"names":["Touchable","children","onClick","stopPropagation","props","ref","React","controls","useAnimationControls","_","motionProps","handleClick","event","element","handlePointerDown","handlePointerUp","jsx","motion"],"mappings":";;;;AAIO,MAAMA,IAAY,CAAC;AAAA,EACxB,UAAAC;AAAA,EACA,SAAAC;AAAA,EACA,iBAAAC,IAAkB;AAAA,EAClB,GAAGC;AACL,MAGM;AACE,QAAAC,IAAMC,EAAM,OAAuB,IAAI,GACvCC,IAAWC,EAAqB,GAIhC,EAAC,KAAKC,GAAG,GAAGC,EAAe,IAAAN,GAE3BO,IAAcL,EAAM;AAAA,IACxB,CAACM,MAA4C;AACvC,MAAAT,OAAuB,gBAAgB,GAC3CD,IAAUU,CAAK;AAAA,IACjB;AAAA,IACA,CAACT,GAAiBD,CAAO;AAAA,EAC3B;AAGA,SAAAI,EAAM,UAAU,MAAM;AACpB,QAAI,CAACH,KAAmB,CAACE,EAAI,QAAS;AAEtC,UAAMQ,IAAUR,EAAI,SAEdS,IAAoB,CAACF,MAAwB;AACjD,MAAAA,EAAM,yBAAyB,GAC/BA,EAAM,gBAAgB,GAGtBL,EAAS,MAAM;AAAA,QACb,SAAS;AAAA,QACT,YAAY;AAAA,UACV,SAAS,EAAC,MAAM,SAAS,UAAU,MAAM,MAAM,SAAQ;AAAA,QAAA;AAAA,MACzD,CACD;AAAA,IACH,GAEMQ,IAAkB,CAACH,MAAwB;AAC/C,MAAAA,EAAM,yBAAyB,GAC/BA,EAAM,gBAAgB,GAGtBL,EAAS,MAAM;AAAA,QACb,SAAS;AAAA,QACT,YAAY;AAAA,UACV,SAAS,EAAC,MAAM,SAAS,UAAU,MAAM,MAAM,SAAQ;AAAA,QAAA;AAAA,MACzD,CACD;AAAA,IACH;AAGQ,WAAAM,EAAA,iBAAiB,eAAeC,GAAmB,EAAI,GACvDD,EAAA,iBAAiB,aAAaE,GAAiB,EAAI,GAEpD,MAAM;AACH,MAAAF,EAAA,oBAAoB,eAAeC,GAAmB,EAAI,GAC1DD,EAAA,oBAAoB,aAAaE,GAAiB,EAAI;AAAA,IAChE;AAAA,EAAA,GACC,CAACZ,GAAiBI,CAAQ,CAAC,GAG5B,gBAAAS;AAAA,IAACC,EAAO;AAAA,IAAP;AAAA,MACC,KAAAZ;AAAA,MACA,kBAAe;AAAA,MACf,WAAU;AAAA,MACV,SAASF,IAAkBI,IAAW;AAAA,MACtC,UAAUJ,IAAkB,SAAY,EAAC,SAAS,IAAG;AAAA,MACrD,YAAY;AAAA,QACV,SAAS,EAAC,MAAM,SAAS,UAAU,MAAM,MAAM,SAAQ;AAAA,MACzD;AAAA,MACA,SAASQ;AAAA,MACT,OAAO;AAAA,QACL,aAAaR,IAAkB,iBAAiB;AAAA,MAClD;AAAA,MACC,GAAGO;AAAA,MAEH,UAAAT;AAAA,IAAA;AAAA,EACH;AAEJ;"}
|
|
@@ -12,10 +12,11 @@ module.exports = {
|
|
|
12
12
|
category: 'Best Practices',
|
|
13
13
|
recommended: true,
|
|
14
14
|
},
|
|
15
|
-
fixable: 'code',
|
|
16
15
|
messages: {
|
|
17
16
|
preferSdkComponent:
|
|
18
17
|
'Use <{{sdkComponent}}> from @shopify/shop-minis-react instead of <{{nativeElement}}>. The SDK component provides optimized performance, consistent styling, and additional features.',
|
|
18
|
+
preferButtonComponent:
|
|
19
|
+
'Use <Button> or <Touchable> from @shopify/shop-minis-react instead of <button>. These SDK components provide optimized performance, consistent styling, and additional features.',
|
|
19
20
|
},
|
|
20
21
|
schema: [
|
|
21
22
|
{
|
|
@@ -52,9 +53,6 @@ module.exports = {
|
|
|
52
53
|
...(options.components || {}),
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
// eslint-disable-next-line @shopify/prefer-module-scope-constants
|
|
56
|
-
const SDK_PACKAGE = '@shopify/shop-minis-react'
|
|
57
|
-
|
|
58
56
|
return {
|
|
59
57
|
JSXOpeningElement(node) {
|
|
60
58
|
const elementName = node.name.name
|
|
@@ -63,89 +61,22 @@ module.exports = {
|
|
|
63
61
|
if (componentMap[elementName]) {
|
|
64
62
|
const sdkComponent = componentMap[elementName]
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Fix opening tag
|
|
84
|
-
const openingTagStart = openingElement.name.range[0]
|
|
85
|
-
const openingTagEnd = openingElement.name.range[1]
|
|
86
|
-
fixes.push(
|
|
87
|
-
fixer.replaceTextRange(
|
|
88
|
-
[openingTagStart, openingTagEnd],
|
|
89
|
-
sdkComponent
|
|
90
|
-
)
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
// Fix closing tag if it exists (not self-closing)
|
|
94
|
-
if (closingElement) {
|
|
95
|
-
const closingTagStart = closingElement.name.range[0]
|
|
96
|
-
const closingTagEnd = closingElement.name.range[1]
|
|
97
|
-
fixes.push(
|
|
98
|
-
fixer.replaceTextRange(
|
|
99
|
-
[closingTagStart, closingTagEnd],
|
|
100
|
-
sdkComponent
|
|
101
|
-
)
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Add import if it doesn't exist
|
|
106
|
-
const program = sourceCode.ast
|
|
107
|
-
const imports = program.body.filter(
|
|
108
|
-
importNode => importNode.type === 'ImportDeclaration'
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
// Check if we already import from the SDK
|
|
112
|
-
const sdkImport = imports.find(
|
|
113
|
-
importNode => importNode.source.value === SDK_PACKAGE
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
if (sdkImport) {
|
|
117
|
-
// Check if this component is already imported
|
|
118
|
-
const hasComponent = sdkImport.specifiers.some(
|
|
119
|
-
spec =>
|
|
120
|
-
spec.type === 'ImportSpecifier' &&
|
|
121
|
-
spec.imported.name === sdkComponent
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
if (!hasComponent) {
|
|
125
|
-
// Add component to existing import
|
|
126
|
-
const lastSpecifier =
|
|
127
|
-
sdkImport.specifiers[sdkImport.specifiers.length - 1]
|
|
128
|
-
fixes.push(
|
|
129
|
-
fixer.insertTextAfter(lastSpecifier, `, ${sdkComponent}`)
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
// Add new import statement
|
|
134
|
-
const firstNode = program.body[0]
|
|
135
|
-
const importStatement = `import {${sdkComponent}} from '${SDK_PACKAGE}'\n`
|
|
136
|
-
|
|
137
|
-
if (firstNode) {
|
|
138
|
-
fixes.push(fixer.insertTextBefore(firstNode, importStatement))
|
|
139
|
-
} else {
|
|
140
|
-
fixes.push(
|
|
141
|
-
fixer.insertTextAfterRange([0, 0], importStatement)
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return fixes
|
|
147
|
-
},
|
|
148
|
-
})
|
|
64
|
+
// Use custom message for button element since it has multiple SDK options
|
|
65
|
+
if (elementName === 'button') {
|
|
66
|
+
context.report({
|
|
67
|
+
node,
|
|
68
|
+
messageId: 'preferButtonComponent',
|
|
69
|
+
})
|
|
70
|
+
} else {
|
|
71
|
+
context.report({
|
|
72
|
+
node,
|
|
73
|
+
messageId: 'preferSdkComponent',
|
|
74
|
+
data: {
|
|
75
|
+
nativeElement: elementName,
|
|
76
|
+
sdkComponent,
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
}
|
|
149
80
|
}
|
|
150
81
|
},
|
|
151
82
|
}
|
|
@@ -9,6 +9,14 @@ const path = require('path')
|
|
|
9
9
|
// Load the hook-scopes map
|
|
10
10
|
const hookScopesMap = require('../../generated-hook-maps/hook-scopes-map.json')
|
|
11
11
|
|
|
12
|
+
// Load the component-scopes map (may not exist yet)
|
|
13
|
+
let componentScopesMap = {}
|
|
14
|
+
try {
|
|
15
|
+
componentScopesMap = require('../../generated-hook-maps/component-scopes-map.json')
|
|
16
|
+
} catch (err) {
|
|
17
|
+
// Component scopes map doesn't exist yet, that's okay
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
// Module-level cache for manifest.json to avoid repeated file I/O
|
|
13
21
|
// ESLint reuses the same rule module across all files, so this cache
|
|
14
22
|
// persists for the entire lint run, dramatically improving performance
|
|
@@ -73,7 +81,7 @@ module.exports = {
|
|
|
73
81
|
fixable: 'code',
|
|
74
82
|
messages: {
|
|
75
83
|
missingScope:
|
|
76
|
-
'
|
|
84
|
+
'{{source}} requires scope "{{scope}}" in src/manifest.json. Add "{{scope}}" to the "scopes" array.',
|
|
77
85
|
missingPermission:
|
|
78
86
|
'{{reason}} requires permission "{{permission}}" in src/manifest.json. Add "{{permission}}" to the "permissions" array.',
|
|
79
87
|
missingTrustedDomain:
|
|
@@ -93,6 +101,7 @@ module.exports = {
|
|
|
93
101
|
let manifest = null
|
|
94
102
|
let manifestParseError = null
|
|
95
103
|
const usedHooks = new Set()
|
|
104
|
+
const usedComponents = new Set()
|
|
96
105
|
const requiredPermissions = new Set()
|
|
97
106
|
const requiredDomains = new Set()
|
|
98
107
|
const fixedIssues = new Set()
|
|
@@ -171,6 +180,35 @@ module.exports = {
|
|
|
171
180
|
})
|
|
172
181
|
})
|
|
173
182
|
}
|
|
183
|
+
|
|
184
|
+
// Check if it's a component that requires scopes
|
|
185
|
+
// We need to check all possible paths where the component might be defined
|
|
186
|
+
const possibleComponentPaths = Object.keys(
|
|
187
|
+
componentScopesMap
|
|
188
|
+
).filter(componentPath => {
|
|
189
|
+
// Extract the component name from the path (e.g., "commerce/add-to-cart" -> "AddToCartButton")
|
|
190
|
+
// We'll check if the import name matches common component naming patterns
|
|
191
|
+
const pathParts = componentPath.split('/')
|
|
192
|
+
const fileName = pathParts[pathParts.length - 1]
|
|
193
|
+
// Convert kebab-case or snake_case to PascalCase
|
|
194
|
+
const componentName = fileName
|
|
195
|
+
.split(/[-_]/)
|
|
196
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
197
|
+
.join('')
|
|
198
|
+
return (
|
|
199
|
+
componentName === importedName || fileName === importedName
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
if (possibleComponentPaths.length > 0) {
|
|
204
|
+
possibleComponentPaths.forEach(componentPath => {
|
|
205
|
+
usedComponents.add({
|
|
206
|
+
path: componentPath,
|
|
207
|
+
name: importedName,
|
|
208
|
+
node,
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
}
|
|
174
212
|
}
|
|
175
213
|
})
|
|
176
214
|
}
|
|
@@ -408,6 +446,7 @@ module.exports = {
|
|
|
408
446
|
if (
|
|
409
447
|
manifestParseError &&
|
|
410
448
|
(usedHooks.size > 0 ||
|
|
449
|
+
usedComponents.size > 0 ||
|
|
411
450
|
requiredPermissions.size > 0 ||
|
|
412
451
|
requiredDomains.size > 0)
|
|
413
452
|
) {
|
|
@@ -447,6 +486,47 @@ module.exports = {
|
|
|
447
486
|
})
|
|
448
487
|
})
|
|
449
488
|
|
|
489
|
+
// Check scopes for components
|
|
490
|
+
usedComponents.forEach(
|
|
491
|
+
({path: componentPath, name: componentName, node}) => {
|
|
492
|
+
const componentData = componentScopesMap[componentPath]
|
|
493
|
+
if (
|
|
494
|
+
!componentData ||
|
|
495
|
+
!componentData.scopes ||
|
|
496
|
+
componentData.scopes.length === 0
|
|
497
|
+
) {
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const requiredScopes = componentData.scopes
|
|
502
|
+
|
|
503
|
+
if (!manifest) {
|
|
504
|
+
issues.push({
|
|
505
|
+
messageId: 'manifestNotFound',
|
|
506
|
+
data: {
|
|
507
|
+
hookName: componentName,
|
|
508
|
+
scopes: requiredScopes.join(', '),
|
|
509
|
+
},
|
|
510
|
+
})
|
|
511
|
+
return
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const manifestScopes = manifest.scopes || []
|
|
515
|
+
|
|
516
|
+
requiredScopes.forEach(requiredScope => {
|
|
517
|
+
if (!manifestScopes.includes(requiredScope)) {
|
|
518
|
+
issues.push({
|
|
519
|
+
type: 'scope',
|
|
520
|
+
scope: requiredScope,
|
|
521
|
+
componentName,
|
|
522
|
+
componentPath,
|
|
523
|
+
node,
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
)
|
|
529
|
+
|
|
450
530
|
// Check permissions
|
|
451
531
|
requiredPermissions.forEach(({permission, reason, node}) => {
|
|
452
532
|
if (!manifest) {
|
|
@@ -513,11 +593,15 @@ module.exports = {
|
|
|
513
593
|
|
|
514
594
|
// Only report if not fixed
|
|
515
595
|
if (!fixedIssues.has(issueKey)) {
|
|
596
|
+
// Determine if this is from a hook or component
|
|
597
|
+
const sourceName = issue.hookName || issue.componentName
|
|
598
|
+
const sourceType = issue.hookName ? 'Hook' : 'Component'
|
|
599
|
+
|
|
516
600
|
context.report({
|
|
517
601
|
loc: {line: 1, column: 0},
|
|
518
602
|
messageId: 'missingScope',
|
|
519
603
|
data: {
|
|
520
|
-
|
|
604
|
+
source: `${sourceType} "${sourceName}"`,
|
|
521
605
|
scope: issue.scope,
|
|
522
606
|
},
|
|
523
607
|
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"commerce/product-card": {
|
|
3
|
+
"scopes": [
|
|
4
|
+
"product_list:write"
|
|
5
|
+
],
|
|
6
|
+
"hooks": [
|
|
7
|
+
"useSavedProductsActions"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"commerce/product-link": {
|
|
11
|
+
"scopes": [
|
|
12
|
+
"product_list:write"
|
|
13
|
+
],
|
|
14
|
+
"hooks": [
|
|
15
|
+
"useSavedProductsActions"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -38,8 +38,10 @@ export const Touchable = ({
|
|
|
38
38
|
|
|
39
39
|
// Animate to pressed state
|
|
40
40
|
controls.start({
|
|
41
|
-
scale: 0.95,
|
|
42
41
|
opacity: 0.7,
|
|
42
|
+
transition: {
|
|
43
|
+
opacity: {type: 'tween', duration: 0.08, ease: 'linear'},
|
|
44
|
+
},
|
|
43
45
|
})
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -49,8 +51,10 @@ export const Touchable = ({
|
|
|
49
51
|
|
|
50
52
|
// Animate back to normal state
|
|
51
53
|
controls.start({
|
|
52
|
-
scale: 1,
|
|
53
54
|
opacity: 1,
|
|
55
|
+
transition: {
|
|
56
|
+
opacity: {type: 'tween', duration: 0.08, ease: 'linear'},
|
|
57
|
+
},
|
|
54
58
|
})
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -70,9 +74,8 @@ export const Touchable = ({
|
|
|
70
74
|
data-touchable="true"
|
|
71
75
|
className="flex w-full"
|
|
72
76
|
animate={stopPropagation ? controls : undefined}
|
|
73
|
-
whileTap={stopPropagation ? undefined : {
|
|
77
|
+
whileTap={stopPropagation ? undefined : {opacity: 0.7}}
|
|
74
78
|
transition={{
|
|
75
|
-
scale: {type: 'tween', duration: 0.08, ease: 'linear'},
|
|
76
79
|
opacity: {type: 'tween', duration: 0.08, ease: 'linear'},
|
|
77
80
|
}}
|
|
78
81
|
onClick={handleClick}
|