Skip to content

Commit 55d550e

Browse files
dhayabsarahdayanHaroenv
authored
feat(ui-components): migrate Hits component (#6042)
--------- Co-authored-by: Sarah Dayan <[email protected]> Co-authored-by: Haroen Viaene <[email protected]>
1 parent 66ac853 commit 55d550e

File tree

24 files changed

+676
-884
lines changed

24 files changed

+676
-884
lines changed

bundlesize.config.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,27 @@
1010
},
1111
{
1212
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
13-
"maxSize": "76.25 kB"
13+
"maxSize": "76.5 kB"
1414
},
1515
{
1616
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
17-
"maxSize": "167.75 kB"
17+
"maxSize": "168.5 kB"
1818
},
1919
{
2020
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
2121
"maxSize": "46.25 kB"
2222
},
2323
{
2424
"path": "packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js",
25-
"maxSize": "57.75 kB"
25+
"maxSize": "58 kB"
2626
},
2727
{
2828
"path": "packages/vue-instantsearch/vue2/umd/index.js",
29-
"maxSize": "64.5 kB"
29+
"maxSize": "65.25 kB"
3030
},
3131
{
3232
"path": "packages/vue-instantsearch/vue3/umd/index.js",
33-
"maxSize": "65.25 kB"
33+
"maxSize": "65.75 kB"
3434
},
3535
{
3636
"path": "packages/vue-instantsearch/vue2/cjs/index.js",
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
import ts from 'typescript';
5+
6+
/**
7+
* Simple linting using the TypeScript compiler API.
8+
*/
9+
function delint(sourceFile: ts.SourceFile) {
10+
const errors: Array<{
11+
file: string;
12+
line: number;
13+
character: number;
14+
message: string;
15+
}> = [];
16+
17+
delintNode(sourceFile);
18+
19+
function delintNode(node: ts.Node) {
20+
switch (node.kind) {
21+
case ts.SyntaxKind.FunctionDeclaration: {
22+
const functionDeclaration = node as ts.FunctionDeclaration;
23+
const fileNameSegment = sourceFile.fileName.replace('.d.ts', '');
24+
const componentName = `create${fileNameSegment}Component`;
25+
if (
26+
functionDeclaration.modifiers?.some(
27+
(modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword
28+
) &&
29+
functionDeclaration.modifiers?.some(
30+
(modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword
31+
)
32+
) {
33+
const actualName = functionDeclaration.name?.getText();
34+
if (actualName !== componentName) {
35+
report(
36+
node,
37+
`Exported component should be named '${componentName}', but was '${actualName}' instead.`
38+
);
39+
}
40+
41+
const returnType = functionDeclaration.type as ts.FunctionTypeNode;
42+
43+
if (returnType.kind !== ts.SyntaxKind.FunctionType) {
44+
report(
45+
node,
46+
`Exported component's return type should be a function.`
47+
);
48+
}
49+
50+
if (
51+
returnType.kind === ts.SyntaxKind.FunctionType &&
52+
returnType.parameters.length !== 1
53+
) {
54+
report(
55+
node,
56+
`Exported component's return type should have exactly one parameter`
57+
);
58+
}
59+
60+
if (
61+
functionDeclaration.type?.kind === ts.SyntaxKind.FunctionType &&
62+
(
63+
functionDeclaration.type as ts.FunctionTypeNode
64+
).parameters[0].name.getText() !== 'userProps'
65+
) {
66+
report(
67+
node,
68+
`Exported component's return type should be called 'userProps'.`
69+
);
70+
}
71+
}
72+
73+
break;
74+
}
75+
default: {
76+
break;
77+
}
78+
}
79+
80+
ts.forEachChild(node, delintNode);
81+
}
82+
83+
function report(node: ts.Node, message: string) {
84+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
85+
node.getStart()
86+
);
87+
errors.push({
88+
file: sourceFile.fileName,
89+
line: line + 1,
90+
character: character + 1,
91+
message,
92+
});
93+
}
94+
95+
return errors;
96+
}
97+
98+
const files = fs
99+
.readdirSync(path.join(__dirname, '../dist/es/components'))
100+
.filter((file) => file.endsWith('.d.ts') && !file.startsWith('index'));
101+
102+
describe('Exposes correct types', () => {
103+
describe.each(files)('%s', (file) => {
104+
it('has no errors', () => {
105+
const setParentNodes = true;
106+
const sourceFile = ts.createSourceFile(
107+
file,
108+
fs
109+
.readFileSync(path.join(__dirname, '../dist/es/components', file))
110+
.toString(),
111+
ts.ScriptTarget.ES2015,
112+
setParentNodes
113+
);
114+
115+
expect(delint(sourceFile)).toEqual([]);
116+
});
117+
});
118+
});

packages/instantsearch-ui-components/src/components/Highlight.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ export function createHighlightComponent({
8080
});
8181

8282
return function Highlight(userProps: HighlightProps) {
83-
// Not destructured in function signature, to make sure it's not exposed in
84-
// the type definition.
8583
const {
8684
parts,
8785
highlightedTagName = 'mark',
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/** @jsx createElement */
2+
import { cx } from '../lib';
3+
4+
import type { ComponentProps, Renderer } from '../types';
5+
6+
// Should be imported from a shared package in the future
7+
type Hit = Record<string, unknown> & {
8+
objectID: string;
9+
};
10+
type SendEventForHits = (...props: unknown[]) => void;
11+
12+
export type HitsProps<THit> = ComponentProps<'div'> & {
13+
hits: THit[];
14+
itemComponent: (props: {
15+
hit: THit;
16+
index: number;
17+
className: string;
18+
onClick: () => void;
19+
onAuxClick: () => void;
20+
}) => JSX.Element;
21+
sendEvent: SendEventForHits;
22+
classNames?: Partial<HitsClassNames>;
23+
emptyComponent?: (props: { className: string }) => JSX.Element;
24+
};
25+
26+
export type HitsClassNames = {
27+
/**
28+
* Class names to apply to the root element
29+
*/
30+
root: string | string[];
31+
/**
32+
* Class names to apply to the root element without results
33+
*/
34+
emptyRoot: string | string[];
35+
/**
36+
* Class names to apply to the list element
37+
*/
38+
list: string | string[];
39+
/**
40+
* Class names to apply to each item element
41+
*/
42+
item: string | string[];
43+
};
44+
45+
export function createHitsComponent({ createElement }: Renderer) {
46+
return function Hits<THit extends Hit>(userProps: HitsProps<THit>) {
47+
const {
48+
classNames = {},
49+
hits,
50+
itemComponent: ItemComponent,
51+
sendEvent,
52+
emptyComponent: EmptyComponent,
53+
...props
54+
} = userProps;
55+
56+
if (hits.length === 0 && EmptyComponent) {
57+
return (
58+
<EmptyComponent
59+
className={cx(
60+
'ais-Hits',
61+
classNames.root,
62+
cx('ais-Hits--empty', classNames.emptyRoot),
63+
props.className
64+
)}
65+
/>
66+
);
67+
}
68+
return (
69+
<div
70+
{...props}
71+
className={cx(
72+
'ais-Hits',
73+
classNames.root,
74+
hits.length === 0 && cx('ais-Hits--empty', classNames.emptyRoot),
75+
props.className
76+
)}
77+
>
78+
<ol className={cx('ais-Hits-list', classNames.list)}>
79+
{hits.map((hit, index) => (
80+
<ItemComponent
81+
key={hit.objectID}
82+
hit={hit}
83+
index={index}
84+
className={cx('ais-Hits-item', classNames.item)}
85+
onClick={() => {
86+
sendEvent('click:internal', hit, 'Hit Clicked');
87+
}}
88+
onAuxClick={() => {
89+
sendEvent('click:internal', hit, 'Hit Clicked');
90+
}}
91+
/>
92+
))}
93+
</ol>
94+
</div>
95+
);
96+
};
97+
}

0 commit comments

Comments
 (0)