import { Meta } from "@storybook/addon-docs"
lorem...
//๐ import axe from jest-axe | |
import { axe } from "jest-axe"; | |
//๐ add to you test file to enable the testing playground link output in the console | |
screen.logTestingPlaygroundURL(); | |
//๐ add to check a11y violations in your test | |
const component = renderedComponent.getByRole("component"); | |
expect(await axe(note)).toHaveNoViolations(); | |
// or use the container key **preferred** | |
expect(await axe(renderOverview.container)).toHaveNoViolations(); |
// babel.config.js | |
module.exports = function (api) { | |
api.cache(true) | |
const presets = ['@babel/preset-env', '@babel/preset-react'] | |
const plugins = ['macros'] | |
return { | |
presets, | |
plugins | |
} | |
} |
import { StoryObj, Meta } from "@storybook/react"; | |
import { within, userEvent } from "@storybook/testing-library"; | |
import { expect } from "@storybook/jest"; | |
import COMPONENT_NAME from "./component"; | |
const meta: Meta<typeof COMPONENT_NAME> = { | |
title: "IH Components/COMPONENT_NAME", | |
component: COMPONENT_NAME, | |
}; | |
export default meta; | |
type Story = StoryObj<typeof COMPONENT_NAME>; | |
export const COMPONENT: Story = { | |
args: {}, | |
}; |
import { render, screen } from "@testing-library/react"; | |
import { axe } from "jest-axe"; | |
import React from "react"; | |
// ๐ import your component | |
import { COMPONENT_NAME } from "@/components/marketplace/ats-capture"; | |
import { createWrapper } from "@/test-utils"; | |
describe("<COMPONENT_NAME />", () => { | |
it("should render", async () => { | |
const renderComponent = render(<COMPONENT_NAME />, { | |
wrapper: createWrapper(), | |
}); | |
expect(renderComponent).toMatchSnapshot(); | |
expect(await axe(renderComponent.container)).toHaveNoViolations(); | |
screen.logTestingPlaygroundURL(); | |
}); | |
}); |
{ | |
"commands": { | |
"code-review": { | |
"prompt": "Review the code in selected file and identify issues such as bad practices, incorrect semantic markup, accessibility issues, security, type issues, lint issues, color contrast issues, un-optimized code ect. Write concise, developer friendly, and informational list of recommendations with code examples whenever possible on how to improve, fix or refactor the code. If no issues are found, state that the code looks good and give the developer positive feedback.", | |
"context": { | |
"codebase": false, | |
"selection": true | |
}, | |
"description": "Get an AI assisted code review of you selected file" | |
}, | |
"doc-comments": { | |
"description": "doc-comments", | |
"prompt": "Generate documentation comments for the chosen code snippet. Ensure that the comments adhere to the doc-comments format and exclude any type inference. The output should solely consist of the comments, without including any of the selected code.", | |
"context": { | |
"selection": true | |
}, | |
"mode": "insert" | |
}, | |
"typedoc-comments": { | |
"description": "typedoc-comments", | |
"prompt": "Write typedoc comments for the selected code. The comments should be in the form of JSDoc comments. Do not output any of the selected code, just the comments.", | |
"context": { | |
"selection": true | |
}, | |
"mode": "insert" | |
}, | |
"inline-docs": { | |
"description": "inline-docs", | |
"prompt": "Add an inline doc comment for each property or type. The comments should describe the purpose, if its optional or required when necessary and expected values, do not output <selected> tag", | |
"context": { | |
"selection": true | |
}, | |
"mode": "replace" | |
} | |
} | |
} |
<ComboSearchElement | |
key={result.item.optionId} | |
ref={(el) => (itemsRef.current[index] = el)} | |
handleButtonClick={(e: React.MouseEvent) => { | |
// @ts-ignore | |
setSearchTerm(e.currentTarget.value); | |
handleButtonClickEvent(e); | |
setActiveIndex(index); | |
}} | |
displayName={result.item.displayName} | |
> | |
{result.item.displayName} | |
</ComboSearchElement>; |
import { render, screen } from '@testing-library/react'; | |
import React from 'react'; | |
import { COMPONENT } from '@/components/shared/integration-hub/COMPONENT'; | |
import { createWrapper } from '@/test-utils'; | |
describe('<COMPONENT />', () => { | |
it('should render', () => { | |
const renderComponent = render( | |
<COMPONENT />, | |
{ | |
wrapper: createWrapper() | |
} | |
); | |
expect(screen.getByText('1')).toBeInTheDocument(); | |
expect(renderComponent).toMatchSnapshot(); | |
}); | |
}); |
import React from "react" | |
import { | |
ComponentStory, | |
ComponentMeta | |
} from "@storybook/react" | |
import { BADGE } from "@geometricpanda/storybook-addon-badges" | |
import { | |
within, | |
userEvent, | |
waitFor | |
} from "@storybook/testing-library" | |
import { expect } from "@storybook/jest" | |
import ComponentName from "./component-name" | |
export default { | |
title: "Elements/ComponentName", | |
component: ComponentName, | |
argTypes: { | |
children: { control: "text" }, | |
// type: { | |
// control: "select", | |
// options: ["ComponentName", "submit", "reset"] | |
// }, | |
// onPointerDown: { action: 'down' } | |
}, | |
parameters: { | |
badges: [BADGE.BETA], | |
docs: { | |
description: { | |
component: "Component description", | |
}, | |
} | |
} | |
} as ComponentMeta<typeof ComponentName> | |
const Template: ComponentStory<typeof ComponentName> = (args) => ( | |
<ComponentName {...args}>{args.children}</ComponentName> | |
) | |
/** | |
* | |
*/ | |
export const DefaultComponentName = Template.bind({}) | |
DefaultComponentName.args = { | |
children: "Default ComponentName", | |
} | |
DefaultComponentName.play = async ({ args, canvasElement }) => { | |
const { getByRole } = within(canvasElement) | |
const ComponentName = getByRole("note") | |
expect(ComponentName).toBeInTheDocument() | |
} | |
DefaultComponentName.parameters = { | |
docs: { | |
description: { | |
story: 'Some story **markdown**', | |
}, | |
}, | |
} |
/* eslint-disable @typescript-eslint/no-unused-vars */ | |
import { expect } from "@storybook/jest"; | |
import { StoryObj, Meta } from "@storybook/react"; | |
import { within, userEvent } from "@storybook/testing-library"; | |
// ๐ import the component you want to test | |
import { CustomModal } from "@/components/shared/modal"; | |
const meta: Meta<typeof CustomModal> = { | |
title: "IH Components/Modals/CustomModal", | |
component: CustomModal, | |
}; | |
export default meta; | |
type Story = StoryObj<typeof CustomModal>; | |
export const COMPONENT: Story = { | |
args: {}, | |
}; |
function HeadingLink({ | |
tag: Tag, | |
children, | |
id, | |
...props | |
}: ComponentProps<'h2'> & { tag: `h${2 | 3 | 4 | 5 | 6}` }): ReactElement { | |
return ( | |
<Tag className={`subheading-${Tag}`} {...props}> | |
{children} | |
<span className="nx-absolute -nx-mt-7" id={id} /> | |
<a | |
href={id && `#${id}`} | |
className="subheading-anchor" | |
aria-label="Permalink for this section" | |
/> | |
</Tag> | |
) | |
} |
import Button from './Buttons/Button' | |
export { Button } |
<form name={`integration-config-step`}> | |
{configSection?.sections?.map((section, index) => ( | |
<ComposedFlex direction={`column`} variant={`slim`} key={index}> | |
{index === 0 ? ( | |
<> | |
<ContextAwareFieldList | |
linkId={linkId} | |
fields={fieldsSections(section)} | |
requiredIfUpdated={true} | |
/> | |
<Divider lineWeight="sm" /> | |
</> | |
) : ( | |
<ContextAwareFieldList | |
linkId={linkId} | |
fields={fieldsSections(section)} | |
requiredIfUpdated={true} | |
/> | |
)} | |
</ComposedFlex> | |
))} | |
</form> |
<form name={`integration-config-step`}> | |
{configSection?.sections?.flatMap((section, index) => ( | |
<ComposedFlex direction={`column`} variant={`slim`} key={index}> | |
<ContextAwareFieldList | |
linkId={linkId} | |
fields={fieldsSections(section)} | |
requiredIfUpdated={true} | |
/> | |
<Divider lineWeight="sm" /> | |
</ComposedFlex> | |
)).slice(0, -1)} | |
</form> |
export const Default = (): JSX.Element => { | |
const list = []; | |
for (let i = 0; i < 5; i++) { | |
list.push(<IntegrationDetailsSkeleton key={i} />); | |
} | |
return <Box>{list}</Box>; | |
}; |
import React from 'react'; | |
import { render, RenderResult, screen } from '@testing-library/react'; | |
import createWrapper from '../../../testing/createWrapper'; | |
import HeaderContainer from '..'; | |
import { UserData } from '../../MPBodyContainer/CandidateComponents/utlities'; | |
describe('<HeaderContainer />', () => { | |
const user = { | |
id: 1, | |
first_name: 'Cash', | |
edit: true | |
}; | |
const component = (userData?: UserData): RenderResult => { | |
return render(<HeaderContainer header={userData} callBack={() => {}} />, { | |
wrapper: createWrapper() | |
}); | |
}; | |
it('should show indeed logo and profile and settings icon', () => { | |
component(); | |
expect(screen.getByRole('img', { name: 'Indeed logo' })).toBeInTheDocument(); | |
expect(screen.getByRole('img', { name: 'Profile icon' })).toBeInTheDocument(); | |
expect(screen.getByRole('img', { name: 'Settings icon' })).toBeInTheDocument(); | |
screen.debug(); | |
}); | |
it('should show User name and edit and settings icons', () => { | |
component(user); | |
expect(screen.getByText(user.first_name)).toBeInTheDocument(); | |
expect(screen.getByRole('img', { name: 'Edit icon' })).toBeInTheDocument(); | |
expect(screen.getByRole('img', { name: 'Trash icon' })).toBeInTheDocument(); | |
screen.debug(); | |
}); | |
}); |
import { useMultipleSelection, useCombobox } from 'downshift' | |
import React from 'react' | |
import { data } from './data' | |
import './index.css' | |
export type options = { | |
displayName: string | |
optionId: string | |
} | |
export const MultipleCombo = ( | |
options: options[] = data.atsSelfReportOptions, | |
) => { | |
const { atsSelfReportOptions } = data | |
const initialSelectedItems = [ | |
atsSelfReportOptions[0], | |
atsSelfReportOptions[1], | |
] | |
function getFilteredBooks(selectedItems: any, inputValue: string) { | |
const lowerCasedInputValue = inputValue.toLowerCase() | |
return atsSelfReportOptions.filter(function filterBook(ats) { | |
// console.log({ selectedItems }) | |
return ( | |
!selectedItems.includes(ats) && | |
ats.displayName.toLowerCase().includes(lowerCasedInputValue) | |
) | |
}) | |
} | |
function MultipleComboBox() { | |
const [inputValue, setInputValue] = React.useState('') | |
const [selectedItems, setSelectedItems] = | |
React.useState(initialSelectedItems) | |
const items = React.useMemo( | |
() => getFilteredBooks(selectedItems, inputValue), | |
[selectedItems, inputValue], | |
) | |
console.log({ items }) | |
const { getSelectedItemProps, getDropdownProps, removeSelectedItem } = | |
useMultipleSelection({ | |
selectedItems, | |
onStateChange({ selectedItems: newSelectedItems, type }) { | |
switch (type) { | |
case useMultipleSelection.stateChangeTypes | |
.SelectedItemKeyDownBackspace: | |
case useMultipleSelection.stateChangeTypes | |
.SelectedItemKeyDownDelete: | |
case useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace: | |
case useMultipleSelection.stateChangeTypes | |
.FunctionRemoveSelectedItem: | |
setSelectedItems(newSelectedItems as any) | |
break | |
default: | |
break | |
} | |
}, | |
}) | |
const { | |
isOpen, | |
getToggleButtonProps, | |
getLabelProps, | |
getMenuProps, | |
getInputProps, | |
highlightedIndex, | |
getItemProps, | |
selectedItem, | |
} = useCombobox({ | |
items, | |
itemToString(item) { | |
return item ? item.displayName : '' | |
}, | |
defaultHighlightedIndex: 0, // after selection, highlight the first item. | |
selectedItem: null, | |
stateReducer(state, actionAndChanges) { | |
const { changes, type } = actionAndChanges | |
switch (type) { | |
case useCombobox.stateChangeTypes.InputKeyDownEnter: | |
case useCombobox.stateChangeTypes.ItemClick: | |
return { | |
...changes, | |
isOpen: true, // keep the menu open after selection. | |
highlightedIndex: 0, // with the first option highlighted. | |
} | |
default: | |
return changes | |
} | |
}, | |
onStateChange({ | |
inputValue: newInputValue, | |
type, | |
selectedItem: newSelectedItem, | |
}) { | |
switch (type) { | |
case useCombobox.stateChangeTypes.InputKeyDownEnter: | |
case useCombobox.stateChangeTypes.ItemClick: | |
case useCombobox.stateChangeTypes.InputBlur: | |
if (newSelectedItem) { | |
setSelectedItems([...selectedItems, newSelectedItem]) | |
} | |
break | |
case useCombobox.stateChangeTypes.InputChange: { | |
setInputValue(inputValue) | |
break | |
} | |
default: | |
break | |
} | |
console.log({ newInputValue }) | |
}, | |
}) | |
return ( | |
<div className="w-[592px]"> | |
<div> | |
<section | |
style={{ textAlign: 'left', position: 'relative', width: '600px' }} | |
> | |
<label htmlFor="multiple-combo-box" {...getLabelProps()}> | |
Pick some books: | |
</label> | |
<input | |
id="multiple-combo-input" | |
type="text" | |
placeholder="Best book ever" | |
className="w-full" | |
{...getInputProps(getDropdownProps({ preventKeyAction: isOpen }))} | |
/>{' '} | |
<div | |
style={{ | |
position: 'absolute', | |
zIndex: '99', | |
backgroundColor: '#fff', | |
overflow: 'auto', | |
}} | |
> | |
<ul | |
data-variant="unstyled" | |
// style={{ all: 'unset', overflow: 'auto' }} | |
{...getMenuProps()} | |
> | |
{isOpen && | |
items.map((item, index) => ( | |
<li | |
key={`item-${index}`} | |
{...getItemProps({ item, index })} | |
className={highlightedIndex === index ? 'selected' : ''} | |
> | |
<span>{item.displayName}</span> | |
<span className="text-sm text-gray-700"> | |
{item.displayName} | |
</span> | |
</li> | |
))} | |
{console.log({ items })} | |
</ul> | |
</div> | |
</section> | |
</div> | |
<div> | |
{selectedItems.map(function renderSelectedItem( | |
selectedItemForRender, | |
index, | |
) { | |
return ( | |
<span | |
role="note" | |
key={`selected-item-${index}`} | |
{...getSelectedItemProps({ | |
selectedItem: selectedItemForRender, | |
index, | |
})} | |
> | |
{selectedItemForRender.displayName} | |
<button | |
style={{ | |
all: 'unset', | |
paddingInline: '0.5rem', | |
display: 'inline-block', | |
}} | |
onClick={(e) => { | |
e.stopPropagation() | |
removeSelectedItem(selectedItemForRender) | |
}} | |
> | |
✕ | |
</button> | |
</span> | |
) | |
})} | |
</div> | |
</div> | |
) | |
} | |
return <MultipleComboBox /> | |
} |
{ | |
"name": "react-uikit", | |
"version": "0.1.0", | |
"private": true, | |
"main": "dist/index.js", | |
"module": "dist/index.js", | |
"files": [ | |
"/dist" | |
], | |
"dependencies": { | |
"@testing-library/jest-dom": "^5.14.1", | |
"@testing-library/react": "^12.0.0", | |
"@testing-library/user-event": "^13.2.1", | |
"react": "^17.0.2", | |
"react-dom": "^17.0.2", | |
"react-hook-form": "^7.12.2", | |
"react-router-dom": "^5.2.0", | |
"react-scripts": "4.0.3", | |
"web-vitals": "^2.1.0" | |
}, | |
"scripts": { | |
"start": "react-scripts start", | |
"build": "react-scripts build", | |
"test": "react-scripts test", | |
"eject": "react-scripts eject", | |
"styleguidist": "styleguidist server", | |
"storybook": "start-storybook -p 6080 -s public", | |
"build-storybook": "build-storybook -s public", | |
"react-uikit": "lerna exec npm start --scope=@techdata/react-uikit", | |
"ecadmin": "lerna exec npm start --scope=@techdata/ecadmin", | |
"compile": "cross-env NODE_ENV=production npx babel src/components --out-dir dist --copy-files" | |
}, | |
"eslintConfig": { | |
"extends": [ | |
"react-app", | |
"react-app/jest" | |
], | |
"overrides": [ | |
{ | |
"files": [ | |
"**/*.stories.*" | |
], | |
"rules": { | |
"import/no-anonymous-default-export": "off" | |
} | |
} | |
] | |
}, | |
"browserslist": { | |
"production": [ | |
">0.2%", | |
"not dead", | |
"not op_mini all" | |
], | |
"development": [ | |
"last 1 chrome version", | |
"last 1 firefox version", | |
"last 1 safari version" | |
] | |
}, | |
"devDependencies": { | |
"@babel/cli": "^7.14.8", | |
"@babel/core": "^7.15.0", | |
"@babel/preset-env": "^7.15.0", | |
"@babel/preset-react": "^7.14.5", | |
"@babel/preset-stage-0": "^7.8.3", | |
"@storybook/addon-actions": "^6.3.6", | |
"@storybook/addon-essentials": "^6.3.6", | |
"@storybook/addon-links": "^6.3.6", | |
"@storybook/node-logger": "^6.3.6", | |
"@storybook/preset-create-react-app": "^3.2.0", | |
"@storybook/react": "^6.3.6", | |
"cross-env": "^7.0.3", | |
"lerna": "^4.0.0", | |
"react-styleguidist": "^11.1.7" | |
}, | |
"babel": { | |
"presets": [ | |
"@babel/preset-react", | |
"@babel/preset-env" | |
] | |
} | |
} |
const Box: PolymorphicComponent = forwardRef( | |
<T extends ElementType>(props: BoxProps<T>, ref: PolymorphicRef<T>) => { | |
const { as, children, ...rest } = props; | |
const Element = as || 'div'; | |
return ( | |
<Element ref={ref} {...rest}> | |
{children} | |
</Element> | |
); | |
} | |
); | |
type PolymorphicComponent = <T extends ElementType = 'div'>( | |
props: BoxProps<T> | |
) => ReactElement | null; | |
const Box:PolymorphicComponent = <T extends React.ElementType>({ as, children, ...rest }:BoxProps<T>) => { | |
const Element = as || 'div'; | |
return <Element {...rest}>{children}</Element>; | |
}; |
/** | |
* Referenced from Radix UI: | |
* https://github.com/radix-ui/primitives/blob/main/packages/react/polymorphic/src/polymorphic.ts | |
*/ | |
import * as React from "react"; | |
/* ------------------------------------------------------------------------------------------------- | |
* Utility types | |
* -----------------------------------------------------------------------------------------------*/ | |
type Merge<P1 = {}, P2 = {}> = Omit<P1, keyof P2> & P2; | |
type VoidFunctionComponent<E, OwnProps> = React.VoidFunctionComponent< | |
Merge< | |
E extends React.ElementType ? React.ComponentPropsWithoutRef<E> : never, | |
OwnProps & { as?: E } | |
> | |
>; | |
type ForwardRefExoticComponent<E, OwnProps> = React.ForwardRefExoticComponent< | |
Merge< | |
E extends React.ElementType ? React.ComponentPropsWithRef<E> : never, | |
OwnProps & { as?: E } | |
> | |
>; | |
/* ------------------------------------------------------------------------------------------------- | |
* Component | |
* -----------------------------------------------------------------------------------------------*/ | |
export interface Component<IntrinsicElementString, OwnProps = {}> | |
extends VoidFunctionComponent<IntrinsicElementString, OwnProps> { | |
/** | |
* When `as` prop is passed, use this overload. | |
* Merges original own props (without DOM props) and the inferred props | |
* from `as` element with the own props taking precendence. | |
* | |
* We explicitly avoid `React.ElementType` and manually narrow the prop types | |
* so that events are typed when using JSX.IntrinsicElements. | |
*/ | |
<As = IntrinsicElementString>( | |
props: As extends "" | |
? { as?: keyof JSX.IntrinsicElements } | |
: As extends React.ComponentType<infer P> | |
? Merge<P, OwnProps & { as?: As }> | |
: As extends keyof JSX.IntrinsicElements | |
? Merge<JSX.IntrinsicElements[As], OwnProps & { as?: As }> | |
: never | |
): React.ReactElement | null; | |
} | |
/* ------------------------------------------------------------------------------------------------- | |
* ForwardRefComponent | |
* -----------------------------------------------------------------------------------------------*/ | |
export interface ForwardRefComponent< | |
IntrinsicElementString, | |
OwnProps = {} | |
/** | |
* Extends original type to ensure built in React types play nice | |
* with polymorphic components still e.g. `React.ElementRef` etc. | |
*/ | |
> extends ForwardRefExoticComponent<IntrinsicElementString, OwnProps> { | |
/** | |
* When `as` prop is passed, use this overload. | |
* Merges original own props (without DOM props) and the inferred props | |
* from `as` element with the own props taking precendence. | |
* | |
* We explicitly avoid `React.ElementType` and manually narrow the prop types | |
* so that events are typed when using JSX.IntrinsicElements. | |
*/ | |
<As = IntrinsicElementString>( | |
props: As extends "" | |
? { as?: keyof JSX.IntrinsicElements } | |
: As extends React.ComponentType<infer P> | |
? Merge<P, OwnProps & { as?: As }> | |
: As extends keyof JSX.IntrinsicElements | |
? Merge<JSX.IntrinsicElements[As], OwnProps & { as?: As }> | |
: never | |
): React.ReactElement | null; | |
} | |
import { themed } from '@indeed/ifl-css'; | |
import * as Polymorphic from '../../types/polymorphic'; | |
import { StyleProps } from '../../utils/styled'; | |
export const BOX_DEFAULT_TAG = 'div'; | |
export type BoxProps = StyleProps; | |
export type BoxComponent = Polymorphic.Component<typeof BOX_DEFAULT_TAG, BoxProps>; | |
export const Box = themed(BOX_DEFAULT_TAG)({ | |
boxSizing: 'border-box', | |
margin: 0, | |
minWidth: 0 | |
}) as BoxComponent; | |
Box.displayName = 'Box'; |
import React from 'react'; | |
import { render, screen, RenderResult } from '@testing-library/react'; | |
import PopoverModal from './PopoverModal'; | |
describe('<PopoverModal />', () => { | |
it('should render', () => { | |
const { container } = render(<PopoverModal child={<div>Hello</div>} />); | |
screen.debug(); | |
}); | |
screen.debug(); | |
}); |
import React from 'react' | |
type ProgressOptions = | |
| { | |
/** Indicates progress bar is in busy/loading state */ | |
isBusy?: true | |
/** No current value when in busy state */ | |
value?: never | |
/** No max value needed when in busy state */ | |
max?: never | |
} | |
| { | |
/** Indicates progress bar is not in busy state */ | |
isBusy?: false | undefined | |
/** Current value of progress bar */ | |
value: number | |
/** Max value of progress bar */ | |
max: number | |
} | |
export type ProgressProps = { | |
/** | |
* Optional styles to pass to override default styles | |
* Accepts CSSProperties | |
*/ | |
styles?: React.CSSProperties | |
/** | |
* Optional accessible label for the progress bar | |
*/ | |
label?: string | |
} & ProgressOptions | |
const defaultStyles = {} as React.CSSProperties | |
/** | |
* Progress bar component | |
* Displays a progress bar with busy and value state | |
* | |
* @param {Object} classes - CSS classes object | |
* @param {ReactNode} [children] - Child elements | |
* @param {boolean} isBusy - Whether progress is in busy state | |
* @param {number} [value] - Current progress value | |
* @param {number} [max] - Max progress value | |
* @param {Object} props - Other props | |
* @returns {JSX.Element} - Rendered progress element | |
*/ | |
const Progress = ({ | |
styles, | |
isBusy, | |
value, | |
max, | |
label = 'Progress', | |
...props | |
}: ProgressProps): React.JSX.Element => { | |
const style = { ...defaultStyles, ...styles } | |
return ( | |
<progress | |
aria-label={label} | |
style={style} | |
aria-busy={isBusy} | |
value={value} | |
max={max} | |
{...props} | |
></progress> | |
) | |
} | |
export default Progress | |
Progress.displayName = 'Progress' | |
Progress.styles = defaultStyles |
// TODO refactor configForSectionsExists | |
// ! needs to be changed from fields to sections | |
// ! may impact the first section to the editable config section | |
// ! since it loops through all the fields | |
// ! use a lodash function to fine the nested field in the array to avoid looping through all the fields | |
const configForSectionExists = useMemo(() => { | |
const _sections = section.sections; | |
const _fields = find(_sections, 'fields')?.fields; | |
// console.log(_sections); | |
// console.log(config[field.name]); | |
console.log( | |
section?.fields?.some((field) => { | |
console.log(config[field.name] ); | |
return config[field.name]; | |
}) | |
); | |
// console.log(_fields?.some((field) => config[field.name])); | |
return section?.fields?.some((field) => config[field.name]); | |
// return _fields?.some((field) => config[field.name]); | |
}, [config, section]); |
import React, { useState } from 'react' | |
import Fuse from 'fuse.js' | |
interface Option { | |
displayName: string | |
optionId: string | |
} | |
interface SearchProps { | |
options: Option[] | |
} | |
const Search: React.FC<SearchProps> = ({ options }) => { | |
const [searchTerm, setSearchTerm] = useState('') | |
const fuse = new Fuse(options, { | |
keys: ['displayName'], | |
}) | |
const results = fuse.search(searchTerm) | |
return ( | |
<div> | |
<input | |
type="text" | |
placeholder="Search" | |
value={searchTerm} | |
onChange={(e) => setSearchTerm(e.target.value)} | |
/> | |
<ul> | |
{results.map((result) => ( | |
<li key={result.item.optionId}>{result.item.displayName}</li> | |
))} | |
</ul> | |
</div> | |
) | |
} | |
export default Search |
import FP from "../fp" | |
import { ComponentProps } from "../../types" | |
export interface PROPS extends ComponentProps {} | |
export const defaultStyles = {} | |
export const COMPONENT = ({}: PROPS) => { | |
return <FP as="img" styles={{ ...defaultStyles }} /> | |
} | |
COMPONENT.displayName = "COMPONENT" |
import { StoryObj, Meta } from "@storybook/react"; | |
import { within } from "@storybook/testing-library"; | |
import { expect } from "@storybook/jest"; | |
// ๐ import you component here -- import Component form "./component"; | |
// replace Component with your component name | |
const meta: Meta<typeof ComponentName> = { | |
title: "ComponentName", | |
component: ComponentName, | |
// @ts-ignore | |
argTypes: { | |
children: { | |
control: { type: "object" }, | |
}, | |
}, | |
}; | |
export default meta; | |
type Story = StoryObj<typeof ComponentName>; | |
export const MyComponent: Story = { | |
args: {}, | |
}; |
import { StoryObj, Meta } from "@storybook/react"; | |
import { within } from "@storybook/testing-library"; | |
import { expect } from "@storybook/jest"; | |
// ๐ import your component here - import COMPONENT from "./COMPONENT"; | |
// ๐ replace all instance of COMPONENT with your component name; | |
// ๐ modify the args to match your component props; | |
// ๐ remove these comments before committing; | |
const meta: Meta<typeof COMPONENT> = { | |
title: "Component name", | |
component: COMPONENT, | |
// @ts-ignore | |
// subcomponents: { NavItem }, | |
args: {}, | |
}; | |
export default meta; | |
type Story = StoryObj<typeof COMPONENT>; | |
export const Default: Story = { | |
args: {}, | |
}; |
import { useState, useEffect, ChangeEvent } from 'react' | |
import Fuse from 'fuse.js' | |
type UseFuseSearchReturnType<T> = [ | |
string, | |
(event: ChangeEvent<HTMLInputElement>) => void, | |
Fuse.FuseResult<T>[], | |
] | |
const useFuseSearch = <T,>( | |
list: T[], | |
options: Fuse.IFuseOptions<T>, | |
): UseFuseSearchReturnType<T> => { | |
const [searchTerm, setSearchTerm] = useState('') | |
const [searchResults, setSearchResults] = useState<Fuse.FuseResult<T>[]>([]) | |
useEffect(() => { | |
const fuse = new Fuse(list, options) | |
setSearchResults(fuse.search(searchTerm)) | |
}, [list, options, searchTerm]) | |
const handleSearch = (event: ChangeEvent<HTMLInputElement>) => { | |
setSearchTerm(event.target.value) | |
} | |
return [searchTerm, handleSearch, searchResults] | |
} | |
export default useFuseSearch |