├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── README.md ├── babel.config.json ├── dev.tsx ├── index.html ├── index.ts ├── jest.config.js ├── jest.setup.ts ├── package-lock.json ├── package.json ├── src ├── assets │ └── images │ │ ├── jake.png │ │ ├── logo.svg │ │ ├── marble.png │ │ ├── meg.png │ │ ├── mili.png │ │ └── steven.png ├── components │ ├── alert │ │ ├── README.md │ │ ├── alert.test.tsx │ │ ├── alert.tsx │ │ └── index.tsx │ ├── avatar │ │ ├── README.md │ │ ├── avatar.test.tsx │ │ ├── avatar.tsx │ │ ├── default-avatar.test.tsx │ │ ├── default-avatar.tsx │ │ └── index.tsx │ ├── button │ │ ├── README.md │ │ ├── button.test.tsx │ │ ├── button.tsx │ │ └── index.tsx │ ├── callout │ │ ├── README.md │ │ ├── callout.test.tsx │ │ ├── callout.tsx │ │ └── index.tsx │ ├── card │ │ ├── README.md │ │ ├── fill-card.test.tsx │ │ ├── fill-card.tsx │ │ ├── generic-card.test.tsx │ │ ├── generic-card.tsx │ │ └── index.tsx │ ├── chat-bubble │ │ ├── README.md │ │ ├── chat-bubble.test.tsx │ │ ├── chat-bubble.tsx │ │ └── index.tsx │ ├── counter │ │ ├── README.md │ │ ├── counter.test.tsx │ │ ├── counter.tsx │ │ └── index.tsx │ ├── icons │ │ ├── icons.tsx │ │ └── index.tsx │ ├── input │ │ ├── README.md │ │ ├── index.tsx │ │ ├── input.test.tsx │ │ ├── input.tsx │ │ ├── text-area.test.tsx │ │ └── text-area.tsx │ ├── modal │ │ ├── README.md │ │ ├── index.tsx │ │ ├── modal.test.tsx │ │ └── modal.tsx │ ├── progress │ │ ├── README.md │ │ ├── index.tsx │ │ ├── progress.test.tsx │ │ └── progress.tsx │ ├── select │ │ ├── README.md │ │ ├── index.tsx │ │ ├── select.test.tsx │ │ └── select.tsx │ ├── space │ │ ├── README.md │ │ ├── index.tsx │ │ ├── space.test.tsx │ │ └── space.tsx │ ├── spinner │ │ ├── README.md │ │ ├── index.tsx │ │ ├── spinner.test.tsx │ │ └── spinner.tsx │ ├── switch │ │ ├── README.md │ │ ├── index.tsx │ │ ├── switch.test.tsx │ │ └── switch.tsx │ ├── tag │ │ ├── README.md │ │ ├── index.tsx │ │ ├── tag.test.tsx │ │ └── tag.tsx │ ├── theme-provider │ │ ├── README.md │ │ ├── global-style.tsx │ │ ├── theme-provider.tsx │ │ └── theme.ts │ ├── tooltip │ │ ├── README.md │ │ ├── index.tsx │ │ ├── tooltip-style.ts │ │ └── tooltip.tsx │ └── typography │ │ ├── README.md │ │ ├── heading.tsx │ │ ├── index.tsx │ │ ├── label.tsx │ │ └── paragraph.tsx ├── directives │ ├── clickOutside.ts │ ├── index.ts │ └── tippyTooltip.ts └── utils │ └── test-utils.tsx ├── tsconfig.json ├── tsconfig.test.json └── vite.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | types -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "rules": { 11 | "no-console": 1, 12 | "quotes": [1, "single"], 13 | "semi": [1, "always"], 14 | "@typescript-eslint/ban-ts-comment": [1], 15 | "@typescript-eslint/no-empty-function": [1], 16 | "@typescript-eslint/no-namespace": [1], 17 | "no-useless-escape": [1] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build-test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [16.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - name: npm ci, build and test 24 | run: | 25 | npm ci 26 | npm run build 27 | npm run test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | types 4 | *.tgz 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

RevKit UI system for SolidJS 8 |

9 | 10 | Part of solid js [hackathon](https://hack.solidjs.com/) 11 | 12 | ## Resources 13 | 14 | [RevKit UI Design System ](https://rev.webkul.design/kit/) 15 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { "targets": { "node": "current" } }], 4 | "@babel/preset-typescript" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /dev.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'solid-js'; 2 | import { render } from 'solid-js/web'; 3 | import { RevKitTheme } from "./src/components/theme-provider/theme-provider"; 4 | import { Button } from "./src/components/button/button"; 5 | 6 | const App: Component = () => ( 7 |
8 | 9 | 10 | 11 |
12 | ); 13 | 14 | render(() => , document.getElementById('root') as HTMLElement) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | DEV: Solid RevKit 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | // components 2 | import { Alert } from './src/components/alert'; 3 | import { Avatar } from './src/components/avatar'; 4 | import { Button } from './src/components/button'; 5 | import { Callout } from './src/components/callout'; 6 | import { Card } from './src/components/card'; 7 | import { ChatBubble } from './src/components/chat-bubble'; 8 | import { Counter } from './src/components/counter'; 9 | import { Typography } from './src/components/typography'; 10 | import { Icons } from './src/components/icons'; 11 | import { Input, TextArea } from './src/components/input'; 12 | import { Modal } from './src/components/modal'; 13 | import { Progress } from './src/components/progress'; 14 | import { RevKitTheme } from './src/components/theme-provider/theme-provider'; 15 | import { Select } from './src/components/select'; 16 | import { Space } from './src/components/space'; 17 | import { Switch } from './src/components/switch'; 18 | import { Spinner } from './src/components/spinner'; 19 | import { Tag } from './src/components/tag'; 20 | import { Tooltip } from './src/components/tooltip/tooltip'; 21 | 22 | // constants 23 | import { theme } from './src/components/theme-provider/theme'; 24 | 25 | export { 26 | Alert, 27 | Avatar, 28 | Button, 29 | Callout, 30 | Card, 31 | ChatBubble, 32 | Counter, 33 | Icons, 34 | Input, 35 | Modal, 36 | Progress, 37 | RevKitTheme, 38 | Select, 39 | Spinner, 40 | Space, 41 | Switch, 42 | Tag, 43 | TextArea, 44 | Tooltip, 45 | Typography 46 | }; 47 | 48 | export const revConstants = { theme }; 49 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'solid-jest/preset/browser', 3 | setupFilesAfterEnv: ['/jest.setup.ts'] 4 | }; 5 | -------------------------------------------------------------------------------- /jest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@specialdoom/solid-rev-kit", 3 | "version": "0.0.27", 4 | "description": "RevKit UI implementation for SolidJS", 5 | "info": "RevKit UI is a revolutionary design system kit for busy designers", 6 | "homepage": "https://specialdoom.github.io/solid-rev-kit-docs", 7 | "keywords": [ 8 | "solidhack", 9 | "best_ecosystem", 10 | "solidjs", 11 | "ui", 12 | "frontend", 13 | "components" 14 | ], 15 | "contributors": [ 16 | { 17 | "name": "Specialdoom", 18 | "email": "bledea.bogdan97@gmail.com", 19 | "url": "https://github.com/specialdoom" 20 | } 21 | ], 22 | "files": [ 23 | "dist", 24 | "src", 25 | "types" 26 | ], 27 | "main": "./dist/solid-rev-kit.cjs", 28 | "module": "./dist/solid-rev-kit.mjs", 29 | "exports": { 30 | ".": { 31 | "import": "./dist/solid-rev-kit.mjs", 32 | "require": "./dist/solid-rev-kit.cjs" 33 | } 34 | }, 35 | "types": "types/index.d.ts", 36 | "scripts": { 37 | "dev": "vite", 38 | "build": "vite build", 39 | "lint": "eslint . --ext .ts,.tsx", 40 | "test": "jest", 41 | "prepublishOnly": "npm run build" 42 | }, 43 | "license": "MIT", 44 | "devDependencies": { 45 | "@babel/preset-typescript": "^7.16.7", 46 | "@testing-library/jest-dom": "^5.16.2", 47 | "@types/jest": "^27.4.1", 48 | "@types/node": "^17.0.10", 49 | "@typescript-eslint/eslint-plugin": "^5.10.1", 50 | "@typescript-eslint/parser": "^5.10.1", 51 | "babel-preset-solid": "^1.3.6", 52 | "eslint": "^8.7.0", 53 | "jest": "^27.5.1", 54 | "solid-jest": "^0.2.0", 55 | "solid-testing-library": "^0.3.0", 56 | "typescript": "^4.5.4", 57 | "vite": "^3.2.4", 58 | "vite-plugin-solid": "^2.4.0" 59 | }, 60 | "peerDependencies": { 61 | "solid-js": "^1.6.0" 62 | }, 63 | "dependencies": { 64 | "solid-styled-components": "^0.27.6", 65 | "tippy.js": "^6.3.7" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/assets/images/jake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/specialdoom/solid-rev-kit/93dbe40dcb597d2a6df0da6b95c31cfd67812ef4/src/assets/images/jake.png -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/marble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/specialdoom/solid-rev-kit/93dbe40dcb597d2a6df0da6b95c31cfd67812ef4/src/assets/images/marble.png -------------------------------------------------------------------------------- /src/assets/images/meg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/specialdoom/solid-rev-kit/93dbe40dcb597d2a6df0da6b95c31cfd67812ef4/src/assets/images/meg.png -------------------------------------------------------------------------------- /src/assets/images/mili.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/specialdoom/solid-rev-kit/93dbe40dcb597d2a6df0da6b95c31cfd67812ef4/src/assets/images/mili.png -------------------------------------------------------------------------------- /src/assets/images/steven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/specialdoom/solid-rev-kit/93dbe40dcb597d2a6df0da6b95c31cfd67812ef4/src/assets/images/steven.png -------------------------------------------------------------------------------- /src/components/alert/README.md: -------------------------------------------------------------------------------- 1 | # Alert component 2 | 3 | ### Usage 4 | 5 | ```jsx 6 | import { Alert } from '@specialdoom/solid-rev-kit'; 7 | 8 | const Container = () => ( 9 | <> 10 | 11 | A bright alert flash for dark backgrounds, which never lose the contrast. 12 | 13 | 14 | A dark (primary type) alert flash for bright backgrounds, which never lose 15 | the contrast. 16 | 17 | 18 | A success alert flash, which never lose the contrast. 19 | 20 | A warning alert flash that never sucks. 21 | An error alert flash that nobody loves. 22 | An accent alert flash that looks pretty nice. 23 | 24 | ); 25 | ``` 26 | 27 | ### API 28 | 29 | | Property | Description | Type | Default | 30 | | -------- | --------------------------------------------------------------------------------- | -------------------------------------------- | -------- | 31 | | type | Type of alert. Options: 'accent', 'error', 'warning', 'success', 'dark', 'bright' | string | 'accent' | 32 | | color | Text and icon color | keyof [Colors](https://tinyurl.com/2p97bv3t) | 'bright' | 33 | -------------------------------------------------------------------------------- /src/components/alert/alert.test.tsx: -------------------------------------------------------------------------------- 1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils'; 2 | import { screen, cleanup } from 'solid-testing-library'; 3 | import { Alert, AlertType } from './alert'; 4 | 5 | const alertTypes: AlertType[] = ['accent', 'success', 'warning', 'error', 'dark', 'bright']; 6 | 7 | describe('Alert', () => { 8 | afterEach(() => { 9 | jest.clearAllMocks(); 10 | cleanup(); 11 | }); 12 | 13 | it('should render', () => { 14 | renderWithRevKitThemeProvider(() => test); 15 | 16 | const alert = screen.getByTestId('alert'); 17 | 18 | expect(alert).toBeInTheDocument(); 19 | }); 20 | 21 | it('should render a
element', () => { 22 | renderWithRevKitThemeProvider(() => Alert); 23 | 24 | const alert = screen.getByTestId('alert'); 25 | 26 | expect(alert).toBeInstanceOf(HTMLDivElement); 27 | }); 28 | 29 | it('should render accent type and color bright by default', () => { 30 | renderWithRevKitThemeProvider(() => Alert); 31 | 32 | const alert = screen.getByTestId('alert'); 33 | 34 | expect(alert).toHaveAttribute('type', 'accent'); 35 | expect(alert).toHaveAttribute('color', 'bright'); 36 | }); 37 | 38 | alertTypes.forEach(value => { 39 | it(`should render ${value} type alert`, () => { 40 | renderWithRevKitThemeProvider(() => Alert); 41 | 42 | const alert = screen.getByTestId('alert'); 43 | 44 | expect(alert).toHaveAttribute('type', value); 45 | }); 46 | }); 47 | 48 | it('should render children', () => { 49 | const children = 'Alert'; 50 | 51 | renderWithRevKitThemeProvider(() => {children}); 52 | 53 | const alert = screen.getByTestId('alert'); 54 | 55 | expect(alert).toHaveTextContent(children); 56 | }); 57 | 58 | it('should destroy if click on close icon', () => { 59 | renderWithRevKitThemeProvider(() => Closable alert); 60 | 61 | const alert = screen.getByTestId('alert'); 62 | expect(alert).toBeInTheDocument(); 63 | 64 | const crossIcon = screen.getByTestId('cross-icon'); 65 | crossIcon.click(); 66 | 67 | expect(alert).not.toBeInTheDocument(); 68 | }); 69 | }); -------------------------------------------------------------------------------- /src/components/alert/alert.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createSignal, JSXElement, Show } from 'solid-js'; 2 | import { styled } from 'solid-styled-components'; 3 | import { Icons } from '../icons'; 4 | import { Colors } from '../theme-provider/theme'; 5 | import { Typography } from '../typography'; 6 | 7 | const { Cross } = Icons; 8 | 9 | export type AlertType = 'accent' | 'success' | 'warning' | 'error' | 'dark' | 'bright'; 10 | 11 | export interface AlertProps { 12 | type?: AlertType; 13 | color?: keyof Colors; 14 | children?: JSXElement; 15 | } 16 | 17 | const StyledAlert = styled('div')<{ 18 | type: AlertType, 19 | color: keyof Colors, 20 | }>` 21 | background-color: ${(props) => props.theme.colors[props.type]}; 22 | box-sizing: border-box; 23 | box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px; 24 | width: 100%; 25 | display: flex; 26 | justify-content: space-between; 27 | align-items: center; 28 | padding: 15px 24px; 29 | border-radius: 10px; 30 | color: ${props => props.theme.colors[props.color]}; 31 | font-weight: 400; 32 | gap: 8px; 33 | 34 | & svg { 35 | cursor: pointer; 36 | 37 | & path { 38 | fill: ${props => props.theme.colors[props.color]}; 39 | } 40 | } 41 | `; 42 | 43 | export const Alert: Component = ({ 44 | type = 'accent', 45 | color = 'bright', 46 | children 47 | }) => { 48 | const [getClosed, setClosed] = createSignal(false); 49 | 50 | return ( 51 | 52 | 53 | {children} 54 | setClosed(true)} /> 55 | 56 | 57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /src/components/alert/index.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertProps, AlertType } from './alert'; 2 | 3 | export { Alert }; 4 | export type { AlertProps, AlertType }; -------------------------------------------------------------------------------- /src/components/avatar/README.md: -------------------------------------------------------------------------------- 1 | # Avatar component 2 | 3 | ### Usage 4 | 5 | ```jsx 6 | import { Avatar } from '@specialdoom/solid-rev-kit'; 7 | 8 | const Container = () => ( 9 | <> 10 | 11 | 12 | 13 | ``` 14 | 15 | ### API 16 | 17 | | Property | Description | Type | Default | 18 | | -------- | --------------------------- | ------- | --------- | 19 | | initials | Display initials | string | undefined | 20 | | round | Whether the avatar is round | boolean | false | 21 | 22 | # Default avatars 23 | 24 | There are 4 named default avatars: Steven, Jake, Mili and Meg. 25 | 26 | ```jsx 27 | import { Avatar } from '@specialdoom/solid-rev-kit'; 28 | 29 | const Container = () => ( 30 | <> 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | ``` 42 | 43 | ### API 44 | 45 | | Property | Description | Type | Default | 46 | | -------- | --------------------------- | ------- | ------- | 47 | | round | Whether the avatar is round | boolean | false | 48 | -------------------------------------------------------------------------------- /src/components/avatar/avatar.test.tsx: -------------------------------------------------------------------------------- 1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils'; 2 | import { screen, cleanup } from 'solid-testing-library'; 3 | import { Avatar } from './avatar'; 4 | 5 | describe('Avatar', () => { 6 | afterEach(() => { 7 | jest.clearAllMocks(); 8 | cleanup(); 9 | }); 10 | 11 | it('should render', () => { 12 | renderWithRevKitThemeProvider(() => ); 13 | 14 | const avatar = screen.getByTestId('avatar'); 15 | 16 | expect(avatar).toBeInTheDocument(); 17 | }); 18 | 19 | it('should render a
element', () => { 20 | renderWithRevKitThemeProvider(() => ); 21 | 22 | const avatar = screen.getByTestId('avatar'); 23 | 24 | expect(avatar).toBeInstanceOf(HTMLDivElement); 25 | }); 26 | 27 | it('should render round avatar', () => { 28 | renderWithRevKitThemeProvider(() => ); 29 | 30 | const avatar = screen.getByTestId('avatar'); 31 | 32 | expect(avatar).toBeInTheDocument(); 33 | expect(avatar).toHaveAttribute('round'); 34 | }); 35 | 36 | it('should render initials as children', () => { 37 | const initials = 'RK'; 38 | 39 | renderWithRevKitThemeProvider(() => ); 40 | 41 | const avatar = screen.getByTestId('avatar'); 42 | 43 | expect(avatar).toHaveTextContent(initials); 44 | }); 45 | }); -------------------------------------------------------------------------------- /src/components/avatar/avatar.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'solid-js'; 2 | import { styled } from 'solid-styled-components'; 3 | 4 | export interface AvatarProps { 5 | initials?: string; 6 | round?: boolean; 7 | } 8 | 9 | const StyledAvatar = styled('div') <{ 10 | round: boolean; 11 | }>` 12 | height: 56px; 13 | width: 56px; 14 | display: flex; 15 | border-radius: ${props => props.round ? '50%' : '4px'}; 16 | justify-content: center; 17 | align-items: center; 18 | font-size: 16px; 19 | background: ${props => props.theme.colors.muted}; 20 | color: ${props => props.theme.colors.bright}; 21 | font-weight: bold; 22 | `; 23 | 24 | export const Avatar: Component = ({ initials, round = false}) => ( 25 | 26 | {initials} 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /src/components/avatar/default-avatar.test.tsx: -------------------------------------------------------------------------------- 1 | import { renderWithRevKitThemeProvider } from '../../utils/test-utils'; 2 | import { screen, cleanup } from 'solid-testing-library'; 3 | import { DefaultAvatar, DefaultAvatarType } from './default-avatar'; 4 | 5 | const defaultAvatarTypes: DefaultAvatarType[] = ['jake', 'mili', 'meg']; 6 | 7 | describe('Default avatar', () => { 8 | afterEach(() => { 9 | jest.clearAllMocks(); 10 | cleanup(); 11 | }); 12 | 13 | it('should render', () => { 14 | renderWithRevKitThemeProvider(() => ); 15 | 16 | const defaultAvatar = screen.getByTestId('default-avatar'); 17 | 18 | expect(defaultAvatar).toBeInTheDocument(); 19 | }); 20 | 21 | it('should render steven type by default', () => { 22 | renderWithRevKitThemeProvider(() => ); 23 | 24 | const defaultAvatar = screen.getByTestId('default-avatar'); 25 | 26 | expect(defaultAvatar).toHaveAttribute('type', 'steven'); 27 | }); 28 | 29 | it('should render round avatar', () => { 30 | renderWithRevKitThemeProvider(() => ); 31 | 32 | const defaultAvatar = screen.getByTestId('avatar'); 33 | 34 | expect(defaultAvatar).toHaveAttribute('round'); 35 | }); 36 | 37 | 38 | it('should render a
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 ); 23 | 24 | const button = screen.getByTestId('button'); 25 | 26 | expect(button).toBeInstanceOf(HTMLButtonElement); 27 | }); 28 | 29 | it('should render accent variant button by default', () => { 30 | renderWithRevKitThemeProvider(() => ); 31 | 32 | const button = screen.getByTestId('button'); 33 | 34 | expect(button).toHaveAttribute('variant', 'accent'); 35 | }); 36 | 37 | it('should render small button', () => { 38 | renderWithRevKitThemeProvider(() => ); 39 | 40 | const button = screen.getByTestId('button'); 41 | 42 | expect(button).toHaveAttribute('small'); 43 | }); 44 | 45 | it('should render disabled button', () => { 46 | renderWithRevKitThemeProvider(() => ); 47 | 48 | const button = screen.getByTestId('button'); 49 | 50 | expect(button).toHaveAttribute('disabled'); 51 | }); 52 | 53 | buttonVariants.forEach(variant => { 54 | it(`should render ${variant} variant button`, () => { 55 | renderWithRevKitThemeProvider(() => ); 56 | 57 | const button = screen.getByTestId('button'); 58 | 59 | expect(button).toHaveAttribute('variant', variant); 60 | }); 61 | }); 62 | 63 | it('should fire onClick event handler', () => { 64 | jest.spyOn(window, 'alert').mockImplementation(() => { }); 65 | 66 | const onClickHandler = () => alert('something'); 67 | renderWithRevKitThemeProvider(() => ); 68 | 69 | const button = screen.getByTestId('button'); 70 | button.click(); 71 | 72 | expect(window.alert).toHaveBeenCalled(); 73 | }); 74 | 75 | it('should render children', () => { 76 | const children = 'Children'; 77 | renderWithRevKitThemeProvider(() => ); 78 | 79 | const button = screen.getByTestId('button'); 80 | 81 | expect(button).toHaveTextContent(children); 82 | }); 83 | }); -------------------------------------------------------------------------------- /src/components/button/button.tsx: -------------------------------------------------------------------------------- 1 | import { Component, JSXElement } from 'solid-js'; 2 | import { styled } from 'solid-styled-components'; 3 | 4 | export type ButtonType = 'bright' | 'ghost' | 'accent'; 5 | 6 | export interface ButtonProps { 7 | variant?: ButtonType; 8 | small?: boolean; 9 | disabled?: boolean; 10 | onClick?: (event: MouseEvent) => void; 11 | children: JSXElement; 12 | } 13 | 14 | function getStylingByVariant(variant: ButtonType, props: any) { 15 | if (variant === "bright") { 16 | return ` 17 | background: ${props.theme.colors.bright}; 18 | color: ${props.theme.colors.primary}; 19 | box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px; 20 | &:hover { 21 | box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; 22 | } 23 | &:active { 24 | border: 2px solid ${props.theme.colors.primary}; 25 | box-shadow: unset; 26 | } 27 | &:disabled { 28 | background: ${props.theme.colors.shade}; 29 | color: rgba(44, 39, 56, 0.24); 30 | box-shadow: unset; 31 | } 32 | ` 33 | } 34 | 35 | if (variant === "accent") { 36 | return ` 37 | background: ${props.theme.colors.accent}; 38 | color: ${props.theme.colors.bright}; 39 | &:hover { 40 | box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; 41 | } 42 | &:active { 43 | border: 2px solid ${props.theme.colors.primary}; 44 | box-shadow: unset; 45 | } 46 | &:disabled { 47 | background: ${props.theme.colors.shade}; 48 | color: rgba(44, 39, 56, 0.24); 49 | box-shadow: unset; 50 | } 51 | ` 52 | } 53 | 54 | if (variant === "ghost") { 55 | return ` 56 | background: ${props.theme.colors.bright}; 57 | color: ${props.theme.colors.muted}; 58 | border: 2px solid ${props.theme.colors.muted}; 59 | box-shadow: unset; 60 | &:hover { 61 | color: ${props.theme.colors.accent}; 62 | border-color: ${props.theme.colors.accent}; 63 | box-shadow: unset; 64 | } 65 | &:active { 66 | color: ${props.theme.colors.secondary}; 67 | border-color: ${props.theme.colors.secondary}; 68 | } 69 | &:disabled { 70 | background: transparent; 71 | color: rgba(44, 39, 56, 0.24); 72 | border-color: rgba(44, 39, 56, 0.24); 73 | } 74 | ` 75 | } 76 | 77 | return ` 78 | background: ${props.theme.colors.accent}; 79 | color: ${props.theme.colors.bright}; 80 | &:hover { 81 | box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; 82 | } 83 | &:active { 84 | border: 2px solid ${props.theme.colors.primary}; 85 | box-shadow: unset; 86 | } 87 | &:disabled { 88 | background: ${props.theme.colors.shade}; 89 | color: rgba(44, 39, 56, 0.24); 90 | box-shadow: unset; 91 | } 92 | `; 93 | } 94 | 95 | const StyledButton = styled('button') <{ 96 | variant: ButtonType; 97 | small: boolean; 98 | }>` 99 | box-sizing: border-box; 100 | border: unset; 101 | border-radius: 3px; 102 | height: ${props => props.small ? '34px' : '48px'}; 103 | padding: 4px 20px; 104 | font-size: 14px; 105 | min-width: 100px; 106 | 107 | ${props => getStylingByVariant(props.variant, props)} 108 | `; 109 | 110 | export const Button: Component = ({ 111 | variant = 'accent', 112 | disabled = false, 113 | small = false, 114 | onClick, 115 | children 116 | }) => ( 117 | 124 | {children} 125 | 126 | ); 127 | -------------------------------------------------------------------------------- /src/components/button/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, ButtonProps, ButtonType } from './button'; 2 | 3 | export { Button }; 4 | export type { ButtonProps, ButtonType }; -------------------------------------------------------------------------------- /src/components/callout/README.md: -------------------------------------------------------------------------------- 1 | # Callout component 2 | 3 | ### Usage 4 | 5 | ```jsx 6 | import { Callout } from '@specialdoom/solid-rev-kit'; 7 | 8 | const Container = () => ( 9 | <> 10 | Action, 15 | 18 | ]} 19 | /> 20 | Action, 24 | 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(''); 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 | 9 | 10 | 11 | ), 12 | Burger: ({ fill }: IconProps) => ( 13 | 14 | 15 | 16 | ), 17 | Cross: ({ fill }: IconProps) => ( 18 | 19 | 20 | 21 | ), 22 | More: ({ fill }: IconProps) => ( 23 | 24 | 25 | 26 | ), 27 | Minus: ({ fill }: IconProps) => ( 28 | 29 | 30 | 31 | ), 32 | Lens: ({ fill }: IconProps) => ( 33 | 34 | 35 | 36 | ), 37 | Circle: ({ fill }: IconProps) => ( 38 | 39 | 40 | 41 | ), 42 | ChevronLeft: ({ fill }: IconProps) => ( 43 | 44 | 45 | 46 | ), 47 | ChevronDown: ({ fill }: IconProps) => ( 48 | 49 | 50 | 51 | ), 52 | Share: ({ fill }: IconProps) => ( 53 | 54 | 55 | 56 | ), 57 | Heart: ({ fill }: IconProps) => ( 58 | 59 | 60 | 61 | ), 62 | Activity: ({ fill }: IconProps) => ( 63 | 64 | 65 | 66 | ), 67 | Alert: ({ fill }: IconProps) => ( 68 | 69 | 70 | 71 | ), 72 | ArrowDown: ({ fill }: IconProps) => ( 73 | 74 | 75 | 76 | ), 77 | ArrowLeft: ({ fill }: IconProps) => ( 78 | 79 | 80 | 81 | ), 82 | ArrowRight: ({ fill }: IconProps) => ( 83 | 84 | 85 | 86 | ), 87 | ArrowUp: ({ fill }: IconProps) => ( 88 | 89 | 90 | 91 | ), 92 | Badge: ({ fill }: IconProps) => ( 93 | 94 | 95 | 96 | ), 97 | Bag: ({ fill }: IconProps) => ( 98 | 99 | 100 | 101 | ), 102 | Battery: ({ fill }: IconProps) => ( 103 | 104 | 105 | 106 | ), 107 | Bell: ({ fill }: IconProps) => ( 108 | 109 | 110 | 111 | ), 112 | Book: ({ fill }: IconProps) => ( 113 | 114 | 115 | 116 | ), 117 | Box: ({ fill }: IconProps) => ( 118 | 119 | 120 | 121 | ), 122 | Bullet: ({ fill }: IconProps) => ( 123 | 124 | 125 | 126 | ), 127 | Calendar: ({ fill }: IconProps) => ( 128 | 129 | 130 | 131 | ), 132 | Camera: ({ fill }: IconProps) => ( 133 | 134 | 135 | 136 | ), 137 | Card: ({ fill }: IconProps) => ( 138 | 139 | 140 | 141 | ), 142 | Cart: ({ fill }: IconProps) => ( 143 | 144 | 145 | 146 | ), 147 | Check: ({ fill }: IconProps) => ( 148 | 149 | 150 | 151 | ), 152 | ChevronRight: ({ fill }: IconProps) => ( 153 | 154 | 155 | 156 | ), 157 | ChevronUp: ({ fill }: IconProps) => ( 158 | 159 | 160 | 161 | ), 162 | Comment: ({ fill }: IconProps) => ( 163 | 164 | 165 | 166 | ), 167 | Cookie: ({ fill }: IconProps) => ( 168 | 169 | 170 | 171 | ), 172 | Currency: ({ fill }: IconProps) => ( 173 | 174 | 175 | 176 | ), 177 | Desktop: ({ fill }: IconProps) => ( 178 | 179 | 180 | 181 | ), 182 | Download: ({ fill }: IconProps) => ( 183 | 184 | 185 | 186 | ), 187 | Equalizer: ({ fill }: IconProps) => ( 188 | 189 | 190 | 191 | ), 192 | File: ({ fill }: IconProps) => ( 193 | 194 | 195 | 196 | ), 197 | Flag: ({ fill }: IconProps) => ( 198 | 199 | 200 | 201 | ), 202 | Folder: ({ fill }: IconProps) => ( 203 | 204 | 205 | 206 | ), 207 | Gear: ({ fill }: IconProps) => ( 208 | 209 | 210 | 211 | ), 212 | Diamond: ({ fill }: IconProps) => ( 213 | 214 | 215 | 216 | ), 217 | GraphBar: ({ fill }: IconProps) => ( 218 | 219 | 220 | 221 | ), 222 | GraphPie: ({ fill }: IconProps) => ( 223 | 224 | 225 | 226 | ), 227 | GraphPoly: ({ fill }: IconProps) => ( 228 | 229 | 230 | 231 | ), 232 | Home: ({ fill }: IconProps) => ( 233 | 234 | 235 | 236 | ), 237 | Image: ({ fill }: IconProps) => ( 238 | 239 | 240 | 241 | ), 242 | Info: ({ fill }: IconProps) => ( 243 | 244 | 245 | 246 | ), 247 | Layers: ({ fill }: IconProps) => ( 248 | 249 | 250 | 251 | ), 252 | Marker: ({ fill }: IconProps) => ( 253 | 254 | 255 | 256 | ), 257 | Mobile: ({ fill }: IconProps) => ( 258 | 259 | 260 | 261 | ), 262 | PaperBag: ({ fill }: IconProps) => ( 263 | 264 | 265 | 266 | ), 267 | Pencil: ({ fill }: IconProps) => ( 268 | 269 | 270 | 271 | ), 272 | Power: ({ fill }: IconProps) => ( 273 | 274 | 275 | 276 | ), 277 | Shield: ({ fill }: IconProps) => ( 278 | 279 | 280 | 281 | ), 282 | Square: ({ fill }: IconProps) => ( 283 | 284 | 285 | 286 | ), 287 | Tag: ({ fill }: IconProps) => ( 288 | 289 | 290 | 291 | ), 292 | Thunder: ({ fill }: IconProps) => ( 293 | 294 | 295 | 296 | ), 297 | Ticket: ({ fill }: IconProps) => ( 298 | 299 | 300 | 301 | ), 302 | Upload: ({ fill }: IconProps) => ( 303 | 304 | 305 | 306 | ), 307 | User: ({ fill }: IconProps) => ( 308 | 309 | 310 | 311 | ), 312 | VideoCamera: ({ fill }: IconProps) => ( 313 | 314 | 315 | 316 | ), 317 | Wallet: ({ fill }: IconProps) => ( 318 | 319 | 320 | 321 | ), 322 | Watch: ({ fill }: IconProps) => ( 323 | 324 | 325 | 326 | ), 327 | Wrench: ({ fill }: IconProps) => ( 328 | 329 | 330 | 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 |