ML: Add FolderCard component

This commit is contained in:
Gustav Hansen 2022-03-14 08:40:20 +01:00
parent c494431a20
commit 609fb1336d
10 changed files with 418 additions and 0 deletions

View File

@ -0,0 +1,92 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { pxToRem } from '@strapi/helper-plugin';
import { Box } from '@strapi/design-system/Box';
import { Stack } from '@strapi/design-system/Stack';
import Folder from '@strapi/icons/Folder';
import { FolderCardContext } from './FolderCardContext';
import useId from './utils/useId';
const FauxClickWrapper = styled.button`
height: 100%;
left: 0;
position: absolute;
opacity: 0;
top: 0;
width: 100%;
&:hover,
&:focus {
text-decoration: none;
}
`;
const StyledFolder = styled(Folder)`
path {
fill: currentColor;
}
`;
export const FolderCard = ({ children, id, startAction, ariaLabel, onDoubleClick, ...props }) => {
const generatedId = useId(id);
return (
<FolderCardContext.Provider value={{ id: generatedId }}>
<Box position="relative" {...props}>
<FauxClickWrapper
type="button"
onClick={event => event.preventDefault()}
onDoubleClick={onDoubleClick}
zIndex={1}
tabIndex={-1}
aria-label={ariaLabel}
aria-hidden
/>
<Stack
hasRadius
background="neutral0"
shadow="tableShadow"
paddingBottom={3}
paddingLeft={4}
paddingRight={4}
paddingTop={3}
spacing={3}
horizontal
cursor="pointer"
>
{startAction}
<Box
hasRadius
background="secondary100"
color="secondary500"
paddingBottom={2}
paddingLeft={3}
paddingRight={3}
paddingTop={2}
>
<StyledFolder width={pxToRem(20)} height={pxToRem(18)} />
</Box>
{children}
</Stack>
</Box>
</FolderCardContext.Provider>
);
};
FolderCard.defaultProps = {
id: undefined,
};
FolderCard.propTypes = {
ariaLabel: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
id: PropTypes.string,
onDoubleClick: PropTypes.func.isRequired,
startAction: PropTypes.element.isRequired,
};

View File

@ -0,0 +1,25 @@
import React from 'react';
import styled from 'styled-components';
import { Flex } from '@strapi/design-system/Flex';
import { useFolderCard } from './FolderCardContext';
const StyledBox = styled(Flex)`
user-select: none;
`;
export const FolderCardBody = props => {
const { id } = useFolderCard();
return (
<StyledBox
{...props}
id={`${id}-title`}
alignItems="flex-start"
direction="column"
position="relative"
zIndex={3}
/>
);
};

View File

@ -0,0 +1,14 @@
import React from 'react';
import { Box } from '@strapi/design-system/Box';
import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
import { useFolderCard } from './FolderCardContext';
export const FolderCardCheckbox = props => {
const { id } = useFolderCard();
return (
<Box position="relative" zIndex={2}>
<BaseCheckbox aria-labelledby={`${id}-title`} {...props} />
</Box>
);
};

View File

@ -0,0 +1,7 @@
import { createContext, useContext } from 'react';
export const FolderCardContext = createContext();
export function useFolderCard() {
return useContext(FolderCardContext);
}

View File

@ -0,0 +1,7 @@
import styled from 'styled-components';
import { BaseLink } from '@strapi/design-system/BaseLink';
export const FolderCardLink = styled(BaseLink)`
text-decoration: none;
`;

View File

@ -0,0 +1,4 @@
export { FolderCard } from './FolderCard';
export { FolderCardBody } from './FolderCardBody';
export { FolderCardCheckbox } from './FolderCardCheckbox';
export { FolderCardLink } from './FolderCardLink';

View File

@ -0,0 +1,65 @@
import React from 'react';
import { BaseLink } from '@strapi/design-system/BaseLink';
import { Flex } from '@strapi/design-system/Flex';
import { ThemeProvider, lightTheme } from '@strapi/design-system';
import { Typography } from '@strapi/design-system/Typography';
import { render, fireEvent } from '@testing-library/react';
import { FolderCard } from '../FolderCard';
import { FolderCardBody } from '../FolderCardBody';
import { FolderCardCheckbox } from '../FolderCardCheckbox';
const ID_FIXTURE = 'folder-1';
// eslint-disable-next-line react/prop-types
const ComponentFixture = ({ children, ...props }) => {
return (
<ThemeProvider theme={lightTheme}>
<FolderCard
id={ID_FIXTURE}
ariaLabel="Folder 1"
href="/"
startAction={<></>}
onDoubleClick={() => {}}
{...props}
>
{children || ''}
</FolderCard>
</ThemeProvider>
);
};
describe('FolderCard', () => {
it('renders and matches the snapshot', () => {
const { container } = render(<ComponentFixture />);
expect(container).toMatchSnapshot();
});
it('properly calls the onDoubleClick callback', () => {
const callback = jest.fn();
const { container } = render(<ComponentFixture onDoubleClick={callback} />);
fireEvent(container.querySelector('a'), new MouseEvent('dblclick', { bubbles: true }));
expect(callback).toHaveBeenCalledTimes(1);
});
it('has all required ids set when rendering a start action', () => {
const { container } = render(
<ComponentFixture startAction={<FolderCardCheckbox value={false} />}>
<FolderCardBody as="h2">
<BaseLink href="https://strapi.io" textDecoration="none">
<Flex direction="column" alignItems="flex-start">
<Typography variant="omega" fontWeight="semiBold">
Pictures
</Typography>
</Flex>
</BaseLink>
</FolderCardBody>
</ComponentFixture>
);
expect(container.querySelector(`[id="${ID_FIXTURE}-title"]`)).toBeInTheDocument();
expect(container.querySelector(`[aria-labelledby="${ID_FIXTURE}-title"]`)).toBeInTheDocument();
});
});

View File

@ -0,0 +1,149 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FolderCard renders and matches the snapshot 1`] = `
.c1 {
cursor: pointer;
}
.c8 {
border: 0;
-webkit-clip: rect(0 0 0 0);
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.c0 {
position: relative;
}
.c6 {
background: #eaf5ff;
color: #66b7f1;
padding-top: 8px;
padding-right: 12px;
padding-bottom: 8px;
padding-left: 12px;
border-radius: 4px;
}
.c3 {
background: #ffffff;
padding-top: 12px;
padding-right: 16px;
padding-bottom: 12px;
padding-left: 16px;
border-radius: 4px;
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
cursor: pointer;
cursor: pointer;
}
.c4 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c5 > * {
margin-left: 0;
margin-right: 0;
}
.c5 > * + * {
margin-left: 12px;
}
.c2 {
height: 100%;
left: 0;
position: absolute;
opacity: 0;
top: 0;
width: 100%;
}
.c2:hover,
.c2:focus {
-webkit-text-decoration: none;
text-decoration: none;
}
.c7 path {
fill: currentColor;
}
<div>
<div
class="c0"
>
<a
aria-hidden="true"
aria-label="Folder 1"
class="c1 c2"
href="/"
tabindex="-1"
target="_self"
text-decoration="none"
/>
<div
class="c3 c4 c5"
cursor="pointer"
spacing="3"
>
<div
class="c6"
>
<svg
class="c7"
fill="none"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.414 5H21a1 1 0 011 1v14a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1h7.414l2 2z"
fill="#212134"
/>
</svg>
</div>
</div>
</div>
<div
class="c8"
>
<p
aria-live="polite"
aria-relevant="all"
id="live-region-log"
role="log"
/>
<p
aria-live="polite"
aria-relevant="all"
id="live-region-status"
role="status"
/>
<p
aria-live="assertive"
aria-relevant="all"
id="live-region-alert"
role="alert"
/>
</div>
</div>
`;

View File

@ -0,0 +1,42 @@
import React from 'react';
import { render, act } from '@testing-library/react';
import useId from '../useId';
function setup(...args) {
let returnVal;
function TestComponent() {
returnVal = useId(...args);
return null;
}
render(<TestComponent />);
return returnVal;
}
describe('useId', () => {
let id;
test('increments', () => {
id = setup('one');
expect(id).toBe('one-1');
act(() => {
id = setup('one');
});
expect(id).toBe('one-2');
});
test('works with namespaces', () => {
act(() => {
id = setup('two');
});
expect(id).toBe('two-3');
});
});

View File

@ -0,0 +1,13 @@
import { useRef } from 'react';
let id = 0;
const genId = () => ++id;
const useId = (prefix, initialId) => {
const idRef = useRef(initialId || `${prefix}-${genId()}`);
return idRef.current;
};
export default useId;