-
Notifications
You must be signed in to change notification settings - Fork 530
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(answers): add
EXPERIMENTAL_answers
widget (#4581)
* feat(answers): add widget * add warning when findAnswers is not supported * hits fall back to an empty array if not exists * split into widget + connector + component set * fix concurrency issue + types * debounce render * update stories * fix types * add tests * update storybook * add comments * pass query parameters to findAnswers * disable lint error * update bundlesize * Revert "pass query parameters to findAnswers" This reverts commit 98529c2. * rename answers widget to EXPERIMENTAL_answers * remove validation of attributesForPrediction because it can be optional * remove unnecessary validation * add __position to hits * make queryLanguages required * add "EXPERIMENTAL_" * Update src/types/algoliasearch.ts Co-authored-by: Haroen Viaene <[email protected]> * clean up import & export * less typing for temporary part * Update src/connectors/answers/connectAnswers.ts Co-authored-by: Haroen Viaene <[email protected]> * add debounceTime * override x-algolia-agent via requestOptions * update FindAnswersResponse type * add test case for queryLanguages missing * add generic to FindAnswersResponse * escape answers hits and add queryID * clear hits when trigger a new search * Update src/connectors/answers/connectAnswers.ts Co-authored-by: Clément Vannicatte <[email protected]> * Update src/connectors/answers/__tests__/connectAnswers-test.ts Co-authored-by: Clément Vannicatte <[email protected]> * do not render list when loading * fix test cases to include objectID and __escaped * debounce search call * fix $$type and $$widgetType * add comment * change default render debounce time to 100ms * catch promise rejects inside the connector * Update src/components/Answers/Answers.tsx Co-authored-by: Clément Vannicatte <[email protected]> * clean up `wait` function * fix wrong closing tag * add types to debounce functions * fix eslint rule for EXPERIMENTAL_ * support extraParameters * remove comma * update types alphabetically * remove unused css * clean up debounce functions * update bundlesize * chore: release v4.13.0 (#4635) * chore: release v4.13.0 * remove unnecessary catch * remove this from debounce * Apply suggestions from code review Co-authored-by: François Chalifour <[email protected]> * fix types for js client v3 * add ts-ignore * remove the user agent workaround * update the types to use Partial * Update src/lib/utils/createConcurrentSafePromise.ts Co-authored-by: Clément Vannicatte <[email protected]> * update tests * fix types * update bundlesize * Update src/types/algoliasearch.ts Co-authored-by: Clément Vannicatte <[email protected]> * Update src/connectors/answers/connectAnswers.ts Co-authored-by: Haroen Viaene <[email protected]> * fix lint error * update bundlesize * update bundlesize Co-authored-by: Haroen Viaene <[email protected]> Co-authored-by: Clément Vannicatte <[email protected]> Co-authored-by: InstantSearch <[email protected]> Co-authored-by: François Chalifour <[email protected]>
- Loading branch information
1 parent
e7aaa8c
commit e4c9070
Showing
25 changed files
with
1,715 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
.my-Answers .ais-Answers-loader { | ||
display: none; | ||
} | ||
|
||
.my-Answers .ais-Answers-list { | ||
list-style: none; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
.my-Answers .ais-Answers-item { | ||
height: 10rem; | ||
border: 1px solid #ddd; | ||
border-radius: 0.5rem; | ||
} | ||
|
||
.my-Answers .title { | ||
padding: 0; | ||
margin: 1rem; | ||
font-size: 1.2rem; | ||
color: #333; | ||
line-height: 1.4rem; | ||
} | ||
|
||
.my-Answers .separator { | ||
border-top: 1px solid #ddd; | ||
} | ||
|
||
.my-Answers .description { | ||
margin: 1rem; | ||
padding: 0; | ||
color: #333; | ||
line-height: 1.4rem; | ||
} | ||
|
||
.my-Answers .description em { | ||
background-color: #ffc168; | ||
} | ||
|
||
.one-line { | ||
display: -webkit-box; | ||
-webkit-line-clamp: 1; | ||
-webkit-box-orient: vertical; | ||
overflow: hidden; | ||
} | ||
|
||
.three-lines { | ||
display: -webkit-box; | ||
-webkit-line-clamp: 3; | ||
-webkit-box-orient: vertical; | ||
overflow: hidden; | ||
} | ||
|
||
/* skeleton loader from https://codepen.io/jordanmsykes/pen/RgPqgV - begin */ | ||
@keyframes placeHolderShimmer { | ||
0% { | ||
-webkit-transform: translateZ(0); | ||
transform: translateZ(0); | ||
background-position: -468px 0; | ||
} | ||
to { | ||
-webkit-transform: translateZ(0); | ||
transform: translateZ(0); | ||
background-position: 468px 0; | ||
} | ||
} | ||
|
||
.card-skeleton { | ||
margin-left: 1rem; | ||
margin-right: 1rem; | ||
width: calc(100% - 2rem); | ||
height: 10rem; | ||
transition: all 0.3s ease-in-out; | ||
-webkit-backface-visibility: hidden; | ||
background: #fff; | ||
z-index: 10; | ||
opacity: 1; | ||
} | ||
|
||
.card-skeleton.hidden { | ||
transition: all 0.3s ease-in-out; | ||
opacity: 0; | ||
height: 0; | ||
padding: 0; | ||
} | ||
|
||
.card-skeleton-img { | ||
width: 100%; | ||
height: 120px; | ||
background: #e6e6e6; | ||
display: block; | ||
} | ||
|
||
.animated-background { | ||
will-change: transform; | ||
animation: placeHolderShimmer 1s linear infinite forwards; | ||
-webkit-backface-visibility: hidden; | ||
background: #e6e6e6; | ||
background: linear-gradient(90deg, #eee 8%, #ddd 18%, #eee 33%); | ||
background-size: 800px 104px; | ||
height: 100%; | ||
position: relative; | ||
} | ||
|
||
.skel-mask-container { | ||
position: relative; | ||
} | ||
|
||
.skel-mask { | ||
background: #fff; | ||
position: absolute; | ||
z-index: 200; | ||
} | ||
|
||
.skel-mask-1 { | ||
width: 100%; | ||
height: 15px; | ||
top: 0; | ||
} | ||
|
||
.skel-mask-2 { | ||
width: 100%; | ||
height: 25px; | ||
top: 45px; | ||
} | ||
|
||
.skel-mask-3 { | ||
width: 100%; | ||
height: 15px; | ||
top: 145px; | ||
} | ||
/* skeleton loader - end */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/** @jsx h */ | ||
|
||
import { h } from 'preact'; | ||
import cx from 'classnames'; | ||
import Template from '../Template/Template'; | ||
import { AnswersTemplates } from '../../widgets/answers/answers'; | ||
import { Hits } from '../../types'; | ||
|
||
type AnswersCSSClasses = { | ||
root: string; | ||
emptyRoot: string; | ||
header: string; | ||
loader: string; | ||
list: string; | ||
item: string; | ||
}; | ||
|
||
export type AnswersProps = { | ||
hits: Hits; | ||
isLoading: boolean; | ||
cssClasses: AnswersCSSClasses; | ||
templateProps: { | ||
[key: string]: any; | ||
templates: AnswersTemplates; | ||
}; | ||
}; | ||
|
||
const Answers = ({ | ||
hits, | ||
isLoading, | ||
cssClasses, | ||
templateProps, | ||
}: AnswersProps) => ( | ||
<div | ||
className={cx(cssClasses.root, { | ||
[cssClasses.emptyRoot]: hits.length === 0, | ||
})} | ||
> | ||
<Template | ||
{...templateProps} | ||
templateKey="header" | ||
rootProps={{ className: cssClasses.header }} | ||
data={{ | ||
hits, | ||
isLoading, | ||
}} | ||
/> | ||
{isLoading ? ( | ||
<Template | ||
{...templateProps} | ||
templateKey="loader" | ||
rootProps={{ className: cssClasses.loader }} | ||
/> | ||
) : ( | ||
<ul className={cssClasses.list}> | ||
{hits.map((hit, position) => ( | ||
<Template | ||
{...templateProps} | ||
templateKey="item" | ||
rootTagName="li" | ||
rootProps={{ className: cssClasses.item }} | ||
key={hit.objectID} | ||
data={{ | ||
...hit, | ||
__hitIndex: position, | ||
}} | ||
/> | ||
))} | ||
</ul> | ||
)} | ||
</div> | ||
); | ||
|
||
export default Answers; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/** @jsx h */ | ||
|
||
import { h } from 'preact'; | ||
import { render } from '@testing-library/preact'; | ||
import Answers, { AnswersProps } from '../Answers'; | ||
|
||
const defaultProps: AnswersProps = { | ||
hits: [], | ||
isLoading: false, | ||
cssClasses: { | ||
root: 'root', | ||
header: 'header', | ||
emptyRoot: 'empty', | ||
loader: 'loader', | ||
list: 'list', | ||
item: 'item', | ||
}, | ||
templateProps: { | ||
templates: { | ||
header: 'header', | ||
loader: 'loader', | ||
item: 'item', | ||
}, | ||
}, | ||
}; | ||
|
||
describe('Answers', () => { | ||
describe('Rendering', () => { | ||
it('renders without anything', () => { | ||
const { container } = render(<Answers {...defaultProps} />); | ||
expect(container.querySelector('.root')).toHaveClass('empty'); | ||
expect(container).toMatchInlineSnapshot(` | ||
<div> | ||
<div | ||
class="root empty" | ||
> | ||
<div | ||
class="header" | ||
> | ||
header | ||
</div> | ||
<ul | ||
class="list" | ||
/> | ||
</div> | ||
</div> | ||
`); | ||
}); | ||
|
||
it('renders the loader', () => { | ||
const { container } = render( | ||
<Answers {...defaultProps} isLoading={true} /> | ||
); | ||
expect(container).toMatchInlineSnapshot(` | ||
<div> | ||
<div | ||
class="root empty" | ||
> | ||
<div | ||
class="header" | ||
> | ||
header | ||
</div> | ||
<div | ||
class="loader" | ||
> | ||
loader | ||
</div> | ||
</div> | ||
</div> | ||
`); | ||
}); | ||
|
||
it('renders the header with data', () => { | ||
const props: AnswersProps = { | ||
...defaultProps, | ||
templateProps: { | ||
templates: { | ||
...defaultProps.templateProps.templates, | ||
header: ({ hits, isLoading }) => { | ||
return `${hits.length} answer(s) ${ | ||
isLoading ? 'loading' : 'loaded' | ||
}`; | ||
}, | ||
}, | ||
}, | ||
}; | ||
const { container } = render( | ||
<Answers | ||
{...props} | ||
isLoading={false} | ||
hits={[{ objectID: '1', __position: 1 }]} | ||
/> | ||
); | ||
expect(container.querySelector('.header')).toHaveTextContent( | ||
'1 answer(s) loaded' | ||
); | ||
}); | ||
|
||
it('renders the answers', () => { | ||
const props: AnswersProps = { | ||
...defaultProps, | ||
templateProps: { | ||
templates: { | ||
...defaultProps.templateProps.templates, | ||
item: hit => { | ||
return `answer: ${hit.title}`; | ||
}, | ||
}, | ||
}, | ||
}; | ||
const { container } = render( | ||
<Answers | ||
{...props} | ||
isLoading={false} | ||
hits={[{ objectID: '1', title: 'hello!', __position: 1 }]} | ||
/> | ||
); | ||
expect(container.querySelector('.list')).toHaveTextContent( | ||
'answer: hello!' | ||
); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.