Skip to content

Instantly share code, notes, and snippets.

@shawn-sandy
Last active March 11, 2024 13:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save shawn-sandy/e3bfd2b342d8ef91a678908cd9b5fc70 to your computer and use it in GitHub Desktop.
Save shawn-sandy/e3bfd2b342d8ef91a678908cd9b5fc70 to your computer and use it in GitHub Desktop.
React snippets

import { Meta } from "@storybook/addon-docs"

FPKIT Button(s)

lorem...

import type { Meta, StoryObj } from '@storybook/react'
import { within, userEvent } from '@storybook/testing-library'
import { expect } from '@storybook/jest'
/**
* import component(s)
*/
import { Button } from './button'
/**
* Add object with test prop values
*/
const buttonProps = {
children: 'Default Button',
type: 'button',
}
/**
* Set component meta data
*/
const meta: Meta<typeof Button> = {
title: 'FP.React Components/Buttons',
component: Button,
}
/**
* Export meta object
*/
export default meta
type Story = StoryObj<typeof Button>
export const AdvButton: Story = {
args: {
// @ts-ignore
children: buttonProps.children,
type: buttonProps.type,
onPointerDown: { action: "down" },
onPointerLeave: { action: "leave" },
onPointerOver: { action: "over" }
}
}
/**
* create story with inherited story args
* (AdvButton.args)
*/
export const ButtonInteractions: Story = {
...AdvButton,
args: {...AdvButton.args},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
const button = canvas.getByRole('button')
expect(button).toBeInTheDocument()
expect(button).toHaveAccessibleName(buttonProps.children)
expect(button).toHaveAttribute('type', buttonProps.type)
await userEvent.click(button)
},
}
//๐Ÿ‘‡ 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();
});
});
// Code: Breadcrumb component
import React from 'react'
type Crumbs = {
path?: string | number
name?: string
url?: string
}
type BreadcrumbProps = {
routes?: Crumbs[]
startRoute?: React.ReactNode
}
export const Breadcrumb: React.FC<BreadcrumbProps> = ({
startRoute = 'Home',
routes,
...props
}) => {
const [currentPath, setCurrentPath] = React.useState('')
React.useEffect(() => {
const path = window.location.pathname
if (path.length) {
setCurrentPath(path)
}
}, [])
const getPathName = (pathSegment: string | number) => {
const route = routes?.find((route) => route.path === pathSegment)
return route ? route.name : pathSegment
}
const segments = currentPath.split('/').filter((segment) => segment)
if (currentPath.length) {
return (
<ul aria-label="breadcrumb" data-list="unstyled inline" {...props}>
<li>
<a href="/">{startRoute}</a>
</li>
{segments.length &&
segments.map((segment: any, index) => (
<li key={index}>
<span>
/{' '}
<a href={`/${segments.slice(0, index + 1).join('/')}`}>
{isNaN(segment) ? (
`${getPathName(segment)}`
) : (
<span>{`Page ${segment}`}</span>
)}
</a>
</span>
</li>
))}
</ul>
)
} else {
return null
}
}
export default Breadcrumb
Breadcrumb.displayName = 'BreadCrumb'
import React from 'react'
const Button = (props) => {
const { className, children, ...rest } = props
return (
<button className={className} {...rest}>
{children}
</button>
)
}
export default Button
import { StoryObj, Meta } from '@storybook/react'
import { within } from '@storybook/testing-library'
import { expect } from '@storybook/jest'
import Button from './button'
import './button.scss'
const meta: Meta<typeof Button> = {
title: 'FP.React Components/Buttons',
component: Button,
args: {
children: 'Click me',
},
parameters: {
actions: { argTypesRegex: '^on.*' },
},
argTypes: { onClick: { action: 'clicked' } },
} as Meta
export default meta
type Story = StoryObj<typeof Button>
export const ButtonComponent: Story = {
args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
expect(canvas.getByRole('button')).toBeInTheDocument()
},
} as Story
export const Small: Story = {
args: {
'data-btn': 'sm',
},
} as Story
export const Medium: Story = {
args: {
'data-btn': 'md',
},
} as Story
export const Large: Story = {
args: {
'data-btn': 'lg',
},
} as Story
export const Custom: Story = {
args: {
styles: {
'--btn-fs': '2rem',
},
},
} as Story
import * as React from 'react';
import { describe, expect, test, it } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from './button';
describe('Button renders correctly', () => {
test('Button renders correctly without crashing', () => {
render(<Button type='button'>Click Here</Button>);
const button = screen.getByRole('button');
expect(button).toHaveAttribute('type', 'button');
expect(screen).toMatchSnapshot();
screen.debug();
} );
});
{
"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 { useRef } from 'react'
import useArrowNavigation from './useArrowNavigation'
function ElementNav() {
const parentRef = useRef<HTMLDivElement>(null)
const itemsRef = useRef<(HTMLButtonElement | null)[]>([null])
const [activeIndex, setActiveIndex] = useArrowNavigation(
itemsRef.current.filter((item) => item !== null) as HTMLButtonElement[],
)
const items = ['Item 1', 'Item 2', 'Item 3']
return (
<div ref={parentRef} tabIndex={0}>
{items.map((item, index) => (
<button
key={index}
ref={(el) => (itemsRef.current[index] = el)}
className={activeIndex === index ? 'active selected' : ''}
onClick={() => setActiveIndex(index)}
style={{ display: 'block', width: '100%' }}
>
{item}
</button>
))}
</div>
)
}
export default ElementNav
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)
}}
>
&#10005;
</button>
</span>
)
})}
</div>
</div>
)
}
return <MultipleComboBox />
}
import React, { useRef } from 'react'
import './MyComponent.css' // Import your css file
import useArrowNavigation from './useArrowNavigation'
/**
* Example arrow navigation component
* @returns
*/
const NavigationComponent: React.FC = () => {
const sectionRef = useRef<HTMLUListElement>(null)
const currentSelection = useArrowNavigation(7, sectionRef)
return (
<ul ref={sectionRef} tabIndex={0}>
{Array.from({ length: 7 }, (_, index) => (
<li
key={index}
className={currentSelection === index ? 'selected' : ''}
onClick={() => {
console.log('clicked')
}}
>
{`Element ${index + 1}`}
</li>
))}
</ul>
)
}
export default NavigationComponent
{
"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

React Snippets

// 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 { Button } from './button'
import { BADGE } from '@geometricpanda/storybook-addon-badges'
import '@shawnsandy/first-paint/dist/css/components/button.min.css'
import { userEvent, screen, waitFor } from '@storybook/testing-library'
import { expect } from '@storybook/jest'
export default {
title: 'FP.React Components/Buttons',
component: Button,
args: {
children: 'Default Button',
type: 'button',
},
argTypes: {
children: { control: 'text' },
onPointerDown: { action: 'down' },
onPointerLeave: { action: 'leave' },
onPointerOver: { action: 'over' },
},
parameters: {
badges: [BADGE.BETA],
docs: {
description: {
component: 'A headless button component',
},
},
},
}
export const Default = {}
export const Primary = {
args: {
children: 'Primary Button',
type: 'submit',
},
play: async () => {
// const canvas = within(canvasElement)
const button = screen.getByRole('button')
expect(button).toBeInTheDocument()
expect(button).toHaveAccessibleName(/primary button/i)
},
}
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 { useEffect, useState } from 'react'
/**
* Use arrow keys to navigate a list
* @param itemCount
* @param sectionRef
* @returns
*/
const useArrowNavigation = (
/**
* The number of items in the list
*/
itemCount: number,
/**
* The ref of the section that contains the list
* @example const sectionRef = useRef<HTMLUListElement>(null)
* @example const currentSelection = useArrowNavigation(7, sectionRef)
* @example <ul ref={sectionRef} tabIndex={0}>
*/
sectionRef:
| React.RefObject<HTMLElement>
| React.RefObject<HTMLDivElement>
| React.RefObject<HTMLUListElement>,
) => {
const [currentIndex, setCurrentIndex] = useState(0)
useEffect(() => {
const handleKeyPress = (event: KeyboardEvent) => {
if (
!sectionRef.current ||
sectionRef.current !== document.activeElement
) {
return
}
if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
setCurrentIndex((prevState) => (prevState + 1) % itemCount)
} else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
setCurrentIndex((prevState) => (prevState - 1 + itemCount) % itemCount)
}
}
window.addEventListener('keydown', handleKeyPress)
return () => {
window.removeEventListener('keydown', handleKeyPress)
}
}, [itemCount, sectionRef])
return currentIndex
}
export default useArrowNavigation
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment