-
Notifications
You must be signed in to change notification settings - Fork 530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
poc: common JSX components #5952
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
{ | ||
"name": "instantsearch-jsx", | ||
"version": "1.0.0", | ||
"description": "Common JSX components for InstantSearch flavors", | ||
"source": "src/index.ts", | ||
"types": "dist/es/index.d.ts", | ||
"main": "dist/cjs/index.js", | ||
"module": "dist/es/index.js", | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"import": "./dist/es/index.js", | ||
"require": "./dist/cjs/index.js" | ||
} | ||
}, | ||
"sideEffects": false, | ||
"license": "MIT", | ||
"homepage": "https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/react/", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/algolia/instantsearch" | ||
}, | ||
"author": { | ||
"name": "Algolia, Inc.", | ||
"url": "https://www.algolia.com" | ||
}, | ||
"keywords": [ | ||
"algolia", | ||
"components", | ||
"fast", | ||
"instantsearch", | ||
"react", | ||
"search" | ||
], | ||
"files": [ | ||
"README.md", | ||
"dist" | ||
], | ||
"scripts": { | ||
"clean": "rm -rf dist", | ||
"watch": "yarn build:cjs --watch", | ||
"build": "yarn build:cjs && yarn build:es && yarn build:types", | ||
"build:cjs": "BABEL_ENV=cjs babel src --root-mode upward --extensions '.js,.ts,.tsx' --out-dir dist/cjs --ignore '**/__tests__/**/*','**/__mocks__/**/*' --quiet && ../../scripts/prepare-cjs.sh", | ||
"build:es": "BABEL_ENV=es babel src --root-mode upward --extensions '.js,.ts,.tsx' --out-dir dist/es --ignore '**/__tests__/**/*','**/__mocks__/**/*' --quiet", | ||
"build:types": "tsc -p ./tsconfig.declaration.json --outDir ./dist/es", | ||
"test:exports": "node ./test/module/is-es-module.mjs && node ./test/module/is-cjs-module.cjs", | ||
"version": "./scripts/version.cjs" | ||
}, | ||
"dependencies": { | ||
"@babel/runtime": "^7.1.2" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/* eslint-disable no-nested-ternary */ | ||
/** @jsx createElement */ | ||
|
||
export type HitsClassNames = { | ||
/** | ||
* Class names to apply to the root element | ||
*/ | ||
root: string; | ||
/** | ||
* Class names to apply to the root element without results | ||
*/ | ||
emptyRoot: string; | ||
/** | ||
* Class names to apply to the list element | ||
*/ | ||
list: string; | ||
/** | ||
* Class names to apply to each item element | ||
*/ | ||
item: string; | ||
}; | ||
|
||
type BaseHit = { | ||
objectID: string; | ||
}; | ||
|
||
export type HitsProps<T extends BaseHit> = { | ||
hitComponent: any; | ||
hitSlot?: any; | ||
hits: T[]; | ||
className?: string; | ||
classNames?: Partial<HitsClassNames>; | ||
sendEvent: (eventName: string, hit: T, event: string) => void; | ||
}; | ||
|
||
export function cx( | ||
...classNames: Array<string | number | boolean | undefined | null> | ||
) { | ||
return classNames.filter(Boolean).join(' '); | ||
} | ||
|
||
export function createHits({ createElement }: any) { | ||
function DefaultHitComponent({ hit }: { hit: BaseHit }) { | ||
return ( | ||
<div style={{ wordBreak: 'break-all' }}> | ||
{JSON.stringify(hit).slice(0, 100)}… | ||
</div> | ||
); | ||
} | ||
|
||
return function Hits<T extends BaseHit>({ | ||
hitComponent: HitComponent, | ||
hitSlot, | ||
classNames = {}, | ||
hits, | ||
sendEvent, | ||
...props | ||
}: HitsProps<T>) { | ||
return ( | ||
<div | ||
{...props} | ||
className={cx( | ||
'ais-Hits', | ||
classNames.root, | ||
hits.length === 0 && cx('ais-Hits--empty', classNames.emptyRoot), | ||
props.className | ||
)} | ||
> | ||
<ol className={cx('ais-Hits-list', classNames.list)}> | ||
{hits.map((hit) => ( | ||
<li | ||
key={hit.objectID} | ||
className={cx('ais-Hits-item', classNames.item)} | ||
onClick={() => { | ||
sendEvent('click:internal', hit, 'Hit Clicked'); | ||
}} | ||
onAuxClick={() => { | ||
sendEvent('click:internal', hit, 'Hit Clicked'); | ||
}} | ||
> | ||
{HitComponent ? ( | ||
<HitComponent hit={hit} sendEvent={sendEvent} /> | ||
) : hitSlot ? ( | ||
hitSlot({ item: hit }) | ||
) : ( | ||
<DefaultHitComponent hit={hit} /> | ||
)} | ||
</li> | ||
))} | ||
</ol> | ||
</div> | ||
); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './Hits'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../tsconfig.declaration" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,80 +1,6 @@ | ||
import React from 'react'; | ||
import { createHits } from 'instantsearch-jsx'; | ||
import { createElement } from 'react'; | ||
|
||
import { cx } from './lib/cx'; | ||
|
||
import type { Hit } from 'instantsearch.js'; | ||
import type { SendEventForHits } from 'instantsearch.js/es/lib/utils'; | ||
|
||
export type HitsProps<THit> = React.ComponentProps<'div'> & { | ||
hits: THit[]; | ||
sendEvent: SendEventForHits; | ||
hitComponent?: React.JSXElementConstructor<{ | ||
hit: THit; | ||
sendEvent: SendEventForHits; | ||
}>; | ||
classNames?: Partial<HitsClassNames>; | ||
}; | ||
|
||
function DefaultHitComponent({ hit }: { hit: Hit }) { | ||
return ( | ||
<div style={{ wordBreak: 'break-all' }}> | ||
{JSON.stringify(hit).slice(0, 100)}… | ||
</div> | ||
); | ||
} | ||
|
||
export type HitsClassNames = { | ||
/** | ||
* Class names to apply to the root element | ||
*/ | ||
root: string; | ||
/** | ||
* Class names to apply to the root element without results | ||
*/ | ||
emptyRoot: string; | ||
/** | ||
* Class names to apply to the list element | ||
*/ | ||
list: string; | ||
/** | ||
* Class names to apply to each item element | ||
*/ | ||
item: string; | ||
}; | ||
|
||
export function Hits<THit extends Hit>({ | ||
hits, | ||
sendEvent, | ||
hitComponent: HitComponent = DefaultHitComponent, | ||
classNames = {}, | ||
...props | ||
}: HitsProps<THit>) { | ||
return ( | ||
<div | ||
{...props} | ||
className={cx( | ||
'ais-Hits', | ||
classNames.root, | ||
hits.length === 0 && cx('ais-Hits--empty', classNames.emptyRoot), | ||
props.className | ||
)} | ||
> | ||
<ol className={cx('ais-Hits-list', classNames.list)}> | ||
{hits.map((hit) => ( | ||
<li | ||
key={hit.objectID} | ||
className={cx('ais-Hits-item', classNames.item)} | ||
onClick={() => { | ||
sendEvent('click:internal', hit, 'Hit Clicked'); | ||
}} | ||
onAuxClick={() => { | ||
sendEvent('click:internal', hit, 'Hit Clicked'); | ||
}} | ||
> | ||
<HitComponent hit={hit} sendEvent={sendEvent} /> | ||
</li> | ||
))} | ||
</ol> | ||
</div> | ||
); | ||
} | ||
export const Hits = createHits({ | ||
createElement, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { createHits } from 'instantsearch-jsx'; | ||
import { connectHitsWithInsights } from 'instantsearch.js/es/connectors'; | ||
|
||
import { createSuitMixin } from '../mixins/suit'; | ||
import { createWidgetMixin } from '../mixins/widget'; | ||
import { renderCompat } from '../util/vue-compat'; | ||
|
||
const augmentH = (baseH) => (tag, propsWithClassName, children) => { | ||
const { className, ...props } = propsWithClassName; | ||
return baseH(tag, Object.assign(props, { class: className }), [children]); | ||
}; | ||
|
||
export default { | ||
name: 'AisHits', | ||
mixins: [ | ||
createWidgetMixin( | ||
{ | ||
connector: connectHitsWithInsights, | ||
}, | ||
{ | ||
$$widgetType: 'ais.hits', | ||
} | ||
), | ||
createSuitMixin({ name: 'Hits' }), | ||
], | ||
props: { | ||
escapeHTML: { | ||
type: Boolean, | ||
default: true, | ||
}, | ||
transformItems: { | ||
type: Function, | ||
default: undefined, | ||
}, | ||
}, | ||
computed: { | ||
items() { | ||
return this.state.hits; | ||
}, | ||
widgetParams() { | ||
return { | ||
escapeHTML: this.escapeHTML, | ||
transformItems: this.transformItems, | ||
}; | ||
}, | ||
}, | ||
render: renderCompat(function (baseH) { | ||
if (!this.state) return null; | ||
|
||
const h = augmentH(baseH); | ||
|
||
return createHits({ createElement: h })({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how can it be externalised? if I remember it correctly in vue 2 you only have access to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Considering Vue 2 will soon reach EOL, we might want to only support Vue 3 so this wouldn't be a problem. |
||
hits: this.state.hits, | ||
hitSlot: this.$scopedSlots.item, | ||
}); | ||
}), | ||
}; |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to map
className
toclass
and children has to be an array for Vue'sh
function (they don't have fragments)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vue 2 and 3 are quite different, we have renderCompat, but I wonder if we should just have separate files for vue 2 and vue 3 once we simplify the rendering?