@edvisor/product-language 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +12 -0
- package/.eslintrc.json +139 -0
- package/.storybook/main.js +57 -0
- package/.storybook/manager.js +7 -0
- package/.storybook/preview-head.html +1 -0
- package/.storybook/preview.js +15 -0
- package/.storybook/tsconfig.json +30 -0
- package/README.md +33 -0
- package/jest.config.ts +15 -0
- package/jest.setup.ts +2 -0
- package/package.json +30 -0
- package/project.json +75 -0
- package/src/assets/svg/example_icon.svg +3 -0
- package/src/assets/svg/example_icon_white.svg +3 -0
- package/src/assets/svg/spinner.svg +3 -0
- package/src/assets/svg/spinner_white.svg +3 -0
- package/src/helpers/index.ts +3 -0
- package/src/helpers/talesOf.tsx +42 -0
- package/src/index.ts +2 -0
- package/src/lib/components/README.md +49 -0
- package/src/lib/components/alert-banner/alert-banner.tsx +34 -0
- package/src/lib/components/alert-banner/alert-level-flags.ts +77 -0
- package/src/lib/components/alert-banner/index.ts +1 -0
- package/src/lib/components/badge/badge-type-flags.ts +72 -0
- package/src/lib/components/badge/badge.stories.tsx +16 -0
- package/src/lib/components/badge/badge.test.tsx +29 -0
- package/src/lib/components/badge/badge.tsx +31 -0
- package/src/lib/components/badge/index.ts +1 -0
- package/src/lib/components/card/atoms/card-frame.tsx +17 -0
- package/src/lib/components/card/atoms/index.ts +1 -0
- package/src/lib/components/card/card.test.tsx +163 -0
- package/src/lib/components/card/card.tsx +78 -0
- package/src/lib/components/card/components/card-alert-banner-slot.tsx +16 -0
- package/src/lib/components/card/components/card-controls-slot.tsx +19 -0
- package/src/lib/components/card/components/card-section-slot.tsx +51 -0
- package/src/lib/components/card/components/index.ts +3 -0
- package/src/lib/components/card/index.ts +2 -0
- package/src/lib/components/card/molecules/index.ts +1 -0
- package/src/lib/components/card/molecules/left-right-card.test.tsx +89 -0
- package/src/lib/components/card/molecules/left-right-card.tsx +63 -0
- package/src/lib/components/card/storybook/card.stories.mdx +100 -0
- package/src/lib/components/card/storybook/components.tsx +240 -0
- package/src/lib/components/checkbox/checkbox.test.tsx +39 -0
- package/src/lib/components/checkbox/checkbox.tsx +124 -0
- package/src/lib/components/checkbox/components/components.tsx +59 -0
- package/src/lib/components/checkbox/index.tsx +1 -0
- package/src/lib/components/checkbox/stories/checkbox.stories.mdx +54 -0
- package/src/lib/components/checkbox/stories/components.tsx +36 -0
- package/src/lib/components/checkbox/stories/index.tsx +1 -0
- package/src/lib/components/divider/divider-type-flags.tsx +37 -0
- package/src/lib/components/divider/divider.test.tsx +34 -0
- package/src/lib/components/divider/divider.tsx +37 -0
- package/src/lib/components/divider/index.tsx +1 -0
- package/src/lib/components/divider/stories/components.tsx +13 -0
- package/src/lib/components/divider/stories/divider.stories.mdx +50 -0
- package/src/lib/components/index.ts +14 -0
- package/src/lib/components/input-field/components/index.ts +2 -0
- package/src/lib/components/input-field/components/labeled-input.tsx +61 -0
- package/src/lib/components/input-field/components/stepper.tsx +59 -0
- package/src/lib/components/input-field/index.ts +6 -0
- package/src/lib/components/input-field/input-field.test.tsx +108 -0
- package/src/lib/components/input-field/input-field.tsx +126 -0
- package/src/lib/components/input-field/input-number.tsx +41 -0
- package/src/lib/components/input-field/input-text.tsx +30 -0
- package/src/lib/components/input-field/storybook/components.tsx +334 -0
- package/src/lib/components/input-field/storybook/input-field.stories.mdx +113 -0
- package/src/lib/components/layout/flex.tsx +22 -0
- package/src/lib/components/layout/grid-layout.tsx +40 -0
- package/src/lib/components/layout/index.ts +3 -0
- package/src/lib/components/layout/left-right-layout.tsx +67 -0
- package/src/lib/components/link/index.ts +1 -0
- package/src/lib/components/link/link.test.tsx +29 -0
- package/src/lib/components/link/link.tsx +56 -0
- package/src/lib/components/link/storybook/link.stories.mdx +51 -0
- package/src/lib/components/molecules/avatar/avatar-size-flags.tsx +55 -0
- package/src/lib/components/molecules/avatar/avatar.test.tsx +114 -0
- package/src/lib/components/molecules/avatar/avatar.tsx +80 -0
- package/src/lib/components/molecules/avatar/index.tsx +1 -0
- package/src/lib/components/molecules/avatar/stories/avatar.stories.mdx +55 -0
- package/src/lib/components/molecules/avatar/stories/components.tsx +36 -0
- package/src/lib/components/molecules/button/button-flags.tsx +235 -0
- package/src/lib/components/molecules/button/button.test.tsx +77 -0
- package/src/lib/components/molecules/button/button.tsx +231 -0
- package/src/lib/components/molecules/button/index.tsx +1 -0
- package/src/lib/components/molecules/button/stories/button.stories.mdx +104 -0
- package/src/lib/components/molecules/button/stories/components.tsx +86 -0
- package/src/lib/components/molecules/index.ts +3 -0
- package/src/lib/components/molecules/input-checkbox/index.tsx +1 -0
- package/src/lib/components/molecules/input-checkbox/input-checkbox.test.tsx +34 -0
- package/src/lib/components/molecules/input-checkbox/input-checkbox.tsx +50 -0
- package/src/lib/components/molecules/input-checkbox/stories/components.tsx +36 -0
- package/src/lib/components/molecules/input-checkbox/stories/index.tsx +1 -0
- package/src/lib/components/molecules/input-checkbox/stories/input-checkbox.stories.mdx +51 -0
- package/src/lib/components/organisms/index.ts +1 -0
- package/src/lib/components/organisms/multi-choice-list/index.tsx +1 -0
- package/src/lib/components/organisms/multi-choice-list/multi-choice-list.test.tsx +33 -0
- package/src/lib/components/organisms/multi-choice-list/multi-choice-list.tsx +53 -0
- package/src/lib/components/organisms/multi-choice-list/stories/components.tsx +126 -0
- package/src/lib/components/organisms/multi-choice-list/stories/index.tsx +1 -0
- package/src/lib/components/organisms/multi-choice-list/stories/multi-choice-list.stories.mdx +99 -0
- package/src/lib/components/spinner/index.tsx +1 -0
- package/src/lib/components/spinner/spinner-size-flags.tsx +39 -0
- package/src/lib/components/spinner/spinner.test.tsx +31 -0
- package/src/lib/components/spinner/spinner.tsx +67 -0
- package/src/lib/components/spinner/stories/components.tsx +8 -0
- package/src/lib/components/spinner/stories/spinner.stories.mdx +42 -0
- package/src/lib/components/thumbnail/index.tsx +1 -0
- package/src/lib/components/thumbnail/stories/thumbnail.stories.mdx +34 -0
- package/src/lib/components/thumbnail/thumbnail-size-flags.tsx +41 -0
- package/src/lib/components/thumbnail/thumbnail.test.tsx +51 -0
- package/src/lib/components/thumbnail/thumbnail.tsx +40 -0
- package/src/lib/components/typography/index.ts +1 -0
- package/src/lib/components/typography/storybook/components.tsx +256 -0
- package/src/lib/components/typography/storybook/typography.stories.mdx +88 -0
- package/src/lib/components/typography/typography.test.tsx +93 -0
- package/src/lib/components/typography/typography.tsx +57 -0
- package/src/lib/foundations/color-system/base-palette/base-palette.stories.tsx +123 -0
- package/src/lib/foundations/color-system/base-palette/base-palette.ts +94 -0
- package/src/lib/foundations/color-system/base-palette/index.ts +1 -0
- package/src/lib/foundations/color-system/color-guidelines/color-guidelines.stories.mdx +85 -0
- package/src/lib/foundations/color-system/color-guidelines/color-guidelines.stories.tsx +231 -0
- package/src/lib/foundations/color-system/color-guidelines/color-guidelines.ts +159 -0
- package/src/lib/foundations/color-system/color-guidelines/index.ts +1 -0
- package/src/lib/foundations/color-system/components/color-sample.tsx +99 -0
- package/src/lib/foundations/color-system/components/index.ts +1 -0
- package/src/lib/foundations/color-system/index.ts +1 -0
- package/src/lib/foundations/index.ts +4 -0
- package/src/lib/foundations/shadows/components.tsx +59 -0
- package/src/lib/foundations/shadows/index.ts +1 -0
- package/src/lib/foundations/shadows/shadows.stories.mdx +71 -0
- package/src/lib/foundations/shadows/shadows.tsx +47 -0
- package/src/lib/foundations/spacing/index.ts +1 -0
- package/src/lib/foundations/spacing/spacing-guidelines.ts +24 -0
- package/src/lib/foundations/spacing/spacing.stories.mdx +51 -0
- package/src/lib/foundations/spacing/spacing.ts +18 -0
- package/src/lib/foundations/typography/constants.ts +25 -0
- package/src/lib/foundations/typography/index.tsx +1 -0
- package/src/lib/foundations/typography/text-aspect-flags.ts +54 -0
- package/src/lib/foundations/typography/typography.tsx +97 -0
- package/src/lib/helpers/generic-types.ts +44 -0
- package/src/lib/helpers/index.ts +6 -0
- package/src/lib/helpers/nothing.tsx +18 -0
- package/src/lib/helpers/numbers.ts +53 -0
- package/src/lib/helpers/safe-navigation.ts +34 -0
- package/src/lib/helpers/slots.tsx +76 -0
- package/src/lib/helpers/strings.test.ts +47 -0
- package/src/lib/helpers/strings.ts +16 -0
- package/tsconfig.json +35 -0
- package/tsconfig.lib.json +28 -0
- package/tsconfig.spec.json +21 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react'
|
|
2
|
+
import { Divider } from './index'
|
|
3
|
+
import { Borders } from '@foundations'
|
|
4
|
+
|
|
5
|
+
describe('Divider Tests', () => {
|
|
6
|
+
describe('Divider Tests', () => {
|
|
7
|
+
it('should render the Divider with default color', async () => {
|
|
8
|
+
render(
|
|
9
|
+
<Divider/>
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
expect(screen.getByTestId('divider-test')).toBeInTheDocument()
|
|
13
|
+
expect(screen.getByTestId('divider-test')).toHaveStyle(`border: 1px solid ${Borders.Default.Default}`)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should render the Divider with default color using parameter', async () => {
|
|
17
|
+
render(
|
|
18
|
+
<Divider default/>
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
expect(screen.getByTestId('divider-test')).toBeInTheDocument()
|
|
22
|
+
expect(screen.getByTestId('divider-test')).toHaveStyle(`border: 1px solid ${Borders.Default.Default}`)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should render the Divider with subdued color', async () => {
|
|
26
|
+
render(
|
|
27
|
+
<Divider subdued/>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expect(screen.getByTestId('divider-test')).toBeInTheDocument()
|
|
31
|
+
expect(screen.getByTestId('divider-test')).toHaveStyle(`border: 1px solid ${Borders.Default.Subdued}`)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import { FC } from '@helpers'
|
|
3
|
+
import { DivideTypeProps, getColor } from './divider-type-flags'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @TODO I added flex here because without it the bottom margin was
|
|
7
|
+
* being collapsed with the next sibling (when I applied margins to the divider,
|
|
8
|
+
* which I expect we basically _always_ will)
|
|
9
|
+
*
|
|
10
|
+
* I'm not confident with reasoning about margin-collapse so if you see
|
|
11
|
+
* this comment and feel like experimenting, go crazy. Check out the
|
|
12
|
+
* card stories for an example of the divider (it's way at the bottom)
|
|
13
|
+
*/
|
|
14
|
+
const DividerWrapper = styled.div`
|
|
15
|
+
position: relative;
|
|
16
|
+
width:100%;
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
const DividerLine = styled.hr<{ color: string }>`
|
|
20
|
+
position: absolute;
|
|
21
|
+
left: 0;
|
|
22
|
+
right: 0;
|
|
23
|
+
margin: unset;
|
|
24
|
+
border: 1px solid ${({ color }) => color};
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
type IProps = { className?: string } & DivideTypeProps
|
|
28
|
+
|
|
29
|
+
export const Divider: FC<IProps> = (props) => {
|
|
30
|
+
const color = getColor(props)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<DividerWrapper className={props.className}>
|
|
34
|
+
<DividerLine color={color} data-testid="divider-test"/>
|
|
35
|
+
</DividerWrapper>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './divider'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
|
|
2
|
+
import { Divider } from '../index'
|
|
3
|
+
import { WrapperStories, AvatarsWithText, AvatarsWithImage, WrapperStoriesWithLabel } from './components'
|
|
4
|
+
|
|
5
|
+
<Meta
|
|
6
|
+
title="Components/Divider"
|
|
7
|
+
component={Divider}
|
|
8
|
+
/>
|
|
9
|
+
|
|
10
|
+
# Divider
|
|
11
|
+
|
|
12
|
+
For more details, check out the component page on [Figma](https://www.figma.com/file/ue1CurHfZ426o2T2l8Dk64/Edvisor-Product-Language?node-id=1194%3A1611)
|
|
13
|
+
|
|
14
|
+
## How to use
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
// Import the component
|
|
18
|
+
import { Divider } from './index'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// Render the component sending the parameters
|
|
22
|
+
<Divider default/>
|
|
23
|
+
|
|
24
|
+
//or
|
|
25
|
+
|
|
26
|
+
<Divider subdued/>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
Default Divider
|
|
32
|
+
|
|
33
|
+
<Canvas>
|
|
34
|
+
<Story name="default">
|
|
35
|
+
<WrapperStories>
|
|
36
|
+
<Divider default/>
|
|
37
|
+
</WrapperStories>
|
|
38
|
+
</Story>
|
|
39
|
+
</Canvas>
|
|
40
|
+
|
|
41
|
+
Subdued Divider
|
|
42
|
+
|
|
43
|
+
<Canvas>
|
|
44
|
+
<Story name="subdued">
|
|
45
|
+
<WrapperStories>
|
|
46
|
+
<Divider subdued/>
|
|
47
|
+
</WrapperStories>
|
|
48
|
+
</Story>
|
|
49
|
+
</Canvas>
|
|
50
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './alert-banner'
|
|
2
|
+
export * from './badge'
|
|
3
|
+
export * from './card'
|
|
4
|
+
export * from './checkbox'
|
|
5
|
+
export * from './divider'
|
|
6
|
+
export * from './input-field'
|
|
7
|
+
export * from './layout'
|
|
8
|
+
export * from './link'
|
|
9
|
+
export * from './thumbnail'
|
|
10
|
+
export * from './typography'
|
|
11
|
+
|
|
12
|
+
//Adding everything in molecules
|
|
13
|
+
export * from './molecules'
|
|
14
|
+
export * from './organisms'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
|
|
3
|
+
import { Icons, Margin } from '@foundations'
|
|
4
|
+
import { PropsWithChildren, FC, maybeRender, safeArray } from '@helpers'
|
|
5
|
+
import { Flex } from 'components/layout'
|
|
6
|
+
import { Body, Label1 } from 'components/typography'
|
|
7
|
+
|
|
8
|
+
export const Icon = styled.div`
|
|
9
|
+
height: 16px;
|
|
10
|
+
width: 16px;
|
|
11
|
+
border: 2px solid ${Icons.Default};
|
|
12
|
+
border-radius: 50%;
|
|
13
|
+
margin-right: ${Margin.s};
|
|
14
|
+
`
|
|
15
|
+
|
|
16
|
+
const ErrorIcon = styled(Icon)`
|
|
17
|
+
border: 2px solid ${Icons.Critical};
|
|
18
|
+
`
|
|
19
|
+
|
|
20
|
+
const HTMLLabel = styled.label`
|
|
21
|
+
all: unset;
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
const LabelText = styled(Label1)`
|
|
25
|
+
margin-bottom: ${Margin.xxs};
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
const HelpText = styled(Body)`
|
|
29
|
+
margin-bottom: ${Margin.xxs};
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
const ErrorMessage = styled(Flex)`
|
|
33
|
+
margin-bottom: ${Margin.xxs};
|
|
34
|
+
`
|
|
35
|
+
|
|
36
|
+
interface ILabeledInputProps extends PropsWithChildren {
|
|
37
|
+
label?: string
|
|
38
|
+
helpText?: string
|
|
39
|
+
errors?: { message: string }[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const LabeledInput: FC<ILabeledInputProps> = (props) => {
|
|
43
|
+
return (
|
|
44
|
+
<HTMLLabel>
|
|
45
|
+
<label>
|
|
46
|
+
{maybeRender(props.label, <LabelText>{props.label}</LabelText>)}
|
|
47
|
+
{props.children}
|
|
48
|
+
</label>
|
|
49
|
+
{safeArray(props.errors).map((e) => (
|
|
50
|
+
<ErrorMessage center key={e.message}>
|
|
51
|
+
<ErrorIcon />
|
|
52
|
+
<Body>{e.message}</Body>
|
|
53
|
+
</ErrorMessage>
|
|
54
|
+
))}
|
|
55
|
+
{maybeRender(
|
|
56
|
+
props.helpText,
|
|
57
|
+
<HelpText subdued>{props.helpText}</HelpText>
|
|
58
|
+
)}
|
|
59
|
+
</HTMLLabel>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Margin, Padding, Surface } from '@foundations'
|
|
2
|
+
import { FC } from '@helpers'
|
|
3
|
+
import { Center } from 'components/layout'
|
|
4
|
+
import styled from 'styled-components'
|
|
5
|
+
|
|
6
|
+
const Controls = styled.div`
|
|
7
|
+
height: 34px;
|
|
8
|
+
width: 26px;
|
|
9
|
+
padding: ${Padding.xxxs} ${Padding.xxxs};
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
const Up = styled(Center)`
|
|
14
|
+
background: ${Surface.Neutral.Default};
|
|
15
|
+
width: 22px;
|
|
16
|
+
height: 14px;
|
|
17
|
+
border-radius: 3px 3px 0px 0px;
|
|
18
|
+
margin-bottom: ${Margin.xxxs};
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
const TriangleUp = styled.div`
|
|
25
|
+
width: 0px;
|
|
26
|
+
height: 0px;
|
|
27
|
+
border-left: 5px solid transparent;
|
|
28
|
+
border-right: 5px solid transparent;
|
|
29
|
+
border-bottom: 5px solid black;
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
const TriangleDown = styled.div`
|
|
33
|
+
width: 0px;
|
|
34
|
+
height: 0px;
|
|
35
|
+
border-left: 5px solid transparent;
|
|
36
|
+
border-right: 5px solid transparent;
|
|
37
|
+
border-top: 5px solid black;
|
|
38
|
+
`
|
|
39
|
+
|
|
40
|
+
const Down = styled(Center)`
|
|
41
|
+
background: ${Surface.Neutral.Default};
|
|
42
|
+
width: 22px;
|
|
43
|
+
height: 14px;
|
|
44
|
+
border-radius: 0px 0px 3px 3px;
|
|
45
|
+
`
|
|
46
|
+
|
|
47
|
+
interface IStepperProps {
|
|
48
|
+
onUpClick?: () => void
|
|
49
|
+
onDownClick?: () => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const Stepper: FC<IStepperProps> = (props: IStepperProps) => {
|
|
53
|
+
return (
|
|
54
|
+
<Controls>
|
|
55
|
+
<Up onClick={props.onUpClick}><TriangleUp /></Up>
|
|
56
|
+
<Down onClick={props.onDownClick}><TriangleDown /></Down>
|
|
57
|
+
</Controls>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {fireEvent, render, screen } from '@testing-library/react'
|
|
2
|
+
import { InputField } from './index'
|
|
3
|
+
|
|
4
|
+
describe('InputField', () => {
|
|
5
|
+
describe('the inner input element', () => {
|
|
6
|
+
it('should receive nice html props', async () => {
|
|
7
|
+
render(
|
|
8
|
+
<InputField
|
|
9
|
+
value="value"
|
|
10
|
+
onChange={() => undefined}
|
|
11
|
+
type="text"
|
|
12
|
+
label="Label"
|
|
13
|
+
placeholder="placeholder"
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument()
|
|
18
|
+
expect(screen.getByPlaceholderText('placeholder')).toBeInTheDocument()
|
|
19
|
+
expect(screen.getByDisplayValue('value')).toBeInTheDocument()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should call the onChange callback on change events', async () => {
|
|
23
|
+
const handleChange = jest.fn()
|
|
24
|
+
|
|
25
|
+
render(<InputField value="value" onChange={handleChange} />)
|
|
26
|
+
|
|
27
|
+
fireEvent.change(screen.getByRole('textbox'), {
|
|
28
|
+
target: { value: 'JavaScript' },
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(handleChange).toHaveBeenCalledTimes(1)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('Label', () => {
|
|
36
|
+
it('should render when given', async () => {
|
|
37
|
+
render(<InputField label="Label" />)
|
|
38
|
+
|
|
39
|
+
expect(screen.getByLabelText('Label')).toBeInTheDocument()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should not render when not given', async () => {
|
|
43
|
+
render(<InputField />)
|
|
44
|
+
|
|
45
|
+
expect(screen.queryByLabelText('Label')).not.toBeInTheDocument()
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('Prefix', () => {
|
|
50
|
+
it('should render when given', async () => {
|
|
51
|
+
render(<InputField prefix="$" />)
|
|
52
|
+
|
|
53
|
+
expect(screen.getByText('$')).toBeInTheDocument()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should not render when not given', async () => {
|
|
57
|
+
render(<InputField />)
|
|
58
|
+
|
|
59
|
+
expect(screen.queryByText('$')).not.toBeInTheDocument()
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('Suffix', () => {
|
|
64
|
+
it('should render when given', async () => {
|
|
65
|
+
render(<InputField suffix="%" />)
|
|
66
|
+
|
|
67
|
+
expect(screen.getByText('%')).toBeInTheDocument()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should not render when not given', async () => {
|
|
71
|
+
render(<InputField />)
|
|
72
|
+
|
|
73
|
+
expect(screen.queryByText('&')).not.toBeInTheDocument()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('Slots', () => {
|
|
78
|
+
it('should render the Icon slot', async () => {
|
|
79
|
+
render(
|
|
80
|
+
<InputField>
|
|
81
|
+
<InputField.Icon>Icon</InputField.Icon>
|
|
82
|
+
</InputField>
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
expect(screen.getByText('Icon')).toBeInTheDocument()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should render the Controls slot', async () => {
|
|
89
|
+
render(
|
|
90
|
+
<InputField>
|
|
91
|
+
<InputField.Controls>Controls</InputField.Controls>
|
|
92
|
+
</InputField>
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
expect(screen.getByText('Controls')).toBeInTheDocument()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should not render other kinds of children', () => {
|
|
99
|
+
render(
|
|
100
|
+
<InputField>
|
|
101
|
+
<div>Other Children</div>
|
|
102
|
+
</InputField>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
expect(screen.queryByText('Other Children')).not.toBeInTheDocument()
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import { InputHTMLAttributes } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Borders, Focused, Margin, Padding, Surface, Typography, _Typography } from '@foundations'
|
|
5
|
+
import { getSlot, Slot, is, forwardRef, If } from '@helpers'
|
|
6
|
+
import { Flex, SpaceBetween } from '../layout'
|
|
7
|
+
|
|
8
|
+
import { LabeledInput } from './components'
|
|
9
|
+
import { Label1, Label2 } from 'components/typography'
|
|
10
|
+
|
|
11
|
+
const FancyBorder = styled(SpaceBetween)<{ invalid?: boolean }>`
|
|
12
|
+
background: ${({ invalid }) => is(invalid) ? Surface.Critical.Subdued : Surface.Default.Default };
|
|
13
|
+
border: 1px solid ${({ invalid }) => is(invalid) ? Borders.Critical.Default : Borders.Default.Default };
|
|
14
|
+
border-radius: 6px;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
margin-bottom: ${Margin.xxs};
|
|
17
|
+
padding-left: ${Padding.s};
|
|
18
|
+
|
|
19
|
+
user-select: none;
|
|
20
|
+
|
|
21
|
+
:focus, :focus-within, :active {
|
|
22
|
+
border: ${({ invalid }) => is(invalid)
|
|
23
|
+
? `1px solid ${Borders.Critical.Default}`
|
|
24
|
+
: `2px solid ${Focused.Default}`
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
|
|
29
|
+
const InputLayout = styled(Flex)`
|
|
30
|
+
width: 100%;
|
|
31
|
+
`
|
|
32
|
+
|
|
33
|
+
export const HTMLInput = styled.input`
|
|
34
|
+
all: unset;
|
|
35
|
+
|
|
36
|
+
padding: ${Padding.none} ${Padding.none} ${Padding.xs} ${Padding.none};
|
|
37
|
+
|
|
38
|
+
width: 100%;
|
|
39
|
+
|
|
40
|
+
::-webkit-inner-spin-button {
|
|
41
|
+
-webkit-appearance: none;
|
|
42
|
+
margin: ${Margin.none};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
${Typography.Body}
|
|
46
|
+
|
|
47
|
+
::placeholder {
|
|
48
|
+
${_Typography.LightText}
|
|
49
|
+
}
|
|
50
|
+
`
|
|
51
|
+
|
|
52
|
+
const PrefixFrame = styled(Label1)`
|
|
53
|
+
margin-right: ${Margin.xs};
|
|
54
|
+
`
|
|
55
|
+
|
|
56
|
+
const SuffixFrame = styled(Label1)`
|
|
57
|
+
margin-right: ${Margin.xs};
|
|
58
|
+
`
|
|
59
|
+
|
|
60
|
+
class IconSlot extends Slot {}
|
|
61
|
+
class ControlsSlot extends Slot {}
|
|
62
|
+
|
|
63
|
+
export type InputFieldSlots = {
|
|
64
|
+
Icon: typeof IconSlot,
|
|
65
|
+
Controls: typeof ControlsSlot,
|
|
66
|
+
Label: typeof LabeledInput,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface IInputFieldProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
70
|
+
invalid?: boolean
|
|
71
|
+
prefix?: string
|
|
72
|
+
suffix?: string
|
|
73
|
+
label?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const InputFrame = styled.label`
|
|
77
|
+
width: 100%;
|
|
78
|
+
padding: ${Padding.xs} ${Padding.none} ${Padding.none} ${Padding.none};
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
display: flex;
|
|
81
|
+
`
|
|
82
|
+
|
|
83
|
+
const ControlsFrame = Flex
|
|
84
|
+
|
|
85
|
+
const Label = styled(Label2)`
|
|
86
|
+
margin-bottom: ${Margin.xxs};
|
|
87
|
+
`
|
|
88
|
+
|
|
89
|
+
export const InputField = forwardRef<
|
|
90
|
+
HTMLInputElement,
|
|
91
|
+
IInputFieldProps,
|
|
92
|
+
InputFieldSlots
|
|
93
|
+
>((props: IInputFieldProps, ref) => {
|
|
94
|
+
const { children, prefix, suffix, label, className, ...inputProps } = props
|
|
95
|
+
const icon = getSlot(IconSlot, children)
|
|
96
|
+
const controls = getSlot(ControlsSlot, children)
|
|
97
|
+
const required = is(props.required) ? '*' : ''
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<FancyBorder invalid={props.invalid} className={className}>
|
|
101
|
+
<InputFrame>
|
|
102
|
+
<If is={label}>
|
|
103
|
+
<Label subdued>
|
|
104
|
+
{label}
|
|
105
|
+
{required}
|
|
106
|
+
</Label>
|
|
107
|
+
</If>
|
|
108
|
+
<InputLayout>
|
|
109
|
+
{icon}
|
|
110
|
+
<If is={prefix}>
|
|
111
|
+
<PrefixFrame subdued>{prefix}</PrefixFrame>
|
|
112
|
+
</If>
|
|
113
|
+
<HTMLInput ref={ref} {...inputProps} />
|
|
114
|
+
<If is={suffix}>
|
|
115
|
+
<SuffixFrame subdued>{suffix}</SuffixFrame>
|
|
116
|
+
</If>
|
|
117
|
+
</InputLayout>
|
|
118
|
+
</InputFrame>
|
|
119
|
+
<ControlsFrame center>{controls}</ControlsFrame>
|
|
120
|
+
</FancyBorder>
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
InputField.Icon = IconSlot
|
|
125
|
+
InputField.Controls = ControlsSlot
|
|
126
|
+
InputField.Label = LabeledInput
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FC, isNil } from '@helpers'
|
|
2
|
+
import { InputField, InputFieldSlots } from './input-field'
|
|
3
|
+
import { Stepper } from './components'
|
|
4
|
+
|
|
5
|
+
interface IInputNumberProps {
|
|
6
|
+
value?: number
|
|
7
|
+
onChange: (change?: number) => void
|
|
8
|
+
|
|
9
|
+
label?: string
|
|
10
|
+
placeholder?: string
|
|
11
|
+
invalid?: boolean
|
|
12
|
+
prefix?: string
|
|
13
|
+
suffix?: string
|
|
14
|
+
required?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type IInputNumberSlots = InputFieldSlots & {
|
|
18
|
+
Stepper: typeof Stepper
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const InputNumber: FC<IInputNumberProps, IInputNumberSlots> = (props) => {
|
|
22
|
+
const { onChange, value, ...inputFieldProps } = props
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<InputField
|
|
26
|
+
type='number'
|
|
27
|
+
value={isNil(value) || isNaN(value) ? '' : value}
|
|
28
|
+
onChange={(e) => {
|
|
29
|
+
const next = e.target.value === '' ? undefined : parseFloat(e.target.value)
|
|
30
|
+
|
|
31
|
+
onChange(next)
|
|
32
|
+
}}
|
|
33
|
+
{...inputFieldProps}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
InputNumber.Icon = InputField.Icon
|
|
39
|
+
InputNumber.Controls = InputField.Controls
|
|
40
|
+
InputNumber.Label = InputField.Label
|
|
41
|
+
InputNumber.Stepper = Stepper
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FC } from '@helpers'
|
|
2
|
+
import { InputField, InputFieldSlots } from './input-field'
|
|
3
|
+
|
|
4
|
+
interface IInputTextProps {
|
|
5
|
+
value: string
|
|
6
|
+
onChange: (change: string) => void
|
|
7
|
+
|
|
8
|
+
label?: string
|
|
9
|
+
invalid?: boolean
|
|
10
|
+
placeholder?: string
|
|
11
|
+
prefix?: string
|
|
12
|
+
suffix?: string
|
|
13
|
+
required?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const InputText: FC<IInputTextProps, InputFieldSlots> = (props) => {
|
|
17
|
+
const { onChange, ...inputFieldProps } = props
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<InputField
|
|
21
|
+
type='text'
|
|
22
|
+
onChange={(e) => onChange(e.target.value)}
|
|
23
|
+
{...inputFieldProps}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
InputText.Icon = InputField.Icon
|
|
29
|
+
InputText.Controls = InputField.Controls
|
|
30
|
+
InputText.Label = InputField.Label
|