element', () => {
39 | renderWithRevKitThemeProvider(() =>
);
40 |
41 | const defaultAvatar = screen.getByTestId('avatar');
42 |
43 | expect(defaultAvatar).toBeInstanceOf(HTMLDivElement);
44 | });
45 |
46 |
47 | defaultAvatarTypes.forEach(type => {
48 | it(`should render ${type} type default avatar`, () => {
49 | renderWithRevKitThemeProvider(() =>
);
50 |
51 | const defaultAvatar = screen.getByTestId('default-avatar');
52 |
53 | expect(defaultAvatar).toHaveAttribute('type', type);
54 | });
55 | });
56 | });
--------------------------------------------------------------------------------
/src/components/avatar/default-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from 'solid-styled-components';
2 |
3 | export type DefaultAvatarType = 'steven' | 'meg' | 'jake' | 'mili'
4 |
5 | export interface DefaultAvatarProps {
6 | type?: DefaultAvatarType;
7 | round?: boolean;
8 | }
9 |
10 | const getImageUrl = (type: 'steven' | 'meg' | 'jake' | 'mili') => `https://github.com/specialdoom/solid-rev-kit/blob/main/src/assets/images/${type}.png?raw=true`;
11 |
12 | const StyledAvatar = styled('div') <{
13 | type: 'steven' | 'meg' | 'jake' | 'mili',
14 | round: boolean;
15 | }>`
16 | height: 56px;
17 | width: 56px;
18 | border-radius: ${props => props.round ? '50%' : '4px'};
19 | background-size: cover;
20 | background-image: ${props => `url(${getImageUrl(props.type)})`};
21 | `;
22 |
23 | export const DefaultAvatar = ({ type = 'steven', round = false, ...rest }: DefaultAvatarProps) => (
24 |
29 | );
--------------------------------------------------------------------------------
/src/components/avatar/index.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar as InternalAvatar, AvatarProps } from './avatar';
2 | import { DefaultAvatar, DefaultAvatarProps } from './default-avatar';
3 |
4 | const Avatar = Object.assign(InternalAvatar, {
5 | Steven: ({ round }: DefaultAvatarProps) =>
,
6 | Jake: ({ round }: DefaultAvatarProps) =>
,
7 | Mili: ({ round }: DefaultAvatarProps) =>
,
8 | Meg: ({ round }: DefaultAvatarProps) =>
9 | });
10 |
11 | export { Avatar };
12 | export type { AvatarProps };
--------------------------------------------------------------------------------
/src/components/button/README.md:
--------------------------------------------------------------------------------
1 | # Button component
2 |
3 | ### Usage
4 |
5 | ```jsx
6 | import { Button } from '@specialdoom/solid-rev-kit';
7 |
8 | const Container = () => {
9 | const handleClick = () => console.log('button click');
10 |
11 | return (
12 | <>
13 |
14 |
15 |
16 |
17 |
18 |
21 |
24 |
25 |
26 |
29 |
32 |
33 |
36 |
39 |
42 |
43 | >
44 | );
45 | };
46 | ```
47 |
48 | ### API
49 |
50 | | Property | Description | Type | Default |
51 | | -------- | ------------------------------------------------------ | ------------------ | --------- |
52 | | variant | Type of button. Options: 'bright', 'ghost' or 'accent' | string | 'accent' |
53 | | small | Whether the button is small | boolean | false |
54 | | disabled | Whether the button is disabled | boolean | false |
55 | | onClick | onClick event handler | (e: Event) => void | undefined |
56 |
--------------------------------------------------------------------------------
/src/components/button/button.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils';
2 | import { screen, cleanup } from 'solid-testing-library';
3 | import { ButtonType, Button } from './button';
4 |
5 | const buttonVariants: ButtonType[] = ['accent', 'bright', 'ghost'];
6 |
7 | describe('Button', () => {
8 | afterEach(() => {
9 | jest.clearAllMocks();
10 | cleanup();
11 | });
12 |
13 | it('should render', () => {
14 | renderWithRevKitThemeProvider(() =>
);
15 |
16 | const button = screen.getByTestId('button');
17 |
18 | expect(button).toBeInTheDocument();
19 | });
20 |
21 | it('should render
,
15 |
16 | Action
17 |
18 | ]}
19 | />
20 |
Action,
24 |
25 | Action
26 |
27 | ]}
28 | small
29 | />
30 | >
31 | );
32 | ```
33 |
34 | ### API
35 |
36 | | Property | Description | Type | Default |
37 | | ----------- | -------------------------------------- | ------------ | --------- |
38 | | title | Title of callout component | string | undefined |
39 | | description | Description of callout component | string | undefined |
40 | | small | Whether the callout is small | boolean | false |
41 | | actions | Array of actions for callout component | JSXElement[] | undefined |
42 |
--------------------------------------------------------------------------------
/src/components/callout/callout.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils';
2 | import { screen, cleanup } from 'solid-testing-library';
3 | import { Callout } from './callout';
4 |
5 | describe('Callout', () => {
6 | afterEach(() => {
7 | jest.clearAllMocks();
8 | cleanup();
9 | });
10 |
11 | it('should render', () => {
12 | renderWithRevKitThemeProvider(() => );
13 |
14 | const callout = screen.getByTestId('callout');
15 |
16 | expect(callout).toBeInTheDocument();
17 | });
18 |
19 | it('should render a element', () => {
20 | renderWithRevKitThemeProvider(() =>
);
21 |
22 | const callout = screen.getByTestId('callout');
23 |
24 | expect(callout).toBeInstanceOf(HTMLDivElement);
25 | });
26 |
27 | it('should render small callout', () => {
28 | const description = 'description';
29 |
30 | renderWithRevKitThemeProvider(() =>
);
31 |
32 | const callout = screen.getByTestId('small-callout');
33 |
34 | expect(callout).toBeInTheDocument();
35 | });
36 |
37 | it('should contain actions', () => {
38 | renderWithRevKitThemeProvider(() =>
Action,
40 | ]} />);
41 |
42 | const callout = screen.getByTestId('callout');
43 | const actionsContainer = callout.querySelector('div');
44 |
45 | expect(actionsContainer).toContainHTML('Action');
46 | });
47 | });
--------------------------------------------------------------------------------
/src/components/callout/callout.tsx:
--------------------------------------------------------------------------------
1 | import { Component, For, JSXElement, Show } from 'solid-js';
2 | import { styled } from 'solid-styled-components';
3 | import { Typography } from '../typography';
4 |
5 | export interface CalloutProps {
6 | title?: string;
7 | description: string;
8 | actions?: JSXElement[];
9 | small?: boolean;
10 | }
11 |
12 | const StyledSmallCallout = styled('div')`
13 | width: 100%;
14 | height: 80%;
15 | display: inline-flex;
16 | flex-wrap: wrap;
17 | justify-content: space-between;
18 | align-items: center;
19 | background: ${props => props.theme.colors.bright};
20 | color: ${props => props.theme.colors.primary};
21 | padding: 24px 20px;
22 | border-radius: 8px;
23 | box-shadow: rgba(0, 0, 0, 0.02) 0px 1px 3px 0px, rgba(27, 31, 35, 0.15) 0px 0px 0px 1px;
24 | `;
25 |
26 | const ActionsContainer = styled('div') <{ small: boolean }>`
27 | display: inline-flex;
28 | justify-content: ${props => props.small ? 'flex-end' : 'flex-start'};
29 | align-items: center;
30 | gap: 8px;
31 | `;
32 |
33 | const StyledLargeCallout = styled('div')`
34 | width: 100%;
35 | height: auto;
36 | min-height: 200px;
37 | padding: 40px;
38 | display: flex;
39 | flex-direction: column;
40 | background: ${props => props.theme.colors.bright};
41 | color: ${props => props.theme.colors.primary};
42 | box-shadow: rgba(0, 0, 0, 0.02) 0px 1px 3px 0px, rgba(27, 31, 35, 0.15) 0px 0px 0px 1px;
43 | gap: 16px;
44 | border-radius: 16px;
45 | `;
46 |
47 | const SmallCallout: Component = ({ description, actions }) => (
48 |
49 | {description}
50 |
51 | {action => action}
52 |
53 |
54 | );
55 |
56 | export const Callout: Component = ({ title, description, actions, small = false }) => (
57 | }
59 | >
60 |
61 | {title}
62 | {description}
63 |
64 | {action => action}
65 |
66 |
67 |
68 | );
69 |
--------------------------------------------------------------------------------
/src/components/callout/index.tsx:
--------------------------------------------------------------------------------
1 | import { Callout, CalloutProps } from './callout';
2 |
3 | export { Callout };
4 | export type { CalloutProps };
--------------------------------------------------------------------------------
/src/components/card/README.md:
--------------------------------------------------------------------------------
1 | # Generic Card component
2 |
3 | ### Usage
4 |
5 | ```jsx
6 | import { Card } from '@specialdoom/solid-rev-kit';
7 |
8 | const Container = () => (
9 | Action]}
13 | >
14 | Supporting description for the card goes here like a breeze.
15 |
16 | );
17 | ```
18 |
19 | ### API
20 |
21 | | Property | Description | Type | Default |
22 | | -------- | ----------------------------------------------------- | ------------ | --------- |
23 | | title | Title of card component | string | undefined |
24 | | imageSrc | Src of card image. Defines whether the card has image | string | undefined |
25 | | actions | Array of actions for callout component | JSXElement[] | undefined |
26 |
27 | # Fill Card component
28 |
29 | ### Usage
30 |
31 | ```jsx
32 | import { Card } from '@specialdoom/solid-rev-kit';
33 |
34 | const Container = () => (
35 | <>
36 |
37 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
38 |
39 | alert('share'),
47 | icon:
48 | },
49 | { label: 'Save', onClick: () => alert('save') }
50 | ]}
51 | small
52 | >
53 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
54 |
55 |
56 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
57 |
58 | >
59 | );
60 | ```
61 |
62 | ### API
63 |
64 | | Property | Description | Type | Default |
65 | | ---------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------- | --------- |
66 | | background | Color or url for background | string | #2C2738 |
67 | | color | Text and icon color | string | #FFFFFF |
68 | | label | Label of fill card component | string | undefined |
69 | | title | Title of fill card component | string | undefined |
70 | | small | Whether the component is small | boolean | false |
71 | | actions | Actions of fill card component | [CardAction](https://github.com/specialdoom/solid-rev-kit/blob/main/src/components/card/FillCard.tsx#L10-L13)[] | [] |
72 |
--------------------------------------------------------------------------------
/src/components/card/fill-card.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils';
2 | import { screen, cleanup } from 'solid-testing-library';
3 | import { FillCard } from './fill-card';
4 |
5 | describe('FillCard', () => {
6 | afterEach(() => {
7 | jest.clearAllMocks();
8 | cleanup();
9 | });
10 |
11 | it('should render', () => {
12 | renderWithRevKitThemeProvider(() => (
13 |
14 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
15 |
16 | ));
17 |
18 | const fillCard = screen.getByTestId('fill-card');
19 |
20 | expect(fillCard).toBeInTheDocument();
21 | });
22 |
23 | it('should render a element', () => {
24 | renderWithRevKitThemeProvider(() => (
25 |
26 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
27 |
28 | ));
29 |
30 | const fillCard = screen.getByTestId('fill-card');
31 |
32 | expect(fillCard).toBeInstanceOf(HTMLDivElement);
33 | });
34 |
35 | it('should have background and color', () => {
36 | renderWithRevKitThemeProvider(() => (
37 |
38 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
39 |
40 | ));
41 |
42 | const fillCard = screen.getByTestId('fill-card');
43 |
44 | expect(fillCard).toHaveAttribute('background');
45 | expect(fillCard).toHaveAttribute('color');
46 | });
47 |
48 | it('should render more icons if there are actions', () => {
49 | renderWithRevKitThemeProvider(() => (
50 |
alert('share'),
54 | }
55 | ]}>
56 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
57 |
58 | ));
59 |
60 | const moreIcon = screen.getByTestId('more-icon');
61 |
62 | expect(moreIcon).toBeInTheDocument();
63 | });
64 |
65 | it('should render small', () => {
66 | renderWithRevKitThemeProvider(() => (
67 |
68 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
69 |
70 | ));
71 |
72 | const fillCard = screen.getByTestId('fill-card');
73 |
74 | expect(fillCard).toHaveAttribute('small');
75 | });
76 | });
--------------------------------------------------------------------------------
/src/components/card/fill-card.tsx:
--------------------------------------------------------------------------------
1 | import { Component, createSignal, For, JSX, JSXElement, Show } from 'solid-js';
2 | import { styled } from 'solid-styled-components';
3 | import { Icons } from '../icons';
4 | import { clickOutside, useDirective } from '../../directives';
5 |
6 | useDirective(clickOutside);
7 |
8 | const { More } = Icons;
9 |
10 | interface CardAction {
11 | label: string;
12 | onClick: () => void;
13 | icon?: JSXElement;
14 | }
15 |
16 | export interface FillCardProps {
17 | background?: string;
18 | color?: string;
19 | label?: string;
20 | title?: string;
21 | small?: boolean;
22 | actions?: CardAction[];
23 | children: JSXElement;
24 | }
25 |
26 | const isValidUrl = (_string: string) => {
27 | const matchPattern = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/;
28 | return matchPattern.test(_string);
29 | };
30 |
31 | const StyledCard = styled('div') <{ background: string, color: string, small: boolean }>`
32 | position: relative;
33 | background: ${props => isValidUrl(props.background) ? `url(${props.background})` : props.background};
34 | color: ${props => props.color};
35 | height: ${props => props.small ? '240px' : '430px'};
36 | background-size: cover;
37 | width: 260px;
38 | border-radius: 20px;
39 | padding: 16px 20px;
40 | box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
41 | `;
42 |
43 | const ActionsHeader = styled('div')`
44 | top: 16px;
45 | right: 20px;
46 | position: absolute;
47 | display: inline-flex;
48 | justify-content: flex-end;
49 |
50 | & svg {
51 | cursor: pointer;
52 | }
53 | `;
54 |
55 | const CardDetails = styled('div')`
56 | position: absolute;
57 | bottom: 16px;
58 | left: 20px;
59 | right: 20px;
60 |
61 | label {
62 | opacity: 0.8;
63 | }
64 | `;
65 |
66 | const ActionsContainer = styled('div')`
67 | position: absolute;
68 | top: 16px;
69 | right: 45px;
70 | border-radius: 4px;
71 | padding: 10px;
72 | background: ${props => props.theme.colors.bright};
73 | width: 70%;
74 | color: ${props => props.theme.colors.dark};
75 | display: flex;
76 | flex-direction: column;
77 | gap: 8px;
78 |
79 | &::before {
80 | position: absolute;
81 | top: 5px;
82 | right: -5px;
83 | height: 10px;
84 | width: 5px;
85 | content: ' ';
86 | background-image: url("data:image/svg+xml,%3Csvg width='5' height='10' viewBox='0 0 5 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0L3.58579 3.58579C4.36684 4.36684 4.36684 5.63316 3.58579 6.41421L0 10V0Z' fill='%23fff'/%3E%3C/svg%3E");
87 | }
88 | `;
89 |
90 | const ActionButton = styled('button')`
91 | outline: none;
92 | border: none;
93 | width: 100%;
94 | text-align: left;
95 | background: ${props => props.theme.colors.bright};
96 | color: ${props => props.theme.colors.dark};
97 | font-size: 16px;
98 | cursor: pointer;
99 | display: inline-flex;
100 | align-items: center;
101 | gap: 8px;
102 |
103 | &:hover {
104 | text-decoration: underline;
105 | }
106 | `;
107 |
108 | export const FillCard: Component
= ({
109 | background = '#2C2738',
110 | color = '#ffffff',
111 | label,
112 | title,
113 | small = false,
114 | actions = [],
115 | children
116 | }) => {
117 | const [getShowActions, setShowActions] = createSignal(false);
118 |
119 | return (
120 |
121 | 0}>
122 |
123 | setShowActions(false)}>
124 | setShowActions(v => !v)} />
125 |
126 |
127 |
128 |
129 | {action => (
130 |
131 | {action.icon}
132 | {action.label}
133 |
134 | )}
135 |
136 |
137 |
138 |
139 |
140 |
141 | {title}
142 |
143 | {children}
144 |
145 |
146 |
147 | );
148 | };
--------------------------------------------------------------------------------
/src/components/card/generic-card.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils';
2 | import { screen, cleanup } from 'solid-testing-library';
3 | import { GenericCard } from './generic-card';
4 |
5 | describe('GenericCard', () => {
6 | afterEach(() => {
7 | jest.clearAllMocks();
8 | cleanup();
9 | });
10 |
11 | it('should render', () => {
12 | renderWithRevKitThemeProvider(() => (
13 | Action]}
17 | >
18 | Supporting description for the card goes here like a breeze.
19 |
20 | ));
21 |
22 | const genericCard = screen.getByTestId('generic-card');
23 |
24 | expect(genericCard).toBeInTheDocument();
25 | });
26 |
27 | it('should render a element', () => {
28 | renderWithRevKitThemeProvider(() => (
29 |
Action]}
33 | >
34 | Supporting description for the card goes here like a breeze.
35 |
36 | ));
37 |
38 | const genericCard = screen.getByTestId('generic-card');
39 |
40 | expect(genericCard).toBeInstanceOf(HTMLDivElement);
41 | });
42 |
43 | it('should render image container when imageSrc valid', () => {
44 | renderWithRevKitThemeProvider(() => (
45 |
Action]}
49 | >
50 | Supporting description for the card goes here like a breeze.
51 |
52 | ));
53 |
54 | const genericCard = screen.getByTestId('generic-card');
55 | const divs = genericCard.querySelectorAll('div');
56 |
57 | expect(divs.length).toBe(3);
58 | });
59 |
60 | it('should not render image container when imageSrc is invalid', () => {
61 | renderWithRevKitThemeProvider(() => (
62 |
Action]}
65 | >
66 | Supporting description for the card goes here like a breeze.
67 |
68 | ));
69 |
70 | const genericCard = screen.getByTestId('generic-card');
71 | const divs = genericCard.querySelectorAll('div');
72 |
73 | expect(divs.length).toBe(2);
74 | });
75 |
76 | it('should render title', () => {
77 | const title = 'Card title';
78 | renderWithRevKitThemeProvider(() => (
79 |
Action]}
82 | >
83 | Supporting description for the card goes here like a breeze.
84 |
85 | ));
86 |
87 | const genericCard = screen.getByTestId('generic-card');
88 | const heading = genericCard.querySelector('h1');
89 |
90 | expect(heading?.innerHTML).toBe(title);
91 | });
92 | });
--------------------------------------------------------------------------------
/src/components/card/generic-card.tsx:
--------------------------------------------------------------------------------
1 | import { Component, For, JSXElement, Show } from 'solid-js';
2 | import { styled } from 'solid-styled-components';
3 | import { Typography } from '../typography';
4 |
5 | export interface GenerircCardProps {
6 | imageSrc?: string;
7 | title?: string;
8 | actions?: JSXElement[];
9 | children: JSXElement;
10 | }
11 |
12 | const StyledCard = styled('div')`
13 | height: fit-content;
14 | width: 300px;
15 | padding: 16px;
16 | box-sizing: border-box;
17 | display: flex;
18 | flex-direction: column;
19 | justify-content: flex-start;
20 | box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
21 | border-radius: 24px;
22 | background: ${props => props.theme.colors.bright};
23 | gap: 8px;
24 | `;
25 |
26 | const Image = styled('div') <{ src?: string }>`
27 | height: 200px;
28 | background: ${props => props.src ? `url(${props.src})` : 'unset'};
29 | background-size: cover;
30 | border-radius: 16px;
31 | width: 100%;
32 | `;
33 |
34 | const ActionsContainer = styled('div')`
35 | padding: 8px 0;
36 | height: auto;
37 | font-size: 14px;
38 | `;
39 |
40 | const BodyContainer = styled('div')`
41 | height: auto;
42 | font-size: 14px;
43 | padding: 8px 0;
44 | `;
45 |
46 | export const GenericCard: Component
= ({ imageSrc, title, children, actions }) => (
47 |
48 |
49 |
50 |
51 | {title}
52 |
53 | {children}
54 |
55 |
56 | {(action) => action}
57 |
58 |
59 | );
--------------------------------------------------------------------------------
/src/components/card/index.tsx:
--------------------------------------------------------------------------------
1 | import { GenericCard, GenerircCardProps } from './generic-card';
2 | import { FillCard, FillCardProps } from './fill-card';
3 |
4 | const Card = Object.assign({}, {
5 | Fill: FillCard,
6 | Generic: GenericCard
7 | });
8 |
9 | export { Card };
10 | export type { GenerircCardProps, FillCardProps };
--------------------------------------------------------------------------------
/src/components/chat-bubble/README.md:
--------------------------------------------------------------------------------
1 | # Chat Bubble component
2 |
3 | ### Usage
4 |
5 | ```jsx
6 | import { ChatBubble } from '@specialdoom/solid-rev-kit';
7 |
8 | const Container = () => (
9 | <>
10 | Chat message
11 | >
12 | ```
13 |
14 | ### API
15 |
16 | | Property | Description | Type | Default |
17 | | --------- | -------------------------------------------------------------------------------------------------- | ------ | ----------- |
18 | | type | Type of chat bubble component. Options: 'bright', 'dark', 'strawberry' and 'blueberry'. | string | 'blueberry' |
19 | | placement | Placement of chat bubble arrow. Options: 'top-right', 'top-left', 'bottom-left' and 'bottom-right' | string | 'top-left' |
20 |
--------------------------------------------------------------------------------
/src/components/chat-bubble/chat-bubble.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils';
2 | import { screen, cleanup } from 'solid-testing-library';
3 | import { ChatBubble, ChatBubbleType } from './chat-bubble';
4 |
5 | const chatBubbleTypes: ChatBubbleType[] = ['blueberry', 'bright', 'dark', 'strawberry'];
6 |
7 | describe('ChatBubble', () => {
8 | afterEach(() => {
9 | jest.clearAllMocks();
10 | cleanup();
11 | });
12 |
13 | it('should render', () => {
14 | renderWithRevKitThemeProvider(() => Chat message);
15 |
16 | const chatBubble = screen.getByTestId('chat-bubble');
17 |
18 | expect(chatBubble).toBeInTheDocument();
19 | });
20 |
21 | it('should render a element', () => {
22 | renderWithRevKitThemeProvider(() =>
Chat message);
23 |
24 | const chatBubble = screen.getByTestId('chat-bubble');
25 |
26 | expect(chatBubble).toBeInstanceOf(HTMLDivElement);
27 | });
28 |
29 | it('should render chat message as children', () => {
30 | const children = 'Chat message';
31 | renderWithRevKitThemeProvider(() =>
{children});
32 |
33 | const chatBubble = screen.getByTestId('chat-bubble');
34 |
35 | expect(chatBubble).toHaveTextContent(children);
36 | });
37 |
38 | chatBubbleTypes.forEach(type => {
39 | it(`should render ${type} type alert`, () => {
40 | renderWithRevKitThemeProvider(() =>
Chat message);
41 |
42 | const chatBubble = screen.getByTestId('chat-bubble');
43 |
44 | expect(chatBubble).toHaveAttribute('type', type);
45 | });
46 | });
47 |
48 | it('should have placement attribue', () => {
49 | const placement = 'top-left';
50 |
51 | renderWithRevKitThemeProvider(() =>
Chat message);
52 |
53 | const chatBubble = screen.getByTestId('chat-bubble');
54 |
55 | expect(chatBubble).toHaveAttribute('placement', placement);
56 | })
57 | });
--------------------------------------------------------------------------------
/src/components/chat-bubble/chat-bubble.tsx:
--------------------------------------------------------------------------------
1 | import { Component, JSXElement } from 'solid-js';
2 | import { styled } from 'solid-styled-components';
3 |
4 | export type ChatBubbleType = 'bright' | 'dark' | 'blueberry' | 'strawberry';
5 |
6 | export interface ChatBubbleProps {
7 | type?: ChatBubbleType;
8 | placement?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
9 | children: JSXElement;
10 | }
11 |
12 | const StyledBubble = styled('div') <{
13 | type: 'bright' | 'dark' | 'blueberry' | 'strawberry',
14 | placement: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left',
15 | }>`
16 | position: relative;
17 | height: 50px;
18 | min-width: 240px;
19 | border-radius: 6px;
20 | padding: 16px;
21 | font-size: 14px;
22 | box-sizing: border-box;
23 | color: ${props => props.type === 'bright' ? props.theme.colors.secondary : props.theme.colors.bright};
24 | background: ${props => props.theme.colors[props.type]};
25 |
26 | &::before {
27 | position: absolute;
28 | z-index: -1;
29 | content: ' ';
30 | width: 0;
31 | height: 0;
32 | border-style: solid;
33 | }
34 |
35 | &[h-position="left"]::before {
36 | left: 0;
37 | border-width: 9px 0 9px 9px;
38 | border-color: transparent transparent transparent ${props => props.theme.colors[props.type]};
39 | }
40 |
41 | &[h-position="right"]::before {
42 | right: 0;
43 | border-width: 9px 9px 9px 0;
44 | border-color: transparent ${props => props.theme.colors[props.type]} transparent transparent;
45 | }
46 |
47 | &[v-position="top"]::before {
48 | top: -8px;
49 | }
50 |
51 | &[v-position="bottom"]::before {
52 | bottom: -8px;
53 | }
54 | `;
55 |
56 | export const ChatBubble: Component
= ({
57 | type = 'blueberry',
58 | placement = 'top-left',
59 | children,
60 | }) => (
61 |
68 | {children}
69 |
70 | );
--------------------------------------------------------------------------------
/src/components/chat-bubble/index.tsx:
--------------------------------------------------------------------------------
1 | import { ChatBubble, ChatBubbleProps, ChatBubbleType } from './chat-bubble';
2 |
3 | export { ChatBubble };
4 | export type { ChatBubbleProps, ChatBubbleType };
--------------------------------------------------------------------------------
/src/components/counter/README.md:
--------------------------------------------------------------------------------
1 | # Counter component
2 |
3 | ### Usage
4 |
5 | ```jsx
6 | import { Counter } from '@specialdoom/solid-rev-kit';
7 |
8 | const Container = () => (
9 | <>
10 |
11 |
12 |
13 | >
14 | );
15 | ```
16 |
17 | ### API
18 |
19 | | Property | Description | Type | Default |
20 | | -------- | ----------------------------------------- | ------------------ | --------- |
21 | | value | Value of counter component | number | 0 |
22 | | minValue | Minimum value of counter component | number | -999 |
23 | | maxValue | Maximum value of counter component | number | 999 |
24 | | disabled | Whether the counter component is disabled | boolean | false |
25 | | onChange | onChange event hanlder | (e: Event) => void | undefined |
26 | | onFocus | onFocus event hanlder | (e: Event) => void | undefined |
27 | | onInput | onInput event hanlder | (e: Event) => void | undefined |
28 | | onBlur | onBlur event hanlder | (e: Event) => void | undefined |
29 |
--------------------------------------------------------------------------------
/src/components/counter/counter.test.tsx:
--------------------------------------------------------------------------------
1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils';
2 | import { screen, cleanup } from 'solid-testing-library';
3 | import { Counter } from './counter';
4 |
5 | describe('Counter', () => {
6 | afterEach(() => {
7 | jest.clearAllMocks();
8 | cleanup();
9 | });
10 |
11 | it('should render', () => {
12 | renderWithRevKitThemeProvider(() => (
13 |
14 | ));
15 |
16 | const counter = screen.getByTestId('counter');
17 |
18 | expect(counter).toBeInTheDocument();
19 | });
20 |
21 | it('should render a element', () => {
22 | renderWithRevKitThemeProvider(() => (
23 |
24 | ));
25 |
26 | const counter = screen.getByTestId('counter');
27 |
28 | expect(counter).toBeInstanceOf(HTMLDivElement);
29 | });
30 |
31 | it('should render value inside input', () => {
32 | const value = 1;
33 | renderWithRevKitThemeProvider(() => (
34 |
35 | ));
36 |
37 | const counter = screen.getByTestId('counter');
38 | const input = counter.querySelector('input');
39 |
40 | expect(input?.value).toBe(value.toString());
41 | });
42 |
43 | it('should increment value inside input on plus click', () => {
44 | renderWithRevKitThemeProvider(() => (
45 |
46 | ));
47 |
48 | const counter = screen.getByTestId('counter');
49 | const plusIcon = screen.getByTestId('plus-icon');
50 | const input = counter.querySelector('input');
51 |
52 | expect(input?.value).toBe('1');
53 | plusIcon.click();
54 | expect(input?.value).toBe('2');
55 | });
56 |
57 | it('should decrement value inside input on minus click', () => {
58 | renderWithRevKitThemeProvider(() => (
59 |
60 | ));
61 |
62 | const counter = screen.getByTestId('counter');
63 | const minusIcon = screen.getByTestId('minus-icon');
64 | const input = counter.querySelector('input');
65 |
66 | expect(input?.value).toBe('1');
67 | minusIcon.click();
68 | expect(input?.value).toBe('0');
69 | });
70 |
71 | it('should disable plus button if maxValue is reached', () => {
72 | renderWithRevKitThemeProvider(() => (
73 |
74 | ));
75 |
76 | const counter = screen.getByTestId('counter');
77 | const plusIcon = screen.getByTestId('plus-icon');
78 | const input = counter.querySelector('input');
79 |
80 | expect(input?.value).toBe('1');
81 | plusIcon.click();
82 | expect(input?.value).toBe('2');
83 | plusIcon.click();
84 | expect(input?.value).toBe('2');
85 | });
86 |
87 | it('should disable minus button if minValue is reached', () => {
88 | renderWithRevKitThemeProvider(() => (
89 |
90 | ));
91 |
92 | const counter = screen.getByTestId('counter');
93 | const minusIcon = screen.getByTestId('minus-icon');
94 | const input = counter.querySelector('input');
95 |
96 | expect(input?.value).toBe('1');
97 | minusIcon.click();
98 | expect(input?.value).toBe('0');
99 | minusIcon.click();
100 | expect(input?.value).toBe('-1');
101 | minusIcon.click();
102 | expect(input?.value).toBe('-2');
103 | minusIcon.click();
104 | expect(input?.value).toBe('-2');
105 | });
106 | });
--------------------------------------------------------------------------------
/src/components/counter/counter.tsx:
--------------------------------------------------------------------------------
1 | import { Component, createSignal } from 'solid-js';
2 | import { styled } from 'solid-styled-components';
3 | import { Icons } from '../icons';
4 | import { BaseInputProps } from '../input';
5 |
6 | export interface CounterProps extends BaseInputProps {
7 | value?: number;
8 | maxValue?: number;
9 | minValue?: number;
10 | }
11 |
12 | const CounterContainer = styled('div') <{ disabled?: boolean }>`
13 | display: inline-flex;
14 | align-items: center;
15 | height: 52px;
16 | background: ${props => props.disabled ? props.theme.colors.shade : props.theme.colors.bright};
17 | border-radius: 6px;
18 | `;
19 |
20 | const ControlButton = styled('button') <{
21 | side: 'left' | 'right'
22 | }>`
23 | display: inline-flex;
24 | justify-content: center;
25 | align-items: center;
26 | padding: 12px;
27 | width: 60px;
28 | background: transparent;
29 | border: unset;
30 | outline: unset;
31 | height: 100%;
32 | cursor: pointer;
33 |
34 | ${props => props.side === 'left' ?
35 | `
36 | border-top-left-radius: 6px;
37 | border-bottom-left-radius: 6px;
38 | `
39 | :
40 | `
41 | border-top-right-radius: 6px;
42 | border-bottom-right-radius: 6px;
43 | `}
44 |
45 | &:active {
46 | background: ${props => props.theme.colors.accent};
47 |
48 | & > span > svg > path {
49 | fill: ${props => props.theme.colors.bright};
50 | }
51 | }
52 |
53 | &:disabled {
54 | background: ${props => props.theme.colors.shade};
55 |
56 | & > span > svg > path {
57 | fill: ${props => props.theme.colors.secondary};
58 | }
59 | }
60 | `;
61 |
62 | const ValueInput = styled('input')`
63 | width: 60px;
64 | padding: 12px;
65 | outline: unset;
66 | border: unset;
67 | text-align: center;
68 | font-size: 16px;
69 | height: 100%;
70 | border-left: 1px solid ${props => props.theme.colors.shade};
71 | border-right: 1px solid ${props => props.theme.colors.shade};
72 | background: transparent;
73 |
74 | `;
75 |
76 | export const Counter: Component
= ({
77 | value = 0,
78 | disabled,
79 | maxValue = 999,
80 | minValue = -999,
81 | onInput,
82 | ...rest
83 | }) => {
84 | const [getValue, setValue] = createSignal(value);
85 |
86 | const handleInput = (e: Event) => {
87 | //@ts-ignore
88 | if (!(/^(0|-*[1-9]+[0-9]*)$/.test(e?.target?.value))) {
89 | //@ts-ignore
90 | e.target.value = e.target.value.slice(0, -1);
91 | }
92 | //@ts-ignore
93 | setValue(Number(e.target.value) ?? 0);
94 | onInput?.(e);
95 | };
96 |
97 | const incremenet = () => setValue(v => v + 1);
98 |
99 | const decrement = () => setValue(v => v - 1);
100 |
101 | return (
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | );
112 | };
113 |
--------------------------------------------------------------------------------
/src/components/counter/index.tsx:
--------------------------------------------------------------------------------
1 | import { Counter } from './counter';
2 |
3 | export { Counter };
--------------------------------------------------------------------------------
/src/components/icons/icons.tsx:
--------------------------------------------------------------------------------
1 | export interface IconProps {
2 | fill?: string;
3 | onClick?: (e: Event) => void;
4 | }
5 |
6 | export const RevIcon = {
7 | Plus: ({ fill }: IconProps) => (
8 |
11 | ),
12 | Burger: ({ fill }: IconProps) => (
13 |
16 | ),
17 | Cross: ({ fill }: IconProps) => (
18 |
21 | ),
22 | More: ({ fill }: IconProps) => (
23 |
26 | ),
27 | Minus: ({ fill }: IconProps) => (
28 |
31 | ),
32 | Lens: ({ fill }: IconProps) => (
33 |
36 | ),
37 | Circle: ({ fill }: IconProps) => (
38 |
41 | ),
42 | ChevronLeft: ({ fill }: IconProps) => (
43 |
46 | ),
47 | ChevronDown: ({ fill }: IconProps) => (
48 |
51 | ),
52 | Share: ({ fill }: IconProps) => (
53 |
56 | ),
57 | Heart: ({ fill }: IconProps) => (
58 |
61 | ),
62 | Activity: ({ fill }: IconProps) => (
63 |
66 | ),
67 | Alert: ({ fill }: IconProps) => (
68 |
71 | ),
72 | ArrowDown: ({ fill }: IconProps) => (
73 |
76 | ),
77 | ArrowLeft: ({ fill }: IconProps) => (
78 |
81 | ),
82 | ArrowRight: ({ fill }: IconProps) => (
83 |
86 | ),
87 | ArrowUp: ({ fill }: IconProps) => (
88 |
91 | ),
92 | Badge: ({ fill }: IconProps) => (
93 |
96 | ),
97 | Bag: ({ fill }: IconProps) => (
98 |
101 | ),
102 | Battery: ({ fill }: IconProps) => (
103 |
106 | ),
107 | Bell: ({ fill }: IconProps) => (
108 |
111 | ),
112 | Book: ({ fill }: IconProps) => (
113 |
116 | ),
117 | Box: ({ fill }: IconProps) => (
118 |
121 | ),
122 | Bullet: ({ fill }: IconProps) => (
123 |
126 | ),
127 | Calendar: ({ fill }: IconProps) => (
128 |
131 | ),
132 | Camera: ({ fill }: IconProps) => (
133 |
136 | ),
137 | Card: ({ fill }: IconProps) => (
138 |
141 | ),
142 | Cart: ({ fill }: IconProps) => (
143 |
146 | ),
147 | Check: ({ fill }: IconProps) => (
148 |
151 | ),
152 | ChevronRight: ({ fill }: IconProps) => (
153 |
156 | ),
157 | ChevronUp: ({ fill }: IconProps) => (
158 |
161 | ),
162 | Comment: ({ fill }: IconProps) => (
163 |
166 | ),
167 | Cookie: ({ fill }: IconProps) => (
168 |
171 | ),
172 | Currency: ({ fill }: IconProps) => (
173 |
176 | ),
177 | Desktop: ({ fill }: IconProps) => (
178 |
181 | ),
182 | Download: ({ fill }: IconProps) => (
183 |
186 | ),
187 | Equalizer: ({ fill }: IconProps) => (
188 |
191 | ),
192 | File: ({ fill }: IconProps) => (
193 |
196 | ),
197 | Flag: ({ fill }: IconProps) => (
198 |
201 | ),
202 | Folder: ({ fill }: IconProps) => (
203 |
206 | ),
207 | Gear: ({ fill }: IconProps) => (
208 |
211 | ),
212 | Diamond: ({ fill }: IconProps) => (
213 |
216 | ),
217 | GraphBar: ({ fill }: IconProps) => (
218 |
221 | ),
222 | GraphPie: ({ fill }: IconProps) => (
223 |
226 | ),
227 | GraphPoly: ({ fill }: IconProps) => (
228 |
231 | ),
232 | Home: ({ fill }: IconProps) => (
233 |
236 | ),
237 | Image: ({ fill }: IconProps) => (
238 |
241 | ),
242 | Info: ({ fill }: IconProps) => (
243 |
246 | ),
247 | Layers: ({ fill }: IconProps) => (
248 |
251 | ),
252 | Marker: ({ fill }: IconProps) => (
253 |
256 | ),
257 | Mobile: ({ fill }: IconProps) => (
258 |
261 | ),
262 | PaperBag: ({ fill }: IconProps) => (
263 |
266 | ),
267 | Pencil: ({ fill }: IconProps) => (
268 |
271 | ),
272 | Power: ({ fill }: IconProps) => (
273 |
276 | ),
277 | Shield: ({ fill }: IconProps) => (
278 |
281 | ),
282 | Square: ({ fill }: IconProps) => (
283 |
286 | ),
287 | Tag: ({ fill }: IconProps) => (
288 |
291 | ),
292 | Thunder: ({ fill }: IconProps) => (
293 |
296 | ),
297 | Ticket: ({ fill }: IconProps) => (
298 |
301 | ),
302 | Upload: ({ fill }: IconProps) => (
303 |
306 | ),
307 | User: ({ fill }: IconProps) => (
308 |
311 | ),
312 | VideoCamera: ({ fill }: IconProps) => (
313 |
316 | ),
317 | Wallet: ({ fill }: IconProps) => (
318 |
321 | ),
322 | Watch: ({ fill }: IconProps) => (
323 |
326 | ),
327 | Wrench: ({ fill }: IconProps) => (
328 |
331 | )
332 | };
--------------------------------------------------------------------------------
/src/components/icons/index.tsx:
--------------------------------------------------------------------------------
1 | import { JSXElement } from 'solid-js';
2 | import { styled } from 'solid-styled-components';
3 | import { IconProps, RevIcon } from './icons';
4 |
5 | export type { IconProps };
6 |
7 | export type IconElement = (props: IconProps) => JSXElement;
8 |
9 | const Icon = styled('span')`
10 | height: 20px;
11 | width: 20px;
12 | display: flex;
13 | justify-content: center;
14 | align-items: center;
15 | `;
16 |
17 | const Plus = ({ fill = '#2c2738', onClick }: IconProps) => ;
18 | const Cross = ({ fill = '#2c2738', onClick }: IconProps) => ;
19 | const Minus = ({ fill = '#2c2738', onClick }: IconProps) => ;
20 | const More = ({ fill = '#2c2738', onClick }: IconProps) => ;
21 | const Burger = ({ fill = '#2c2738', onClick }: IconProps) => ;
22 | const Lens = ({ fill = '#2c2738', onClick }: IconProps) => ;
23 | const Circle = ({ fill = '#2c2738', onClick }: IconProps) => ;
24 | const ChevronLeft = ({ fill = '#2c2738', onClick }: IconProps) => ;
25 | const ChevronDown = ({ fill = '#2c2738', onClick }: IconProps) => ;
26 | const Share = ({ fill = '#2c2738', onClick }: IconProps) => ;
27 | const Heart = ({ fill = '#2c2738', onClick }: IconProps) => ;
28 | const Activity = ({ fill = '#2c2738', onClick }: IconProps) => ;
29 | const Alert = ({ fill = '#2c2738', onClick }: IconProps) => ;
30 | const ArrowDown = ({ fill = '#2c2738', onClick }: IconProps) => ;
31 | const ArrowUp = ({ fill = '#2c2738', onClick }: IconProps) => ;
32 | const ArrowLeft = ({ fill = '#2c2738', onClick }: IconProps) => ;
33 | const ArrowRight = ({ fill = '#2c2738', onClick }: IconProps) => ;
34 | const Badge = ({ fill = '#2c2738', onClick }: IconProps) => ;
35 | const Bag = ({ fill = '#2c2738', onClick }: IconProps) => ;
36 | const Battery = ({ fill = '#2c2738', onClick }: IconProps) => ;
37 | const Bell = ({ fill = '#2c2738', onClick }: IconProps) => ;
38 | const Book = ({ fill = '#2c2738', onClick }: IconProps) => ;
39 | const Box = ({ fill = '#2c2738', onClick }: IconProps) => ;
40 | const Bullet = ({ fill = '#2c2738', onClick }: IconProps) => ;
41 | const Calendar = ({ fill = '#2c2738', onClick }: IconProps) => ;
42 | const Camera = ({ fill = '#2c2738', onClick }: IconProps) => ;
43 | const Card = ({ fill = '#2c2738', onClick }: IconProps) => ;
44 | const Cart = ({ fill = '#2c2738', onClick }: IconProps) => ;
45 | const Check = ({ fill = '#2c2738', onClick }: IconProps) => ;
46 | const ChevronRight = ({ fill = '#2c2738', onClick }: IconProps) => ;
47 | const ChevronUp = ({ fill = '#2c2738', onClick }: IconProps) => ;
48 | const Comment = ({ fill = '#2c2738', onClick }: IconProps) => ;
49 | const Cookie = ({ fill = '#2c2738', onClick }: IconProps) => ;
50 | const Currency = ({ fill = '#2c2738', onClick }: IconProps) => ;
51 | const Desktop = ({ fill = '#2c2738', onClick }: IconProps) => ;
52 | const Diamond = ({ fill = '#2c2738', onClick }: IconProps) => ;
53 | const Download = ({ fill = '#2c2738', onClick }: IconProps) => ;
54 | const Equalizer = ({ fill = '#2c2738', onClick }: IconProps) => ;
55 | const File = ({ fill = '#2c2738', onClick }: IconProps) => ;
56 | const Flag = ({ fill = '#2c2738', onClick }: IconProps) => ;
57 | const Folder = ({ fill = '#2c2738', onClick }: IconProps) => ;
58 | const Gear = ({ fill = '#2c2738', onClick }: IconProps) => ;
59 | const GraphBar = ({ fill = '#2c2738', onClick }: IconProps) => ;
60 | const GraphPie = ({ fill = '#2c2738', onClick }: IconProps) => ;
61 | const GraphPoly = ({ fill = '#2c2738', onClick }: IconProps) => ;
62 | const Home = ({ fill = '#2c2738', onClick }: IconProps) => ;
63 | const Image = ({ fill = '#2c2738', onClick }: IconProps) => ;
64 | const Info = ({ fill = '#2c2738', onClick }: IconProps) => ;
65 | const Layers = ({ fill = '#2c2738', onClick }: IconProps) => ;
66 | const Marker = ({ fill = '#2c2738', onClick }: IconProps) => ;
67 | const Mobile = ({ fill = '#2c2738', onClick }: IconProps) => ;
68 | const PaperBag = ({ fill = '#2c2738', onClick }: IconProps) => ;
69 | const Pencil = ({ fill = '#2c2738', onClick }: IconProps) => ;
70 | const Power = ({ fill = '#2c2738', onClick }: IconProps) => ;
71 | const Shield = ({ fill = '#2c2738', onClick }: IconProps) => ;
72 | const Square = ({ fill = '#2c2738', onClick }: IconProps) => ;
73 | const Tag = ({ fill = '#2c2738', onClick }: IconProps) => ;
74 | const Thunder = ({ fill = '#2c2738', onClick }: IconProps) => ;
75 | const Ticket = ({ fill = '#2c2738', onClick }: IconProps) => ;
76 | const Upload = ({ fill = '#2c2738', onClick }: IconProps) => ;
77 | const User = ({ fill = '#2c2738', onClick }: IconProps) => ;
78 | const VideoCamera = ({ fill = '#2c2738', onClick }: IconProps) => ;
79 | const Wallet = ({ fill = '#2c2738', onClick }: IconProps) => ;
80 | const Watch = ({ fill = '#2c2738', onClick }: IconProps) => ;
81 | const Wrench = ({ fill = '#2c2738', onClick }: IconProps) => ;
82 |
83 | export const Icons = Object.assign({}, {
84 | Burger,
85 | ChevronLeft,
86 | ChevronDown,
87 | Circle,
88 | Cross,
89 | Heart,
90 | Lens,
91 | Minus,
92 | More,
93 | Plus,
94 | Share,
95 | Activity,
96 | Alert,
97 | ArrowDown,
98 | ArrowUp,
99 | ArrowLeft,
100 | ArrowRight,
101 | Badge,
102 | Bag,
103 | Battery,
104 | Bell,
105 | Book,
106 | Box,
107 | Bullet,
108 | Calendar,
109 | Camera,
110 | Card,
111 | Cart,
112 | Check,
113 | ChevronRight,
114 | ChevronUp,
115 | Comment,
116 | Cookie,
117 | Currency,
118 | Desktop,
119 | Diamond,
120 | Download,
121 | Equalizer,
122 | File,
123 | Flag,
124 | Folder,
125 | Gear,
126 | GraphBar,
127 | GraphPie,
128 | GraphPoly,
129 | Home,
130 | Image,
131 | Info,
132 | Layers,
133 | Marker,
134 | Mobile,
135 | PaperBag,
136 | Pencil,
137 | Power,
138 | Shield,
139 | Square,
140 | Tag,
141 | Thunder,
142 | Ticket,
143 | Upload,
144 | User,
145 | VideoCamera,
146 | Wallet,
147 | Watch,
148 | Wrench
149 | });
--------------------------------------------------------------------------------
/src/components/input/README.md:
--------------------------------------------------------------------------------
1 | # Input component
2 |
3 | ### Usage
4 |
5 | ```jsx
6 | import { Input, Icons } from '@specialdoom/solid-rev-kit';
7 |
8 | const Container = () => (
9 | <>
10 |
11 |
12 |
13 |
14 | } />
15 | >
16 | );
17 | ```
18 |
19 | ### API
20 |
21 | | Property | Description | Type | Default |
22 | | ----------- | --------------------------------------------------------------------- | ------------------ | --------- |
23 | | value | Value of input component | string | undefined |
24 | | placeholder | Placeholder of input component | string | undefined |
25 | | disabled | Whether the input component is disabled | boolean | false |
26 | | icon | Icon of input component. Decides whether the input component has icon | JSXElement | undefined |
27 | | onChange | onChange event hanlder | (e: Event) => void | undefined |
28 | | onFocus | onFocus event hanlder | (e: Event) => void | undefined |
29 | | onInput | onInput event hanlder | (e: Event) => void | undefined |
30 | | onBlur | onBlur event hanlder | (e: Event) => void | undefined |
31 |
32 | # Textarea component
33 |
34 | ### Usage
35 |
36 | ```jsx
37 | import { Input, Icons } from '@specialdoom/solid-rev-kit';
38 |
39 | const Container = () => (
40 | <>
41 |
42 |
43 |
44 |
45 |
46 | >
47 | );
48 | ```
49 |
50 | ### API
51 |
52 | | Property | Description | Type | Default |
53 | | ----------- | ------------------------------------------ | ------------------ | --------- |
54 | | value | Value of textarea component | string | undefined |
55 | | placeholder | Placeholder of textarea component | string | undefined |
56 | | disabled | Whether the textarea component is disabled | boolean | false |
57 | | rows | Rows number of textarea component | number | 4 |
58 | | onChange | onChange event hanlder | (e: Event) => void | undefined |
59 | | onFocus | onFocus event hanlder | (e: Event) => void | undefined |
60 | | onInput | onInput event hanlder | (e: Event) => void | undefined |
61 | | onBlur | onBlur event hanlder | (e: Event) => void | undefined |
62 |
--------------------------------------------------------------------------------
/src/components/input/index.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from './input';
2 | import { TextArea } from './text-area';
3 |
4 | export { Input, TextArea };
5 |
6 | interface BaseInputProps {
7 | placeholder?: string;
8 | disabled?: boolean;
9 | onChange?: (event: Event) => void;
10 | onBlur?: (event: Event) => void;
11 | onInput?: (event: Event) => void;
12 | onFocus?: (event: Event) => void;
13 | }
14 |
15 | export type { BaseInputProps };
--------------------------------------------------------------------------------
/src/components/input/input.test.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils';
3 | import { screen, cleanup } from 'solid-testing-library';
4 | import { Input } from './input';
5 | import { Icons } from '../icons';
6 |
7 |
8 | describe('Input', () => {
9 | afterEach(() => {
10 | jest.clearAllMocks();
11 | cleanup();
12 | });
13 |
14 | it('should render', () => {
15 | renderWithRevKitThemeProvider(() => (
16 |
17 | ));
18 |
19 | const input = screen.getByTestId('input');
20 |
21 | expect(input).toBeInTheDocument();
22 | });
23 |
24 | it('should render a element', () => {
25 | renderWithRevKitThemeProvider(() => (
26 |
27 | ));
28 |
29 | const input = screen.getByTestId('input');
30 |
31 | expect(input).toBeInstanceOf(HTMLDivElement);
32 | });
33 |
34 | it('should have placeholder attribute', () => {
35 | const placeholder = 'Placeholder';
36 | renderWithRevKitThemeProvider(() => (
37 |
38 | ));
39 |
40 | const input = screen.getByTestId('input');
41 | const inputElement = input.querySelector('input');
42 |
43 | expect(inputElement).toHaveAttribute('placeholder', placeholder);
44 | });
45 |
46 | it('should render icon', () => {
47 | renderWithRevKitThemeProvider(() => (
48 |
} />
49 | ));
50 |
51 | const input = screen.getByTestId('input');
52 | const icon = screen.getByTestId('lens-icon');
53 |
54 | expect(input).toContainElement(icon);
55 | });
56 | });
--------------------------------------------------------------------------------
/src/components/input/input.tsx:
--------------------------------------------------------------------------------
1 | import { Component, JSXElement, Show } from 'solid-js';
2 | import { styled } from 'solid-styled-components';
3 | import { BaseInputProps } from '.';
4 |
5 | export interface InputProps extends BaseInputProps {
6 | value?: string;
7 | icon?: JSXElement;
8 | }
9 |
10 | const InputContainer = styled('div') <{
11 | disabled?: boolean
12 | }>`
13 | display: inline-flex;
14 | justify-content: space-between;
15 | align-items: center;
16 | height: 52px;
17 | outline: unset;
18 | border-radius: 6px;
19 | background: ${props => props.disabled ? props.theme.colors.shade : props.theme.colors.bright};
20 | border: 1px solid ${props => props.theme.colors.shade};
21 | font-size: 16px;
22 | box-sizing: border-box;
23 | gap: 16px;
24 | padding: 0 16px;
25 | min-width: 360px;
26 |
27 | &:focus-within {
28 | outline: none;
29 | border: 2px solid ${props => props.theme.colors.accent};
30 | }
31 | `;
32 |
33 | const StyledInput = styled('input')`
34 | outline: unset;
35 | background: transparent;
36 | border: unset;
37 | font-size: 16px;
38 | margin: 16px 0;
39 | width: 100%;
40 |
41 | &::placeholder {
42 | color: ${props => props.theme.colors.muted};
43 | }
44 |
45 | &:disabled, &:disabled::placeholder {
46 | color: ${props => props.theme.colors.secondary};
47 | }
48 | `;
49 |
50 | export const Input: Component
= ({ icon, disabled, ...rest }) => (
51 |
52 |
58 |
59 | {icon}
60 |
61 |
62 | );
63 |
--------------------------------------------------------------------------------
/src/components/input/text-area.test.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils';
3 | import { screen, cleanup } from 'solid-testing-library';
4 | import { TextArea } from './text-area';
5 |
6 |
7 | describe('Input', () => {
8 | afterEach(() => {
9 | jest.clearAllMocks();
10 | cleanup();
11 | });
12 |
13 | it('should render', () => {
14 | renderWithRevKitThemeProvider(() => (
15 |
16 | ));
17 |
18 | const textArea = screen.getByTestId('text-area');
19 |
20 | expect(textArea).toBeInTheDocument();
21 | });
22 |
23 | it('should render