├── .babelrc
├── .editorconfig
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .prettierrc
├── .storybook
├── addons.js
├── config.js
├── theme.js
└── webpack.config.js
├── README.md
├── jest.config.js
├── package.json
├── src
├── common
│ ├── TestUtil.tsx
│ ├── _utils
│ │ ├── __mocks__
│ │ │ └── fileMock.ts
│ │ ├── __tests__
│ │ │ └── utils.test.ts
│ │ └── index.ts
│ ├── assets
│ │ └── fonts
│ │ │ ├── Inter-Black.woff
│ │ │ ├── Inter-BlackItalic.woff
│ │ │ ├── Inter-Bold.woff
│ │ │ ├── Inter-BoldItalic.woff
│ │ │ ├── Inter-ExtraBold.woff
│ │ │ ├── Inter-ExtraBoldItalic.woff
│ │ │ ├── Inter-ExtraLight-BETA.woff
│ │ │ ├── Inter-ExtraLightItalic-BETA.woff
│ │ │ ├── Inter-Italic.woff
│ │ │ ├── Inter-Light-BETA.woff
│ │ │ ├── Inter-LightItalic-BETA.woff
│ │ │ ├── Inter-Medium.woff
│ │ │ ├── Inter-MediumItalic.woff
│ │ │ ├── Inter-Regular.woff
│ │ │ ├── Inter-SemiBold.woff
│ │ │ ├── Inter-SemiBoldItalic.woff
│ │ │ ├── Inter-Thin-BETA.woff
│ │ │ └── Inter-ThinItalic-BETA.woff
│ ├── styles
│ │ ├── GlobalStyles.tsx
│ │ ├── ThemeProvider.stories.tsx
│ │ ├── ThemeProvider.tsx
│ │ ├── __test__
│ │ │ ├── ThemeProvider.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── ThemeProvider.test.tsx.snap
│ │ ├── fonts.tsx
│ │ ├── index.tsx
│ │ ├── palette.tsx
│ │ └── variables.tsx
│ └── types.ts
├── components
│ ├── Alerts
│ │ ├── Alert.tsx
│ │ ├── AlertContext.ts
│ │ ├── Alerts.README.md
│ │ ├── Alerts.stories.tsx
│ │ ├── AlertsProvider.tsx
│ │ ├── StyledAlert.tsx
│ │ ├── StyledAlerts.tsx
│ │ ├── TransitionWrapper.tsx
│ │ ├── __test__
│ │ │ ├── Alerts.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Alerts.test.tsx.snap
│ │ ├── index.tsx
│ │ ├── types.ts
│ │ ├── useAlerts.ts
│ │ └── withAlerts.tsx
│ ├── Avatar
│ │ ├── Avatar.stories.tsx
│ │ ├── Avatar.tsx
│ │ ├── README.md
│ │ ├── __test__
│ │ │ ├── Avatar.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Avatar.test.tsx.snap
│ │ ├── index.ts
│ │ └── types.ts
│ ├── Breadcrumb
│ │ ├── Breadcrumb.README.md
│ │ ├── Breadcrumb.stories.tsx
│ │ ├── Breadcrumb.tsx
│ │ ├── BreadcrumbItem.tsx
│ │ ├── __tests__
│ │ │ ├── Breadcrumb.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Breadcrumb.test.tsx.snap
│ │ ├── index.tsx
│ │ └── types.ts
│ ├── Button
│ │ ├── Button.stories.tsx
│ │ ├── ButtonGroup.tsx
│ │ ├── ButtonWrapper.tsx
│ │ ├── README.md
│ │ ├── __tests__
│ │ │ ├── Button.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Button.test.tsx.snap
│ │ ├── components
│ │ │ ├── ButtonBase.tsx
│ │ │ ├── ButtonContent.tsx
│ │ │ ├── ButtonInset.tsx
│ │ │ └── index.tsx
│ │ ├── index.tsx
│ │ └── types.ts
│ ├── Dropdown
│ │ ├── Dropdown.stories.tsx
│ │ ├── Dropdown.styles.tsx
│ │ ├── Dropdown.tsx
│ │ ├── README.md
│ │ └── index.ts
│ ├── Extractor
│ │ ├── DataStructure.ts
│ │ ├── DownSelect.tsx
│ │ ├── Dropdown.tsx
│ │ ├── Expression.tsx
│ │ ├── __tests__
│ │ │ ├── Extractor.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Extractor.test.tsx.snap
│ │ ├── helpers.ts
│ │ ├── index.tsx
│ │ ├── stories
│ │ │ └── Extractor.stories.tsx
│ │ └── types.ts
│ ├── Icon
│ │ ├── Icon.stories.tsx
│ │ ├── Icon.tsx
│ │ ├── Icons.tsx
│ │ └── index.tsx
│ ├── Input
│ │ ├── Input.stories.tsx
│ │ ├── Input.tsx
│ │ ├── InputAddons.tsx
│ │ ├── InputRef.tsx
│ │ ├── README.md
│ │ ├── RenderInput.tsx
│ │ ├── __tests__
│ │ │ ├── Input.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Input.test.tsx.snap
│ │ ├── index.tsx
│ │ ├── styledComponents.tsx
│ │ ├── types.ts
│ │ └── utils.ts
│ ├── Label
│ │ ├── InlineLabel.tsx
│ │ ├── Label.stories.tsx
│ │ ├── Label.tsx
│ │ ├── README.md
│ │ ├── __tests__
│ │ │ ├── Label.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Label.test.tsx.snap
│ │ ├── commonUtils.ts
│ │ ├── index.ts
│ │ └── types.ts
│ ├── Link
│ │ ├── Link.stories.tsx
│ │ ├── Link.styles.tsx
│ │ ├── Link.tsx
│ │ ├── README.md
│ │ ├── __tests__
│ │ │ ├── Link.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Link.test.tsx.snap
│ │ ├── index.tsx
│ │ └── types.ts
│ ├── Loaders
│ │ ├── IconLoader.stories.tsx
│ │ ├── IconLoader.tsx
│ │ ├── README.md
│ │ └── index.tsx
│ ├── Modal
│ │ ├── Modal.stories.tsx
│ │ ├── ModalWrapper.tsx
│ │ ├── README.md
│ │ ├── __tests__
│ │ │ ├── Modal.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Modal.test.tsx.snap
│ │ ├── components
│ │ │ ├── Footer.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── Main.tsx
│ │ │ ├── ScrollWrapper.tsx
│ │ │ └── index.ts
│ │ ├── helpers.ts
│ │ ├── index.ts
│ │ ├── types.ts
│ │ ├── variants
│ │ │ ├── Base.tsx
│ │ │ ├── BottomPanel.tsx
│ │ │ ├── LeftPanel.tsx
│ │ │ ├── RightPanel.tsx
│ │ │ ├── index.ts
│ │ │ ├── styledComponents.ts
│ │ │ └── types.ts
│ │ └── wrappers
│ │ │ ├── Body.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── Panel.tsx
│ │ │ └── index.ts
│ ├── OmniSearch
│ │ ├── OmniSearch.stories.tsx
│ │ ├── OmniSearch.tsx
│ │ ├── README.md
│ │ ├── __tests__
│ │ │ ├── OmniSearch.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── OmniSearch.test.tsx.snap
│ │ ├── index.ts
│ │ └── types.ts
│ ├── Option
│ │ ├── Components
│ │ │ ├── Option.styles.tsx
│ │ │ ├── Option.tsx
│ │ │ ├── OptionGroup.tsx
│ │ │ └── OptionText.tsx
│ │ ├── Option.stories.tsx
│ │ ├── README.md
│ │ ├── __tests__
│ │ │ ├── Option.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Option.test.tsx.snap
│ │ ├── index.tsx
│ │ └── types.ts
│ ├── Radio
│ │ ├── Interface.ts
│ │ ├── Radio.stories.tsx
│ │ ├── Radio.tsx
│ │ ├── RadioGroup.tsx
│ │ ├── RadioWrapper.tsx
│ │ ├── __tests__
│ │ │ ├── Radio.test.jsx
│ │ │ └── __snapshots__
│ │ │ │ └── Radio.test.jsx.snap
│ │ ├── index.tsx
│ │ └── styledRadio.tsx
│ ├── Shimmer
│ │ ├── README.md
│ │ ├── Shimmer.stories.tsx
│ │ ├── Shimmer.tsx
│ │ ├── __tests__
│ │ │ ├── Shimmer.test.tsx
│ │ │ └── __snapshots__
│ │ │ │ └── Shimmer.test.tsx.snap
│ │ └── index.tsx
│ ├── Switch
│ │ ├── Switch.stories.tsx
│ │ ├── Switch.tsx
│ │ ├── index.tsx
│ │ └── styledComponents.tsx
│ ├── Tabs
│ │ ├── README.md
│ │ ├── TabItem.tsx
│ │ ├── Tabs.stories.tsx
│ │ ├── Tabs.tsx
│ │ ├── TabsList.tsx
│ │ ├── index.ts
│ │ ├── styles.tsx
│ │ ├── types.ts
│ │ └── utils.ts
│ └── index.tsx
└── index.ts
├── tsconfig.json
├── tsconfig.test.json
├── tslint.json
├── typings
└── index.d.ts
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-typescript",
4 | "@babel/preset-react",
5 | [
6 | "@babel/preset-env",
7 | {
8 | "modules": "commonjs",
9 | "targets": {
10 | "browsers": "last 2 versions"
11 | }
12 | }
13 | ]
14 | ],
15 | "plugins": [
16 | [
17 | "babel-plugin-styled-components",
18 | {
19 | "pure": true
20 | }
21 | ],
22 | "@babel/plugin-proposal-class-properties"
23 | ],
24 | "env": {
25 | "es": {
26 | "presets": [
27 | "@babel/preset-typescript",
28 | "@babel/preset-react",
29 | [
30 | "@babel/preset-env",
31 | {
32 | "modules": "false",
33 | "targets": {
34 | "browsers": "last 2 versions"
35 | }
36 | }
37 | ]
38 | ],
39 | "plugins": [
40 | [
41 | "babel-plugin-styled-components",
42 | {
43 | "pure": true
44 | }
45 | ],
46 | "@babel/plugin-proposal-class-properties",
47 | "@babel/plugin-transform-runtime"
48 | ]
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: knit-ui
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 | name: Tests
14 |
15 | runs-on: ${{ matrix.os }}
16 |
17 | strategy:
18 | matrix:
19 | os: [ubuntu-latest]
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | with:
24 | fetch-depth: 0
25 |
26 | - name: Install dependencies
27 | run: yarn
28 |
29 | - name: Build components
30 | run: yarn build
31 |
32 | - name: Run tests
33 | run: yarn test
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 | .ipr
4 | .out
5 | .iws
6 | *~
7 | ~*
8 | *.diff
9 | *.patch
10 | *.bak
11 | .DS_Store
12 | Thumbs.db
13 | .project
14 | .*proj
15 | .svn/
16 | *.swp
17 | *.swo
18 | *.log
19 | *.log.*
20 | *.json.gzip
21 | node_modules/
22 | .buildpath
23 | .settings
24 | npm-debug.log
25 | nohup.out
26 | dist
27 | lib
28 | es
29 | config/base.yaml
30 | /.vscode/
31 | /coverage
32 | /build
33 | /.history
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "bracketSpacing": true,
4 | "jsxBracketSameLine": true,
5 | "semi": false
6 | }
7 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import "@storybook/addon-actions/register"
2 | import "storybook-readme/register"
3 | import '@storybook/addon-knobs/register';
4 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { addParameters, configure, addDecorator } from "@storybook/react"
2 | import { withInfo } from "@storybook/addon-info"
3 | import { addReadme, configureReadme } from "storybook-readme"
4 | import React from "react"
5 | import theme from "./theme"
6 | import { GlobalStyles, ThemeProvider } from "../src/common/styles"
7 |
8 | // automatically import all files ending in *.stories.tsx
9 | const req = require.context("../src/", true, /.stories.tsx$/)
10 |
11 | const ThemeProviderDecorator = storyFn => (
12 | {storyFn()}
13 | )
14 |
15 | const GlobalStylesDecorator = storyFn => (
16 | <>
17 |
18 | {storyFn()}
19 | >
20 | )
21 |
22 | addParameters({
23 | options: {
24 | name: "KnitUI",
25 | theme,
26 | },
27 | })
28 |
29 | function loadStories() {
30 | req.keys().forEach(req)
31 | }
32 |
33 | addDecorator(withInfo)
34 | addDecorator(ThemeProviderDecorator)
35 | addDecorator(GlobalStylesDecorator)
36 | addDecorator(addReadme)
37 | configureReadme({
38 | StoryPreview: ({ children }) => (
39 |
{children}
40 | ),
41 | }),
42 | configure(loadStories, module)
43 |
--------------------------------------------------------------------------------
/.storybook/theme.js:
--------------------------------------------------------------------------------
1 | import { create } from "@storybook/theming";
2 |
3 | export default create({
4 | // Is this a 'light' or 'dark' theme?
5 | base: "dark",
6 |
7 | // Color palette
8 | colorPrimary: "red", // primary color
9 | colorSecondary: "#0088FF", // secondary color
10 |
11 | // UI
12 | appBg: "#262626",
13 | appContentBg: "white",
14 | appBorderColor: "rgba(0,0,0,.1)",
15 | appBorderRadius: 4,
16 |
17 | // Fonts
18 | fontBase: '"Inter", "Helvetica", Arial, sans-serif',
19 | fontCode: "Monaco, monospace",
20 |
21 | // Text colors
22 | textColor: "#b1b1b1",
23 | textInverseColor: "#333333"
24 | });
25 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = ({ config, mode }) => {
2 | config.module.rules.push({
3 | test: /\.(ts|tsx)$/,
4 | loader: require.resolve("babel-loader"),
5 | options: {
6 | presets: [["react-app", { flow: false, typescript: true }]],
7 | },
8 | })
9 | config.module.rules.push({
10 | test: /\.(ts|tsx)$/,
11 | loader: require.resolve("react-docgen-typescript-loader"),
12 | })
13 | config.resolve.extensions.push(".ts", ".tsx")
14 | return config
15 | }
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # knit.design
2 |
3 | Storybook : [Link](https://knitui.design)
4 |
5 | Roadmap: https://www.figma.com/file/o3UDrumhtDT0huZEsPnGPw/KnitUI-planning?node-id=0%3A1
6 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | "^.+\\.jsx?$": "babel-jest",
4 | "^.+\\.tsx?$": "ts-jest",
5 | },
6 | moduleNameMapper: {
7 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
8 | "/src/common/_utils/__mocks__/fileMock.ts",
9 | "\\.(css|less)$": "identity-obj-proxy",
10 | },
11 | preset: "ts-jest",
12 | globals: {
13 | "ts-jest": {
14 | tsConfig: "./tsconfig.test.json",
15 | },
16 | },
17 | setupFilesAfterEnv: [
18 | "@testing-library/jest-dom/extend-expect",
19 | "jest-styled-components",
20 | ],
21 | modulePathIgnorePatterns: [
22 | "/build",
23 | "/es",
24 | "/lib",
25 | ],
26 | testPathIgnorePatterns: [
27 | "/build/",
28 | "/es",
29 | "/lib",
30 | "/node_modules/",
31 | ],
32 | }
33 |
--------------------------------------------------------------------------------
/src/common/TestUtil.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/react"
2 | import React, { ReactElement } from "react"
3 | import { ThemeProvider } from "./styles"
4 |
5 | const ProviderWrapper = ({
6 | children,
7 | }: {
8 | children: ReactElement
9 | }): ReactElement => {
10 | // Nest all providers here
11 | return {children}
12 | }
13 |
14 | const customRender = (ui: ReactElement, options?: object) =>
15 | render(ui, { wrapper: ProviderWrapper, ...options })
16 |
17 | // re-export everything
18 | export * from "@testing-library/react"
19 | // override render method
20 | export { customRender as render }
21 |
--------------------------------------------------------------------------------
/src/common/_utils/__mocks__/fileMock.ts:
--------------------------------------------------------------------------------
1 | // To enabel jest to run tests for components that import static
2 | // assets like images and fonts.
3 | // https://jestjs.io/docs/en/webpack#handling-static-assets
4 |
5 | module.exports = "test-file-stub"
6 |
--------------------------------------------------------------------------------
/src/common/_utils/__tests__/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { getFontColor, parseColorPreset, parseCustomColor } from "../index"
2 | import { palette, generateTheme } from "../../../"
3 | const { knitui: theme } = generateTheme(palette)
4 | const chromaPalette = theme.chromaPalette
5 | const { Neutral0, Neutral90 } = chromaPalette
6 |
7 | describe("_utils", () => {
8 | describe("getFontColor", () => {
9 | it("font is white for black background", () => {
10 | const fontColor = getFontColor(theme, chromaPalette.Neutral90)
11 | expect(fontColor.toString()).toBe(Neutral0.toString())
12 | })
13 |
14 | it("font is black for white background", () => {
15 | const fontColor = getFontColor(theme, chromaPalette.Neutral0)
16 | expect(fontColor.toString()).toBe(Neutral90.toString())
17 | })
18 |
19 | it("font is white for neutral background", () => {
20 | const fontColor = getFontColor(theme, chromaPalette.Blue100)
21 | expect(fontColor.toString()).toBe(Neutral0.toString())
22 | })
23 |
24 | it("font is white for success background", () => {
25 | const fontColor = getFontColor(theme, chromaPalette.Neutral90)
26 | expect(fontColor.toString()).toBe(Neutral0.toString())
27 | })
28 |
29 | it("font is white for error background", () => {
30 | const fontColor = getFontColor(theme, chromaPalette.Green80)
31 | expect(fontColor.toString()).toBe(Neutral0.toString())
32 | })
33 |
34 | it("font is white for unsaved background", () => {
35 | const fontColor = getFontColor(theme, chromaPalette.Magenta80)
36 | expect(fontColor.toString()).toBe(Neutral0.toString())
37 | })
38 |
39 | it("font is black for warning background", () => {
40 | const fontColor = getFontColor(theme, chromaPalette.Yellow80)
41 | expect(fontColor.toString()).toBe(Neutral90.toString())
42 | })
43 | })
44 |
45 | describe("parseCustomColor", () => {
46 | it("should return an object with parsed colors", () => {
47 | const backgroundColor = "#000000"
48 | const fontColor = "#ffffff"
49 | const parsed = parseCustomColor(theme, backgroundColor)
50 | expect(parsed.background.toString()).toBe(backgroundColor)
51 | expect(parsed.font.toString()).toBe(fontColor)
52 | })
53 | })
54 |
55 | describe("parseColorPreset", () => {
56 | it("should return an object with parsed colors", () => {
57 | const parsed = parseColorPreset(theme, "neutral")
58 | expect(parsed.background.toString()).toBe("#002966")
59 | expect(parsed.font.toString()).toBe("#ffffff")
60 | })
61 | })
62 | })
63 |
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-Black.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-BlackItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-BlackItalic.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-Bold.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-BoldItalic.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-ExtraBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-ExtraBold.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-ExtraBoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-ExtraBoldItalic.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-ExtraLight-BETA.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-ExtraLight-BETA.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-ExtraLightItalic-BETA.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-ExtraLightItalic-BETA.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-Italic.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-Light-BETA.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-Light-BETA.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-LightItalic-BETA.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-LightItalic-BETA.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-Medium.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-MediumItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-MediumItalic.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-Regular.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-SemiBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-SemiBold.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-SemiBoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-SemiBoldItalic.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-Thin-BETA.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-Thin-BETA.woff
--------------------------------------------------------------------------------
/src/common/assets/fonts/Inter-ThinItalic-BETA.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/common/assets/fonts/Inter-ThinItalic-BETA.woff
--------------------------------------------------------------------------------
/src/common/styles/GlobalStyles.tsx:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components"
2 | import { fontsDefinition } from "./fonts"
3 |
4 | const GlobalStyles = createGlobalStyle`
5 | ${fontsDefinition}
6 |
7 | body,
8 | div,
9 | dl,
10 | dt,
11 | dd,
12 | ul,
13 | ol,
14 | li,
15 | h1,
16 | h2,
17 | h3,
18 | h4,
19 | h5,
20 | h6,
21 | pre,
22 | code,
23 | form,
24 | fieldset,
25 | legend,
26 | input,
27 | textarea,
28 | p,
29 | blockquote,
30 | th,
31 | td,
32 | hr,
33 | button,
34 | article,
35 | aside,
36 | details,
37 | figcaption,
38 | figure,
39 | footer,
40 | header,
41 | hgroup,
42 | menu,
43 | nav,
44 | section {
45 | margin: 0;
46 | padding: 0;
47 | }
48 |
49 | body {
50 | overscroll-behavior-x: none; /* Prevent the page redirection on swipe */
51 | }
52 |
53 | ul,
54 | ol {
55 | list-style: none;
56 | }
57 |
58 | html {
59 | font-size: 10px;
60 | }
61 |
62 | body {
63 | font-family: InterRegular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
64 | }
65 |
66 | *, *::before, *::after {
67 | box-sizing: border-box;
68 | }
69 | :focus {
70 | outline-color: #0066FF;
71 | }
72 | `
73 |
74 | export default GlobalStyles
75 |
--------------------------------------------------------------------------------
/src/common/styles/ThemeProvider.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { storiesOf } from "@storybook/react"
3 | import ThemeProvider from "./ThemeProvider"
4 | import * as palette from "./palette"
5 | import { Button } from "../../"
6 | import generateTheme from "./variables"
7 |
8 | const stories = storiesOf("ThemeProvider", module)
9 |
10 | stories.add("Blue100 overright to #1182D4", () => {
11 | const newPalette = {
12 | ...palette,
13 | Blue100: {
14 | hsl: [205, 85, 45],
15 | hex: "#1182D4",
16 | },
17 | }
18 | const theme = generateTheme(newPalette)
19 | return (
20 |
21 |
22 |
23 | )
24 | })
25 |
--------------------------------------------------------------------------------
/src/common/styles/ThemeProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from "react"
2 | import { ThemeProvider as Theme } from "styled-components"
3 | import { generateTheme, palette as defaultPalette } from "./"
4 |
5 | interface ThemeProviderProps {
6 | children: ReactElement
7 | theme?: object
8 | }
9 |
10 | const ThemeProvider = ({
11 | children,
12 | theme = generateTheme(defaultPalette),
13 | }: ThemeProviderProps): ReactElement => {children}
14 |
15 | export default ThemeProvider
16 |
--------------------------------------------------------------------------------
/src/common/styles/__test__/ThemeProvider.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/react"
2 | import React from "react"
3 | import { Button } from "../../.."
4 | import * as palette from "../palette"
5 | import ThemeProvider from "../ThemeProvider"
6 | import generateTheme from "../variables"
7 |
8 | describe("Passing Custom Theme", () => {
9 | // Changing one of the palette color
10 | const newPalette = {
11 | ...palette,
12 | Blue100: {
13 | hsl: [205, 85, 45],
14 | hex: "#1182D4",
15 | },
16 | }
17 |
18 | const knituiTheme = generateTheme(newPalette)
19 |
20 | it("Changing palette color Blue100 to #1182D4", () => {
21 | const { asFragment } = render(
22 |
23 |
24 |
25 | )
26 | expect(asFragment()).toMatchSnapshot()
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/src/common/styles/__test__/__snapshots__/ThemeProvider.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Passing Custom Theme Changing palette color Blue100 to #1182D4 1`] = `
4 |
5 | .c0 {
6 | display: -webkit-box;
7 | display: -webkit-flex;
8 | display: -ms-flexbox;
9 | display: flex;
10 | -webkit-align-items: center;
11 | -webkit-box-align: center;
12 | -ms-flex-align: center;
13 | align-items: center;
14 | width: inherit;
15 | font-size: 1.2rem;
16 | line-height: 1.8rem;
17 | color: #ffffff;
18 | background-color: #1183d4;
19 | border-radius: 0.4rem;
20 | border-style: none;
21 | box-sizing: border-box;
22 | border: 1px solid #00000000;
23 | }
24 |
25 | .c0 span {
26 | margin-right: 0rem;
27 | }
28 |
29 | .c0 span svg path {
30 | fill: #ffffff;
31 | }
32 |
33 | .c0:hover,
34 | .c0:active,
35 | .c0:focus {
36 | color: #ffffff;
37 | background-color: #2a9dee;
38 | cursor: pointer;
39 | }
40 |
41 | .c0:hover svg path,
42 | .c0:active svg path,
43 | .c0:focus svg path {
44 | fill: #ffffff;
45 | }
46 |
47 | .c0:active,
48 | .c0:focus {
49 | border: 1px solid #5ab2f2 !important;
50 | box-shadow: 0px 0px 2px #0066ff;
51 | outline: none;
52 | }
53 |
54 | .c0:hover {
55 | border: 1px solid #00000000;
56 | }
57 |
58 | .c0:disabled {
59 | opacity: 0.5;
60 | cursor: not-allowed;
61 | }
62 |
63 | .c1 {
64 | padding-left: 0.7rem;
65 | padding-right: 0.7rem;
66 | padding-top: 0.2rem;
67 | padding-bottom: 0.2rem;
68 | display: -webkit-box;
69 | display: -webkit-flex;
70 | display: -ms-flexbox;
71 | display: flex;
72 | -webkit-align-items: center;
73 | -webkit-box-align: center;
74 | -ms-flex-align: center;
75 | align-items: center;
76 | width: 100%;
77 | -webkit-box-pack: center;
78 | -webkit-justify-content: center;
79 | -ms-flex-pack: center;
80 | justify-content: center;
81 | }
82 |
83 |
92 |
93 | `;
94 |
--------------------------------------------------------------------------------
/src/common/styles/fonts.tsx:
--------------------------------------------------------------------------------
1 | const InterThin = require("../assets/fonts/Inter-Thin-BETA.woff")
2 | const InterLight = require("../assets/fonts/Inter-Light-BETA.woff")
3 | const InterRegular = require("../assets/fonts/Inter-Regular.woff")
4 | const InterMedium = require("../assets/fonts/Inter-Medium.woff")
5 | const InterSemiBold = require("../assets/fonts/Inter-SemiBold.woff")
6 | const InterBold = require("../assets/fonts/Inter-Bold.woff")
7 | const InterBlack = require("../assets/fonts/Inter-Black.woff")
8 |
9 | export const fontsDefinition = `
10 | @font-face {
11 | font-family: "InterThin";
12 | src: url(${InterThin}) format('woff'); /*URL to font*/
13 | }
14 |
15 | @font-face {
16 | font-family: "InterLight";
17 | src: url(${InterLight}) format('woff');
18 | }
19 |
20 | @font-face {
21 | font-family: "InterRegular";
22 | src: url(${InterRegular}) format('woff');
23 | }
24 |
25 | @font-face {
26 | font-family: "InterMedium";
27 | src: url(${InterMedium}) format('woff');
28 | }
29 |
30 | @font-face {
31 | font-family: "InterSemiBold";
32 | src: url(${InterSemiBold}) format('woff');
33 | }
34 |
35 | @font-face {
36 | font-family: "InterBold";
37 | src: url(${InterBold}) format('woff');
38 | }
39 |
40 | @font-face {
41 | font-family: "InterBlack";
42 | src: url(${InterBlack}) format('woff');
43 | }
44 | `
45 |
46 | export const fontsSource = {
47 | InterThin,
48 | InterLight,
49 | InterRegular,
50 | InterMedium,
51 | InterBold,
52 | InterBlack,
53 | InterSemiBold,
54 | }
55 |
--------------------------------------------------------------------------------
/src/common/styles/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as GlobalStyles } from "./GlobalStyles"
2 | export { default as ThemeProvider } from "./ThemeProvider"
3 | import * as palette from "./palette"
4 | export { palette }
5 | export { default as generateTheme } from "./variables"
6 |
--------------------------------------------------------------------------------
/src/common/styles/palette.tsx:
--------------------------------------------------------------------------------
1 | /* Primary Palette
2 | ---------------
3 | The primary palette comprises bright and bold range of hues derived from Lapis lazuli. These should be used selectively to highlight the important actions and guide the eye.
4 | */
5 |
6 | export const Blue100 = {
7 | hsl: [216, 100, 20],
8 | hex: "#002966",
9 | }
10 |
11 | export const Azure80 = {
12 | hsl: [205, 85, 65],
13 | hex: "#5AB2F2",
14 | }
15 |
16 | export const LightBlue40 = {
17 | hsl: [203, 100, 95],
18 | hex: "#E5F5FF",
19 | }
20 |
21 | export const Neutral0 = {
22 | hsl: [0, 0, 100],
23 | hex: "#FFFFFF",
24 | }
25 |
26 | /* Secondary Palette
27 | --------------------
28 | The secondary pallete comprises composed hues that compliment the primaries well. These provide a warmer tone to the product, and are deeply associated with the state and type of action, for instance, Crimson - danger/failure, Dark Lime - success, Pure Yellow - warning alerts, Moderate magenta - unsaved state.
29 | */
30 |
31 | export const Crimson80 = {
32 | hsl: [0, 100, 30],
33 | hex: "#990000",
34 | }
35 |
36 | export const Green80 = {
37 | hsl: [118, 100, 20],
38 | hex: "#036600",
39 | }
40 |
41 | export const Yellow80 = {
42 | hsl: [47, 100, 50],
43 | hex: "#FFC700",
44 | }
45 |
46 | export const Magenta80 = {
47 | hsl: [288, 20, 35],
48 | hex: "#64476B",
49 | }
50 |
51 | /* Tertiary Palette
52 | ---------------
53 | The tertiary palette comprises the washed-out hues that are used for highlights. It provides a softer experience and help in easily differentiating between bits.
54 | */
55 |
56 | export const Beige10 = {
57 | hsl: [42, 38, 95],
58 | hex: "#F7F4ED",
59 | }
60 |
61 | export const Rose10 = {
62 | hsl: [0, 100, 95],
63 | hex: "#FFE3E3",
64 | }
65 |
66 | export const Fern10 = {
67 | hsl: [148, 33, 90],
68 | hex: "#DDEEE5",
69 | }
70 |
71 | export const Sun10 = {
72 | hsl: [40, 92, 90],
73 | hex: "#FDEDCE",
74 | }
75 |
76 | export const Sapphire10 = {
77 | hsl: [217, 80, 90],
78 | hex: "#D1E1FA",
79 | }
80 |
81 | export const Violet10 = {
82 | hsl: [274, 49, 90],
83 | hex: "#E7D9F2",
84 | }
85 |
86 | export const Coral10 = {
87 | hsl: [22, 80, 90],
88 | hex: "#FAE0D1",
89 | }
90 |
91 | export const Wood10 = {
92 | hsl: [33, 35, 85],
93 | hex: "#E6DACB",
94 | }
95 |
96 | /* Neutral Palette
97 | ---------------
98 | The neutrals bring balance to the color palette, and used for actions that either don’t need direct attention or reduce focus from those already taking more than needed. Darker neutrals are used for shadows, borders, etc.
99 | */
100 |
101 | export const Neutral05 = {
102 | hsl: [0, 0, 97],
103 | hex: "#F7F7F7",
104 | }
105 |
106 | export const Neutral10 = {
107 | hsl: [0, 0, 95],
108 | hex: "#F2F2F2",
109 | }
110 |
111 | export const Neutral20 = {
112 | hsl: [0, 0, 90],
113 | hex: "#E5E5E5",
114 | }
115 |
116 | export const Neutral30 = {
117 | hsl: [0, 0, 85],
118 | hex: "#D9D9D9",
119 | }
120 |
121 | export const Neutral40 = {
122 | hsl: [0, 0, 80],
123 | hex: "#CCCCCC",
124 | }
125 |
126 | export const Neutral45 = {
127 | hsl: [0, 0, 65],
128 | hex: "#A6A6A6",
129 | }
130 |
131 | export const Neutral50 = {
132 | hsl: [0, 0, 50],
133 | hex: "#808080",
134 | }
135 |
136 | export const Neutral60 = {
137 | hsl: [0, 0, 40],
138 | hex: "#666666",
139 | }
140 |
141 | export const Neutral70 = {
142 | hsl: [0, 0, 30],
143 | hex: "#4C4C4C",
144 | }
145 |
146 | export const Neutral80 = {
147 | hsl: [0, 0, 20],
148 | hex: "#333333",
149 | }
150 |
151 | export const Neutral90 = {
152 | hsl: [0, 0, 10],
153 | hex: "#1A1A1A",
154 | }
155 |
156 | export const Azure100 = {
157 | hsl: [205, 85, 40],
158 | hex: "#0F74BD",
159 | }
160 |
--------------------------------------------------------------------------------
/src/common/types.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react"
2 |
3 | export type ColorPreset =
4 | | "neutral"
5 | | "danger"
6 | | "success"
7 | | "warning"
8 | | "unsaved"
9 | | "regular"
10 |
11 | export type CustomColor = string | { color: string; secondaryColor: string }
12 |
13 | export interface ParsedColorTheme {
14 | background: any
15 | font: any
16 | }
17 |
18 | export type fontSizeType = 10 | 12 | 14 | 16 | 18 | 20
19 |
20 | export interface BaseComponentProps {
21 | /** CSS classname to be added */
22 | className?: string
23 | /** CSS styles to be adeed */
24 | style?: CSSProperties
25 | }
26 |
27 | /**
28 | * styled-components will by default apply valid HTML props onto the DOM.
29 | * Refer: https://www.styled-components.com/docs/basics#passed-props
30 | * However in some cases we do use props only for some computation and we
31 | * would not want them to be applied on the DOM. To address this, we use
32 | * add all such props within another property called `customProps`.
33 | */
34 | export interface IStyled {
35 | customProps: PropType
36 | theme?: any
37 | [htmlProp: string]: any
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/Alerts/AlertContext.ts:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { AlertContextInterface } from "./types"
3 |
4 | export default React.createContext(null)
5 |
--------------------------------------------------------------------------------
/src/components/Alerts/AlertsProvider.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom"
3 | import { v4 as uuid } from "uuid"
4 | import AlertContext from "./AlertContext"
5 | import TransitionWrapper from "./TransitionWrapper"
6 | import {
7 | TopLeftBox,
8 | TopRightBox,
9 | BottomLeftBox,
10 | BottomRightBox,
11 | } from "./StyledAlerts"
12 | import { AlertProps, placementType, AlertsProviderState } from "./types"
13 |
14 | const PlacementWrapperDiv = {
15 | topLeft: TopLeftBox,
16 | topRight: TopRightBox,
17 | bottomLeft: BottomLeftBox,
18 | bottomRight: BottomRightBox,
19 | }
20 |
21 | class AlertsProvider extends React.Component<{}, AlertsProviderState> {
22 | queue: AlertProps[] = []
23 | interval: number | undefined
24 | alertMountNode: HTMLDivElement
25 | constructor(props) {
26 | super(props)
27 |
28 | this.state = {
29 | alerts: [],
30 | contextAPI: {
31 | addAlert: this.handleAdd,
32 | removeAlert: this.handleRemove,
33 | },
34 | }
35 |
36 | this.alertMountNode = document.createElement("div")
37 | this.alertMountNode.classList.add("knitui-alerts-root")
38 | }
39 |
40 | componentDidMount() {
41 | document.body.appendChild(this.alertMountNode)
42 | }
43 |
44 | handleAdd = (alertProps: AlertProps): string => {
45 | alertProps.placement =
46 | alertProps.placement || ("bottomLeft" as placementType)
47 |
48 | // If the key is not provided, generate a new random key.
49 | alertProps.alertKey = alertProps.alertKey || uuid()
50 |
51 | // Closing Alert will call remove method of this class to update state
52 | const curOnClose = alertProps.onClose
53 | alertProps.onClose = key => {
54 | if (curOnClose) {
55 | curOnClose(key)
56 | }
57 | this.handleRemove(alertProps.alertKey!)
58 | }
59 | this.queue.push(alertProps)
60 | this.displayAlert()
61 | return alertProps.alertKey
62 | }
63 |
64 | handleRemove = (key: string): boolean => {
65 | const index = this.state.alerts.findIndex(i => i.alertKey === key)
66 | if (index > -1) {
67 | this.setState({
68 | alerts: [
69 | ...this.state.alerts.slice(0, index),
70 | ...this.state.alerts.slice(index + 1),
71 | ],
72 | })
73 | return true
74 | }
75 | return false
76 | }
77 |
78 | displayAlert() {
79 | this.interval = this.interval
80 | ? this.interval
81 | : setInterval(() => {
82 | if (this.queue.length > 0) {
83 | this.setState(prevState => ({
84 | alerts: [...prevState.alerts, this.queue.shift()!],
85 | }))
86 | } else {
87 | clearInterval(this.interval)
88 | this.interval = undefined
89 | }
90 | }, 350)
91 | }
92 |
93 | componentWillUnmount() {
94 | clearInterval(this.interval)
95 | document.body.removeChild(this.alertMountNode)
96 | }
97 |
98 | render() {
99 | // alertsByPlace placement keys needs to be predefined, can not use each
100 | // alerts placement prop directly, because for transition of last element,
101 | // Wrapper Component is needed.
102 | const alertsByPlace = {
103 | topLeft: [],
104 | topRight: [],
105 | bottomLeft: [],
106 | bottomRight: [],
107 | }
108 |
109 | this.state.alerts.forEach((alert: AlertProps) => {
110 | const placement = alert.placement as string
111 | alertsByPlace[placement] = [alert, ...alertsByPlace[placement]]
112 | })
113 |
114 | return (
115 |
116 | {this.props.children}
117 | {ReactDOM.createPortal(
118 |
119 | {Object.entries(alertsByPlace).map(([placement, alertArr]) => {
120 | const Wrapper = PlacementWrapperDiv[placement]
121 | return (
122 |
126 |
127 |
128 | )
129 | })}
130 |
,
131 | this.alertMountNode
132 | )}
133 |
134 | )
135 | }
136 | }
137 |
138 | export default AlertsProvider
139 |
--------------------------------------------------------------------------------
/src/components/Alerts/StyledAlerts.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import { animated } from "react-spring"
3 |
4 | const positionBoxStyle = `
5 | position: fixed;
6 | overflow: hidden;
7 | display: flex;
8 | pointer-events: none;
9 | > * {
10 | pointer-events: auto;
11 | }
12 | `
13 |
14 | export const TopLeftBox = styled.div`
15 | ${positionBoxStyle}
16 | left: 0px;
17 | top: 0px;
18 | padding-top: 10px;
19 | padding-left: 10px;
20 | flex-flow: column nowrap;
21 | align-items: flex-start;
22 | `
23 |
24 | export const TopRightBox = styled.div`
25 | ${positionBoxStyle}
26 | right: 0px;
27 | top: 0px;
28 | padding-top: 10px;
29 | padding-right: 10px;
30 | flex-flow: column nowrap;
31 | align-items: flex-end;
32 | `
33 |
34 | export const BottomLeftBox = styled.div`
35 | ${positionBoxStyle}
36 | left: 0px;
37 | bottom: 0px;
38 | padding-bottom: 10px;
39 | padding-left: 10px;
40 | flex-flow: column-reverse nowrap;
41 | align-items: flex-start;
42 | `
43 |
44 | export const BottomRightBox = styled.div`
45 | ${positionBoxStyle}
46 | right: 0px;
47 | bottom: 0px;
48 | padding-bottom: 10px;
49 | padding-right: 10px;
50 | flex-flow: column-reverse nowrap;
51 | align-items: flex-end;
52 | `
53 |
54 | export const TransitionDiv = styled(animated.div)`
55 | margin: 4px 0;
56 | `
57 |
58 | export const AlertDiv = styled(animated.div)``
59 |
--------------------------------------------------------------------------------
/src/components/Alerts/TransitionWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { AlertProps } from "./types"
2 | import React, { useState, useEffect } from "react"
3 | import { useTransition, UseTransitionResult } from "react-spring"
4 | import { TransitionDiv, AlertDiv } from "./StyledAlerts"
5 | import Alert from "./Alert"
6 | import { SpringConfig, State } from "react-spring/renderprops"
7 |
8 | // This interface extend Transition's return component with prop have properties Life,
9 | // which is not defined in react-spring's types but implemented in the their code
10 | interface UserTransitionResultWithLife
11 | extends UseTransitionResult {
12 | props: { life: string }
13 | }
14 | //Transition/Animation Using react-spring useTransition
15 | const TransitionWrapper = (props: { alerts: Array }) => {
16 | const config = (_item: AlertProps, state: State): SpringConfig => {
17 | return {
18 | duration: 250,
19 | easing: t => {
20 | if (state === "leave") {
21 | return (t ** 2 + t ** 3) / 2
22 | } else {
23 | return t ** 3 //Bezier Curve (0,0,0,1)
24 | }
25 | },
26 | }
27 | }
28 | const [alerts, setAlerts] = useState(props.alerts)
29 | const [alertRefMap] = useState(() => new WeakMap())
30 |
31 | useEffect(() => {
32 | setAlerts(props.alerts)
33 | }, [props.alerts])
34 |
35 | // The type is defined `object` because react-spring typescript does not define
36 | // config as function with more than one argument, but it's js implementation does.
37 | // real interface is `UseTransition`
38 | const transitionProperties: object = {
39 | from: { height: 0, opacity: 0 },
40 | enter: (item: AlertProps) => async (next: Function) =>
41 | await next({ height: alertRefMap.get(item), opacity: 1 }),
42 | leave: { opacity: 0, height: 0 },
43 | config,
44 | }
45 |
46 | const transitionProps = useTransition(
47 | alerts,
48 | item => item.alertKey as string,
49 | transitionProperties
50 | )
51 |
52 | return (
53 | <>
54 | {transitionProps.map(
55 | ({
56 | key,
57 | item,
58 | props: { life, ...style },
59 | }: UserTransitionResultWithLife) => {
60 | return (
61 |
62 | ref && alertRefMap.set(item, ref.offsetHeight)}>
64 |
65 |
66 |
67 | )
68 | }
69 | )}
70 | >
71 | )
72 | }
73 |
74 | export default TransitionWrapper
75 |
--------------------------------------------------------------------------------
/src/components/Alerts/index.tsx:
--------------------------------------------------------------------------------
1 | import { FC, Component, ComponentClass } from "react"
2 | import Alert from "./Alert"
3 | import AlertsProvider from "./AlertsProvider"
4 | import useAlerts from "./useAlerts"
5 | import withAlerts from "./withAlerts"
6 |
7 | import { AlertProps, useAlertType } from "./types"
8 |
9 | interface IAlertWrapper {
10 | Alert: FC
11 | AlertsProvider: ComponentClass
12 | useAlerts: useAlertType
13 | withAlerts: (component: any) => any
14 | }
15 |
16 | const AlertWrapper: IAlertWrapper = {
17 | Alert,
18 | AlertsProvider,
19 | useAlerts,
20 | withAlerts
21 | }
22 |
23 | export default AlertWrapper
24 |
--------------------------------------------------------------------------------
/src/components/Alerts/types.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode, Component } from "react"
2 |
3 | // Size types of the alert components
4 | export type sizeType = "x-small" | "small" | "medium" | "large"
5 |
6 | // Type of alert in particular which define it's other property like color, icon,...
7 | export type alertType = "neutral" | "warning" | "success" | "danger" | "unsaved"
8 |
9 | // define where will be the placement in the container
10 | export type placementType =
11 | | "topLeft"
12 | | "topRight"
13 | | "bottomLeft"
14 | | "bottomRight"
15 |
16 | // Type of buttons given to have action
17 | export type actionType = {
18 | // Text To be displayed on action button
19 | text: string
20 | // Callback function when action button is Clicked
21 | callback: Function
22 | }
23 |
24 | // Alert Props are attributes which are used to define alert
25 | export interface AlertProps {
26 | // type of the alert
27 | type?: alertType
28 | // Size of the alert, default is small
29 | size?: sizeType
30 | // Main content of the alert
31 | content: ReactNode
32 | // whether alert is to be auto dismissed
33 | autoDismiss?: boolean
34 | // time after which we need to dismiss, effective when auto dismiss is set
35 | dismissDuration?: number
36 | // heading of the alert
37 | heading?: string
38 | // custom icon, effective when mention in React Component or as Prop in object passed to API,
39 | // for use of default icon just pass it empty or undefined
40 | icon?: string
41 | // image url to be shown, overrides icon when both are specified
42 | image?: string
43 | // whether alert is multiline, defaults to false
44 | multiLine?: boolean
45 | // actions of alert, max 2, pass an array of objects with keys as text and callback
46 | actions?: Array
47 | // Function to call once the alert is closed
48 | onClose?: (event) => void
49 | // Function to call while alert is unmounted
50 | onExit?: (key: string) => void
51 | // position on window where it will be displayed
52 | placement?: placementType
53 | // key to reference Element from Alerts Container (help in remove function)
54 | alertKey?: string
55 | // custom className to be passed
56 | className?: string
57 | // prefix classname for custom style
58 | prefixClassName?: string
59 | // custom Color is we don't want to use type presetColor
60 | customColor?: string
61 | }
62 |
63 | export type addAlertType = (props: AlertProps) => string
64 |
65 | export type removeAlertType = (key: string) => boolean
66 |
67 | export interface AlertContextInterface {
68 | // This function will add alert to your provider and return a unique key to reference the alert
69 | addAlert: addAlertType
70 | // This function will remove alert when passed unique key related to each alert
71 | removeAlert: removeAlertType
72 | }
73 |
74 | export interface AlertsProviderState {
75 | alerts: AlertProps[]
76 | contextAPI: AlertContextInterface
77 | }
78 |
79 | export type AlertProviderType = Component<{}, AlertsProviderState>
80 |
81 | export type useAlertType = () => {
82 | addAlert: addAlertType
83 | removeAlert: removeAlertType
84 | }
--------------------------------------------------------------------------------
/src/components/Alerts/useAlerts.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from "react"
2 | import AlertContext from "./AlertContext"
3 |
4 | // Will be used in functional component (hooks)
5 | export default () => {
6 | const { addAlert, removeAlert } = useContext(AlertContext)!
7 | return { addAlert, removeAlert }
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/Alerts/withAlerts.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import AlertContext from "./AlertContext"
3 | import hoistNonReactStatics from "hoist-non-react-statics"
4 | import { AlertContextInterface } from "./types"
5 |
6 | function withAlerts(Component: any) {
7 | // Reference for ForwardRef - https://reactjs.org/docs/higher-order-components.html#refs-arent-passed-through
8 | const WrappedComponent = React.forwardRef((props, ref) => (
9 |
10 | {(context: AlertContextInterface) => (
11 |
17 | )}
18 |
19 | ))
20 |
21 | // To display name during debuging
22 | if (process.env.NODE_ENV !== "production") {
23 | WrappedComponent.displayName = `WithAlert(${getDisplayName(Component)})`
24 | }
25 | function getDisplayName(Component) {
26 | return Component.displayName || Component.name || "Component"
27 | }
28 |
29 | // Reference https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
30 | hoistNonReactStatics(WrappedComponent, Component)
31 |
32 | return WrappedComponent
33 | }
34 |
35 | export default withAlerts
36 |
--------------------------------------------------------------------------------
/src/components/Avatar/Avatar.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { storiesOf } from "@storybook/react"
3 | import Avatar from "./"
4 | import {
5 | withKnobs,
6 | text,
7 | boolean,
8 | optionsKnob as options,
9 | } from "@storybook/addon-knobs"
10 | import { action } from "@storybook/addon-actions"
11 | const Readme = require("./README.md")
12 |
13 | const stories = storiesOf("Avatar", module)
14 | stories.addDecorator(withKnobs)
15 |
16 | const defaultProps = {
17 | size: text("Size", "24px"),
18 | disabled: boolean("Disabled", false),
19 | onClick: action("button-click"),
20 | }
21 |
22 | const additionalProps = {
23 | picture: text("Picture", "https://i.ibb.co/DwVLSqF/user-profile.jpg"),
24 | name: text("Name", "Jane Doe"),
25 | }
26 |
27 | stories
28 | .addParameters({
29 | readme: {
30 | // Show readme before story
31 | codeTheme: "shades-of-purple",
32 | // Show readme at the addons panel
33 | sidebar: Readme,
34 | },
35 | })
36 | .add("Picture avatar", () => (
37 |
42 | ))
43 | .add("Name avatar", () => (
44 |
45 | ))
46 | .add("Unknown user avatar", () => )
47 | .add("Different size picture avatar", () => (
48 |
49 | ))
50 | .add("Different size name avatar", () => (
51 |
52 | ))
53 | .add("Different size icon avatar", () => (
54 |
55 | ))
56 | .add("Disabled picture avatar", () => (
57 |
58 | ))
59 | .add("Disabled name avatar", () => (
60 |
61 | ))
62 | .add("Disabled icon avatar", () => )
63 |
--------------------------------------------------------------------------------
/src/components/Avatar/README.md:
--------------------------------------------------------------------------------
1 | # Avatar
2 |
3 | Avatar are useful to show information of user like user-profile, user-name. (it act as button, so according to usecase, valid to pass onClick or any html button attributes.)
4 |
5 | ## Usage
6 |
7 | ```javascript
8 | import { Avatar } from "KnitUI"
9 |
10 | )
17 | ```
18 |
19 | ### Props
20 |
21 | | Prop name | Type | Optional | Default | Description |
22 | | --------- | ---------- | -------- | ----------- | ---------------------------------------------------------------------------------------------- |
23 | | picture | string | Yes | `undefined` | url of the profile picture or any valid component for react img src attribute |
24 | | name | string | Yes | `undefined` | user name which first letter will be shown, if no picture url is passed or falsy picture value |
25 | | size | string | Yes | `24px` | Physical area occupied on the screen, any css valid length parameter |
26 | | disabled | boolean | Yes | `false` | Whether the Avatar should be interactive or not |
27 | | onClick | Function | Yes | None | A click handler to be executed on click of the button. Will receive the `event` as an argument |
28 | | className | string | Yes | None | CSS class name to be added |
29 | | style | CSS Object | Yes | None | CSS style to be added |
30 |
--------------------------------------------------------------------------------
/src/components/Avatar/__test__/Avatar.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Avatar from "../"
3 | import { ThemeProvider } from "../../../common/styles"
4 | import { cleanup, fireEvent, render } from "../../../common/TestUtil"
5 |
6 | afterEach(cleanup)
7 |
8 | const defaultProps = {
9 | size: "24px",
10 | disabled: false,
11 | onClick: () => {},
12 | }
13 |
14 | const additionalProps = {
15 | picture: "https://i.ibb.co/DwVLSqF/user-profile.jpg",
16 | name: "Jane Doe",
17 | }
18 |
19 | describe("Avatar Snapshots", () => {
20 | it("Picture avatar", () => {
21 | const { asFragment } = render(
22 | ,
23 | { wrapper: ThemeProvider }
24 | )
25 | expect(asFragment()).toMatchSnapshot()
26 | })
27 | it("Name avatar", () => {
28 | const { asFragment, getByText } = render(
29 | ,
30 | { wrapper: ThemeProvider }
31 | )
32 | expect(getByText(additionalProps.name[0])).toBeInTheDocument()
33 | expect(asFragment()).toMatchSnapshot()
34 | })
35 | it("Unknown user avatar", () => {
36 | const { asFragment } = render(, {
37 | wrapper: ThemeProvider,
38 | })
39 | expect(asFragment()).toMatchSnapshot()
40 | })
41 | it("Different size picture avatar", () => {
42 | const { asFragment } = render(
43 | ,
44 | { wrapper: ThemeProvider }
45 | )
46 | expect(asFragment()).toMatchSnapshot()
47 | })
48 | it("Different size name avatar", () => {
49 | const size = "100px"
50 | const { asFragment, getByTestId } = render(
51 | ,
52 | { wrapper: ThemeProvider }
53 | )
54 | const fontSize = `calc(1.4 * ${size} / 2.4)`
55 | const lineHeight = `calc(2.0 * ${size} / 2.4)`
56 | expect(getByTestId("avatar")).toHaveStyleRule("font-size", fontSize)
57 | expect(getByTestId("avatar")).toHaveStyleRule("line-height", lineHeight)
58 | expect(asFragment()).toMatchSnapshot()
59 | })
60 | it("Different size icon avatar", () => {
61 | const { asFragment } = render(, {
62 | wrapper: ThemeProvider,
63 | })
64 | expect(asFragment()).toMatchSnapshot()
65 | })
66 | it("Disabled picture avatar", () => {
67 | const { asFragment } = render(
68 | ,
69 | { wrapper: ThemeProvider }
70 | )
71 | expect(asFragment()).toMatchSnapshot()
72 | })
73 | it("Disabled name avatar", () => {
74 | const { asFragment } = render(
75 | ,
81 | { wrapper: ThemeProvider }
82 | )
83 | expect(asFragment()).toMatchSnapshot()
84 | })
85 | it("Disabled icon avatar", () => {
86 | const { asFragment } = render(
87 | ,
88 | { wrapper: ThemeProvider }
89 | )
90 | expect(asFragment()).toMatchSnapshot()
91 | })
92 | })
93 |
94 | describe("Avatar onClick Functionality", () => {
95 | const spyOnClick = jest.spyOn(defaultProps, "onClick")
96 |
97 | afterEach(() => {
98 | spyOnClick.mockReset()
99 | })
100 | it("passed onClick function get fired when clicked", () => {
101 | const { getByTestId } = render(
102 | ,
103 | { wrapper: ThemeProvider }
104 | )
105 |
106 | fireEvent.click(getByTestId("avatar"))
107 | expect(spyOnClick).toBeCalledTimes(1)
108 | })
109 |
110 | it("passed onClick function does not get fired when clicked on Disabled Avatar", () => {
111 | const { getByTestId } = render(
112 | ,
117 | { wrapper: ThemeProvider }
118 | )
119 |
120 | fireEvent.click(getByTestId("avatar"))
121 | expect(spyOnClick).toBeCalledTimes(0)
122 | })
123 | })
124 |
--------------------------------------------------------------------------------
/src/components/Avatar/index.ts:
--------------------------------------------------------------------------------
1 | import Avatar from "./Avatar"
2 | export default Avatar
3 |
--------------------------------------------------------------------------------
/src/components/Avatar/types.ts:
--------------------------------------------------------------------------------
1 | import { SyntheticEvent } from 'react'
2 |
3 | export interface IAvatarProps extends React.ButtonHTMLAttributes {
4 | size?: string
5 | onClick?: (event?: SyntheticEvent) => void
6 | picture?: string
7 | name?: string
8 | disabled?: boolean
9 | }
10 |
11 | export interface IAvatarBaseProps
12 | extends React.ButtonHTMLAttributes {
13 | isPicture: boolean
14 | isName: boolean
15 | isIcon: boolean
16 | size: string
17 | disabled: boolean
18 | }
--------------------------------------------------------------------------------
/src/components/Breadcrumb/Breadcrumb.README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Breadcrumb
4 |
5 | ### Usage:
6 |
7 | ##### Default
8 | ```javascript
9 |
10 | Dashboard
11 | Reports
12 |
13 | 50467
14 |
15 |
16 | ```
17 |
18 | ##### Truncated
19 |
20 | ```javascript
21 | // 2 from end will be shown
22 | Dashboard
23 | Reports
24 | 50467
25 | Dashboard
26 | Reports
27 | 50467
28 |
29 | ```
30 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb/Breadcrumb.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | ReactNode,
3 | ReactElement,
4 | CSSProperties,
5 | cloneElement,
6 | useState,
7 | } from "react"
8 | import styled from "styled-components"
9 | import BreadcrumbItem, { StyledText } from "./BreadcrumbItem"
10 | import { BreadcrumbProps, IBreadCrumb } from "./types"
11 |
12 | const StyledParent = styled.div`
13 | display: flex;
14 | max-width: ${props => props.maxWidth || "initial"};
15 | flex-wrap: wrap;
16 | width: fit-content;
17 | align-items: center;
18 | `
19 |
20 | const Breadcrumb: IBreadCrumb = props => {
21 | const {
22 | className,
23 | rootStyle,
24 | maxWidth,
25 | children,
26 | truncateTo = Infinity,
27 | separator,
28 | activeStyles,
29 | childStyle,
30 | } = props
31 |
32 | const [showAll, setShowAll] = useState(false)
33 |
34 | const renderBreadcrumbs = (): ReactNode => {
35 | const updatedChildren =
36 | React.Children.count(children) > 1
37 | ? preprocessCrumbs(
38 | children as ReactNode[],
39 | separator,
40 | truncateTo,
41 | setShowAll,
42 | showAll,
43 | childStyle
44 | )
45 | : children
46 | const crumbs = React.Children.map(
47 | updatedChildren,
48 | (element: ReactNode, index) => {
49 | if (!element) {
50 | return element
51 | }
52 | return cloneElement(element as ReactElement, {
53 | key: index,
54 | childStyle,
55 | activeStyles,
56 | })
57 | }
58 | )
59 | return crumbs
60 | }
61 |
62 | return (
63 |
67 | {renderBreadcrumbs()}
68 |
69 | )
70 | }
71 |
72 | // Logic to insert separators among the list of children
73 | const preprocessCrumbs = (
74 | crumbs: ReactNode[],
75 | separator: ReactNode | string,
76 | truncateTo: number,
77 | setShowAll: Function,
78 | showAll: boolean,
79 | childStyle?: CSSProperties
80 | ) => {
81 | // Container where we insert nodes and separators in appropriate places
82 | const updatedCrumbs: ReactNode[] = []
83 | // A bool to check whether the ... is inserted or not
84 | let truncated = false
85 | // Handle the range of truncateTo
86 | if (truncateTo < 1) {
87 | truncateTo = 1
88 | console.warn(
89 | `Prop 'truncateTo' has been set to 1 since it's the lowest possible value`
90 | )
91 | }
92 | crumbs.forEach((crumb: ReactNode, index: number) => {
93 | const shouldInsertCrumbs =
94 | index === 0 || index >= crumbs.length - truncateTo || showAll
95 | if (shouldInsertCrumbs) {
96 | if (index !== crumbs.length - 1) {
97 | updatedCrumbs.push(
98 | cloneElement(crumb as ReactElement, {
99 | childStyle,
100 | })
101 | )
102 | } else {
103 | updatedCrumbs.push(
104 | cloneElement(crumb as ReactElement, {
105 | activeElement: true,
106 | childStyle,
107 | })
108 | )
109 | }
110 | // Insert nodes till last element
111 | if (index < crumbs.length - 1) {
112 | updatedCrumbs.push(
113 |
114 | {separator}
115 |
116 | )
117 | }
118 | } else if (!truncated) {
119 | updatedCrumbs.push(
120 | setShowAll(true)}>
121 | ...
122 |
123 | )
124 | updatedCrumbs.push(
125 |
126 | {separator}
127 |
128 | )
129 | truncated = true
130 | }
131 | })
132 | return updatedCrumbs
133 | }
134 |
135 | Breadcrumb.defaultProps = {
136 | separator: "/",
137 | truncateTo: Infinity,
138 | }
139 |
140 | Breadcrumb.Item = BreadcrumbItem
141 |
142 | export default Breadcrumb
143 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb/BreadcrumbItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { SFC } from "react"
2 | import styled, { css } from "styled-components"
3 | import { BreadcrumbItemProps } from "./types"
4 |
5 | const TYPOGRAPHY_SIZE = 14
6 |
7 | const sharedStyles = css`
8 | font-size: ${({ theme: { knitui } }) =>
9 | `${knitui.typography[TYPOGRAPHY_SIZE].fontSize}rem`};
10 | line-height: ${({ theme: { knitui } }) =>
11 | `${knitui.typography[TYPOGRAPHY_SIZE].lineHeight}rem`};
12 | border-radius: ${({ theme: { knitui } }) => knitui.inputBorderRadius};
13 | padding: 0 3px 0 3px;
14 | display: flex;
15 | align-items: center;
16 | &:hover {
17 | background-color: ${({ separator, theme: { knitui } }) =>
18 | separator ? "" : knitui.chromaPalette.Neutral10};
19 | }
20 | cursor: ${props => (props.separator ? "default" : "pointer")};
21 | a,
22 | * a {
23 | text-decoration: underline;
24 | color: ${({ theme: { knitui } }) => knitui.shades.blue40};
25 | }
26 | `
27 |
28 | export const StyledText = styled.span`
29 | ${sharedStyles}
30 | color: ${({ theme: { knitui } }) => knitui.chromaPalette.Neutral50};
31 | `
32 |
33 | export const StyledActive = styled.span`
34 | ${sharedStyles}
35 | color: ${({ theme: { knitui } }) => knitui.chromaPalette.Neutral90}
36 | `
37 |
38 | const BreadcrumbItem: SFC = props => {
39 | const {
40 | separator,
41 | children,
42 | childStyle,
43 | style,
44 | activeStyles,
45 | className,
46 | onClick,
47 | ...restProps
48 | } = props
49 | let link
50 | if ("activeElement" in props) {
51 | link = (
52 |
57 | {children}
58 |
59 | )
60 | } else {
61 | link = (
62 |
67 | {children}
68 |
69 | )
70 | }
71 |
72 | return link
73 | }
74 |
75 | export default BreadcrumbItem
76 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb/index.tsx:
--------------------------------------------------------------------------------
1 | import Breadcrumb from "./Breadcrumb"
2 |
3 | export default Breadcrumb
4 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb/types.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode, CSSProperties } from "react"
2 |
3 | export interface BreadcrumbProps {
4 | /** separate to be using between crumbs */
5 | separator?: string | ReactNode
6 | /** Styles applied to the root */
7 | rootStyle?: CSSProperties
8 | /** Styles applied to each child */
9 | childStyle?: CSSProperties
10 | /** Breadcrumb Items in children */
11 | children?: ReactNode[] | ReactNode
12 | /** className for user to specify their class */
13 | className?: string
14 | /** Max width of the breadcrumb to be wrapped in */
15 | maxWidth?: string
16 | /** Level till which the crumbs are to be shown (from end). First is shown anyways */
17 | truncateTo?: number
18 | /** Styles for the active item */
19 | activeStyles?: CSSProperties
20 | }
21 |
22 | export interface BreadcrumbItemProps {
23 | /** Link to the route which breadcrumb item should redirect to */
24 | href?: string
25 | /** Separator to be used */
26 | separator?: string | ReactNode
27 | /** Custom styles of the breadcrumb item */
28 | style?: CSSProperties
29 | /** className to be passed to the item */
30 | className?: string
31 | /** Styling of the active element if any */
32 | activeStyles?: CSSProperties
33 | /** Styles for all crumbs */
34 | childStyle?: CSSProperties
35 | /** onClick event */
36 | onClick?: (event) => void
37 | }
38 |
39 | export interface IBreadCrumb extends React.FC {
40 | Item: React.FC
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Button/ButtonGroup.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode, ReactElement } from "react"
2 | import styled from "styled-components"
3 | import { ButtonBase } from "./components"
4 | import { ButtonGroupProps } from "./types"
5 |
6 | const getStyleForGhostButtons = (props: ButtonGroupWrapperProps) => {
7 | const style = props.isAllGhost
8 | ? `
9 | & ${ButtonBase}:not(:last-of-type){
10 | border-right: 0px;
11 | }
12 | & ${ButtonBase}:hover,& ${ButtonBase}:focus{
13 | border-right: 1px solid;
14 | }
15 | & ${ButtonBase}:hover+${ButtonBase}:focus{
16 | margin-left: -1px;
17 | }
18 | & ${ButtonBase}:focus {
19 | box-shadow: 0 0 2px;
20 | z-index: 1;
21 | }
22 | & ${ButtonBase}:hover+${ButtonBase}, & ${ButtonBase}:focus+${ButtonBase}{
23 | border-left: 0px;
24 | }
25 | `
26 | : `
27 | & ${ButtonBase}:not(:last-of-type)::after {
28 | content: "";
29 | display: block;
30 | width: 1px;
31 | }
32 | `
33 | return style
34 | }
35 |
36 | const StyledVerticalBar = styled.hr`
37 | margin-left: -1px;
38 | border: none;
39 | border-left: 1px solid rgba(255, 255, 255, 0.2);
40 | z-index: 1;
41 | `
42 |
43 | interface ButtonGroupWrapperProps extends ButtonGroupProps {
44 | isAllGhost: boolean
45 | }
46 | const ButtonGroupWrapper = styled.div`
47 | //To have width equal to all of it's child component.
48 | display: inline-flex;
49 |
50 | & ${ButtonBase} {
51 | border-radius: 0rem;
52 | }
53 | & ${ButtonBase}:first-of-type {
54 | border-top-left-radius: 0.4rem;
55 | border-bottom-left-radius: 0.4rem;
56 | }
57 | & ${ButtonBase}:last-of-type {
58 | border-top-right-radius: 0.4rem;
59 | border-bottom-right-radius: 0.4rem;
60 | }
61 |
62 | // Style will be added if all buttons have ghost property
63 | ${props => getStyleForGhostButtons(props)}
64 | `
65 |
66 | const ButtonGroup: React.FC = props => {
67 | const { children, ...rest } = props
68 |
69 | const childrenCount = React.Children.count(children)
70 | let countGhost = 0
71 | React.Children.forEach(children, (child: ReactElement, i) => {
72 | if (child.props.ghost) countGhost += 1
73 | })
74 |
75 | const isAllGhost = countGhost === childrenCount ? true : false
76 |
77 | return (
78 |
79 | {React.Children.map(props.children, (child: ReactElement, i: Number) =>
80 | i == childrenCount - 1 ? (
81 | child
82 | ) : (
83 | <>
84 | {child}
85 | {!isAllGhost ? : null}
86 | >
87 | )
88 | )}
89 |
90 | )
91 | }
92 |
93 | export default ButtonGroup
94 |
--------------------------------------------------------------------------------
/src/components/Button/ButtonWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, SyntheticEvent } from "react"
2 | import Icon from "../Icon"
3 | import { ButtonBase, ButtonInset, ButtonContent } from "./components"
4 | import { ThemeContext } from "styled-components"
5 | import { parseCustomColor, parseColorPreset } from "../../common/_utils"
6 | import { ButtonWrapperInterface, ButtonWrapperProps } from "./types"
7 |
8 | const DEFAULT_COLOR_THEME = "neutral"
9 |
10 | const insetPositions = {
11 | LEFT: "left",
12 | RIGHT: "right",
13 | }
14 |
15 | const ButtonWrapper: ButtonWrapperInterface = ({
16 | label,
17 | ghost = false,
18 | size = "medium",
19 | href,
20 | onClick,
21 | icon,
22 | bare = false,
23 | insetLabel,
24 | insetPosition = "right",
25 | colorPreset = DEFAULT_COLOR_THEME,
26 | customColor,
27 | insetCustomColor,
28 | customProps /** (define just to exclude from rest) In case Button is wrapped in StyledComponent with customProps passed down to manipulate style,
29 | like when Button is used in other components. eg. Alerts, yet no need in Button Component itself*/,
30 | ...rest
31 | }) => {
32 | const themeContext = useContext(ThemeContext)
33 | const { knitui } = themeContext
34 |
35 | // Typography
36 | const typographySize =
37 | size === "small" ? knitui.typography[12] : knitui.typography[14]
38 | const iconSize = size === "small" ? "1.4rem" : "1.8rem"
39 | const baseFontSize = typographySize.fontSize
40 | const baseLineHeight = typographySize.lineHeight
41 | const insetFontSize = baseFontSize - knitui.baseIncrementUnit
42 | const lowerTypographyUnit = knitui.typography[`${insetFontSize * 10}`]
43 | const insetLineHeight =
44 | (lowerTypographyUnit && lowerTypographyUnit.lineHeight) || baseLineHeight
45 | // Colors
46 |
47 | // Cannot be set as default arg since theme is not available in that context
48 | const DEFAULT_INSET_THEME = {
49 | background: knitui.chromaPalette.Neutral0,
50 | font: knitui.chromaPalette.Neutral80,
51 | }
52 |
53 | const insetColorTheme = insetCustomColor
54 | ? parseCustomColor(knitui, insetCustomColor)
55 | : DEFAULT_INSET_THEME
56 | const parsedColorTheme = customColor
57 | ? parseCustomColor(knitui, customColor)
58 | : parseColorPreset(knitui, colorPreset)
59 | return (
60 |
72 | (onClick && onClick(e)) || (href && window.location.assign(href))
73 | }
74 | {...rest}>
75 | {insetLabel && insetPosition === insetPositions.LEFT && (
76 |
85 | {insetLabel}
86 |
87 | )}
88 |
99 | {icon ? : null}
100 | {label}
101 |
102 | {insetLabel && insetPosition === insetPositions.RIGHT && (
103 |
112 | {insetLabel}
113 |
114 | )}
115 |
116 | )
117 | }
118 |
119 | import ButtonGroup from "./ButtonGroup"
120 | ButtonWrapper.Group = ButtonGroup
121 | export default ButtonWrapper
122 |
--------------------------------------------------------------------------------
/src/components/Button/components/ButtonContent.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import { IStyled } from "../../../common/types"
3 |
4 | import { ButtonProps } from "../types"
5 |
6 | type IStyledBaseButton = IStyled
7 |
8 | const ButtonContent = styled.div`
9 | display: flex;
10 | align-items: center;
11 | width: 100%;
12 | justify-content: center;
13 | `
14 |
15 | export default ButtonContent
16 |
--------------------------------------------------------------------------------
/src/components/Button/components/ButtonInset.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import { IStyled } from "../../../common/types"
3 |
4 | interface ButtonInsetProps {
5 | backgroundColor: string
6 | fontColor: string
7 | fontSize: string
8 | lineHeight: string
9 | insetPosition: string
10 | size: string
11 | }
12 |
13 | const getBorderRadius = position => {
14 | if (position === "right") return "0rem 0.3rem 0.3rem 0"
15 | return "0.3rem 0 0 0.3rem"
16 | }
17 |
18 | const getVerticalMargin = size => {
19 | switch (size) {
20 | case "small":
21 | return "0.1rem"
22 | case "large":
23 | return "0.7rem"
24 | default:
25 | return "0.4rem"
26 | }
27 | }
28 |
29 | const ButtonInset = styled.div>`
30 | display: inline-flex;
31 | background-color: ${({ customProps }) => customProps.backgroundColor};
32 | color: ${({ customProps }) => customProps.fontColor};
33 | font-size: ${({ customProps }) => customProps.fontSize};
34 | line-height: ${({ customProps }) => customProps.lineHeight};
35 | border-radius: ${({ customProps }) =>
36 | getBorderRadius(customProps.insetPosition)};
37 | margin-left: ${({ customProps }) =>
38 | customProps.insetPosition === "right" ? "0.4rem" : 0};
39 | margin-right: ${({ customProps }) =>
40 | customProps.insetPosition === "right" ? 0 : "0.4rem"};
41 | span {
42 | margin: ${({ customProps }) => getVerticalMargin(customProps.size)} 0.7rem;
43 | line-height: 2rem;
44 | }
45 | `
46 |
47 | export default ButtonInset
48 |
--------------------------------------------------------------------------------
/src/components/Button/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as ButtonBase } from "./ButtonBase"
2 | export { default as ButtonInset } from "./ButtonInset"
3 | export { default as ButtonContent } from "./ButtonContent"
4 |
--------------------------------------------------------------------------------
/src/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * We can not directly `export { default as ComponentName } from "./ComponentPath"`
3 | * That will not support { ButtonGroup } to get exported. So instead we are
4 | * doing it in following way
5 | */
6 | import Button from "./ButtonWrapper"
7 | export default Button
8 |
--------------------------------------------------------------------------------
/src/components/Button/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ColorPreset,
3 | CustomColor,
4 | BaseComponentProps,
5 | } from "../../common/types"
6 | import { SyntheticEvent, ReactNode } from "react"
7 |
8 | export interface ParsedColorTheme {
9 | background: any
10 | font: any
11 | insetBackground?: any
12 | insetFont?: any
13 | }
14 |
15 | type TInsetPosition = "left" | "right"
16 |
17 | export interface ButtonBaseProps
18 | extends React.ButtonHTMLAttributes {
19 | /** The text label to be shown on the button */
20 | label?: string
21 | /**
22 | * One of a set of predefined values that are representative of
23 | * the type of action
24 | */
25 | ghost?: boolean
26 | /** Physical area occupied on the screen */
27 | size?: "small" | "medium" | "large" | "fluid"
28 | /** Only text/icon stripping the background */
29 | bare?: boolean
30 | /** An icon type to be rendered in the button */
31 | icon?: string
32 | /** An event handler to be called on click of the button */
33 | onClick?: (event: SyntheticEvent) => void
34 | }
35 |
36 | export interface ButtonWrapperProps extends ButtonBaseProps {
37 | colorPreset?: ColorPreset
38 | /** Override preset colors, should be valid CSS string */
39 | customColor?: CustomColor
40 | /** Override defaults, should be valid CSS string */
41 | insetCustomColor?: CustomColor
42 | /** An inset value, typically used for showing notifications */
43 | insetLabel?: string
44 | /** A location to navigate to on click of the button */
45 | href?: string
46 | /** Specify the inset label position */
47 | insetPosition?: TInsetPosition
48 | /** CustomProps, just to avoid, when passed from it's parent, yet no use case here */
49 | customProps?: any
50 | }
51 |
52 | export interface ButtonProps extends ButtonBaseProps {
53 | colorTheme: ParsedColorTheme
54 | fontSize: number
55 | lineHeight: number
56 | customProps?: any
57 | }
58 |
59 | export interface ButtonGroupProps extends BaseComponentProps {
60 | children: ReactNode
61 | [htmlProp: string]: any
62 | }
63 |
64 | export interface ButtonWrapperInterface extends React.FC {
65 | Group: React.FunctionComponent
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/Dropdown/Dropdown.stories.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/components/Dropdown/Dropdown.stories.tsx
--------------------------------------------------------------------------------
/src/components/Dropdown/Dropdown.styles.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clarisights/KnitUI/46bc49ce60e0b704f5f75b871c6b8a80c7aa5a28/src/components/Dropdown/Dropdown.styles.tsx
--------------------------------------------------------------------------------
/src/components/Dropdown/Dropdown.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | const Dropdown = () => {
4 | return Dropppu
5 | }
6 |
7 | export default Dropdown
8 |
--------------------------------------------------------------------------------
/src/components/Dropdown/README.md:
--------------------------------------------------------------------------------
1 | # Dropdown
2 |
--------------------------------------------------------------------------------
/src/components/Dropdown/index.ts:
--------------------------------------------------------------------------------
1 | import Dropdown from "./Dropdown"
2 |
3 | export default Dropdown
4 |
--------------------------------------------------------------------------------
/src/components/Extractor/DataStructure.ts:
--------------------------------------------------------------------------------
1 | import { NodeType, TreeNodeValueType } from "./types"
2 |
3 | export class TreeNode {
4 | value: TreeNodeValueType
5 | children: NodeType[]
6 | constructor(value) {
7 | this.value = value
8 | this.children = []
9 | }
10 |
11 | addChild(node: NodeType) {
12 | this.children.push(node)
13 | }
14 |
15 | setValue(val) {
16 | this.value = val
17 | }
18 |
19 | clearChildren() {
20 | this.children = []
21 | }
22 | }
23 |
24 | export class EditorState {
25 | root: NodeType
26 |
27 | constructor(node) {
28 | this.root = node
29 | }
30 |
31 | buildExpression = (node: NodeType = this.root): any => {
32 | let str = ""
33 | if (node.value.type !== "fn") return node.value.data
34 | node.children.forEach((child, idx) => {
35 | str += this.buildExpression(child)
36 | str += idx === node.children.length - 1 ? "" : ", "
37 | })
38 | return `${node.value.data.label} (${str})`
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/Extractor/Expression.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef, FC, SyntheticEvent } from "react"
2 | import Drop from "./Dropdown"
3 | import { TreeNode } from "./DataStructure"
4 | import { ExpressionRootPropTypes, OptionType } from "./types"
5 |
6 | const Expression: FC = (
7 | props: ExpressionRootPropTypes
8 | ) => {
9 | const {
10 | fname,
11 | node,
12 | EditorData,
13 | setExp,
14 | options,
15 | setValue,
16 | onChangeFn,
17 | expressionRootClass = "",
18 | expressionInputClass = "",
19 | validationFn,
20 | } = props
21 | const [rootFocus, setRootFocus] = useState(false)
22 | const expressionRoot = useRef(null)
23 | // find function metadata as per the given key.
24 | const fnData: OptionType | undefined = options.find(
25 | f => f.key === fname.toLowerCase()
26 | )
27 |
28 | useEffect(() => {
29 | // create nodes for all children of the given function
30 | const { params } = fnData!
31 | params!.forEach(() => {
32 | const refNode = new TreeNode({ data: "", type: "string" })
33 | node.addChild(refNode)
34 | })
35 | }, [])
36 |
37 | const findNextNode = () => {
38 | const initNode = (expressionRoot.current as any).firstElementChild
39 | if (initNode.dataset.type === "expression-root") return initNode
40 | return initNode.firstElementChild
41 | }
42 |
43 | const findPrevNode = () => {
44 | let initNode = expressionRoot.current as any
45 | if (initNode.previousElementSibling) {
46 | initNode = initNode.previousElementSibling
47 | while (
48 | initNode.lastElementChild &&
49 | initNode.dataset.type === "expression-root"
50 | ) {
51 | initNode = initNode.lastElementChild
52 | }
53 | initNode = initNode.firstElementChild
54 | } else {
55 | if (initNode.parentElement.dataset.type === "expression-root")
56 | initNode = initNode.parentElement
57 | }
58 | return initNode
59 | }
60 |
61 | const handleKeyDown = (e: React.KeyboardEvent) => {
62 | e.stopPropagation()
63 | // remove node when backspace is pressed and expression is in focus
64 | switch (e.keyCode) {
65 | case 8:
66 | case 46:
67 | if (rootFocus) {
68 | setExp(false)
69 | node.clearChildren()
70 | setValue("")
71 | }
72 | break
73 | case 39:
74 | findNextNode().focus()
75 | break
76 | case 37:
77 | findPrevNode().focus()
78 | break
79 | default:
80 | return
81 | }
82 | }
83 |
84 | // Build the dom with dropdowns for the parameters of the function
85 | const PHDom = () => {
86 | const { params } = fnData!
87 | return params!.map((param, i) => {
88 | return (
89 |
101 | )
102 | })
103 | }
104 | if (fname) {
105 | return (
106 | setRootFocus(true)}
112 | onBlur={() => setRootFocus(false)}
113 | tabIndex={0}
114 | style={{ display: "flex" }}>
115 | {fnData!.label}({PHDom()})
116 |
117 | )
118 | }
119 | return null
120 | }
121 |
122 | export default Expression
123 |
--------------------------------------------------------------------------------
/src/components/Extractor/helpers.ts:
--------------------------------------------------------------------------------
1 | export const functions = [
2 | {
3 | value: "SPLIT (dim, delimiter, occurrence_number)",
4 | label: "SPLIT",
5 | type: "function",
6 | key: "split",
7 | keyLabel: "f(x)",
8 | params: ["dim", "delimiter", "occurrence_number"],
9 | helper:
10 | "Returns the nth substring divided by a specified delimiter. Index, n, starts from 0",
11 | },
12 | {
13 | value: "CONCAT (dim1, dim2)",
14 | label: "CONCAT",
15 | key: "concat",
16 | params: ["dim1", "dim2"],
17 | type: "function",
18 | keyLabel: "f(x)",
19 | helper: "Returns the concatenation of two strings.",
20 | },
21 | {
22 | value: "SUB (dim, starting_at, ending_at)",
23 | label: "SUB",
24 | key: "sub",
25 | params: ["dim", "starting_at", "ending_at"],
26 | type: "function",
27 | keyLabel: "f(x)",
28 | helper:
29 | "Returns a substring between specified character indices. Index starts from 0",
30 | },
31 | {
32 | value: "EXTRACT (dim, prefix_string, suffix_string)",
33 | label: "EXTRACT",
34 | key: "extract",
35 | params: ["dim1", "prefix_string", "suffix_string"],
36 | type: "function",
37 | keyLabel: "f(x)",
38 | helper:
39 | "Returns a substring between the first prefix_string and first suffix_string",
40 | },
41 | ]
42 |
43 | export const staticValues = [
44 | {
45 | value: "ACCOUNT",
46 | label: "Account",
47 | type: "dimension",
48 | keyLabel: "dim",
49 | key: "account",
50 | helper: "Account dimension",
51 | },
52 | {
53 | value: "AD",
54 | label: "Ad",
55 | type: "dimension",
56 | keyLabel: "dim",
57 | key: "ad",
58 | helper: "Account ad",
59 | },
60 | ]
61 |
--------------------------------------------------------------------------------
/src/components/Extractor/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Dropdown from "./Dropdown"
3 | import { EditorState, TreeNode } from "./DataStructure"
4 | import styled from "styled-components"
5 | import { getThemeColor } from "../../common/_utils"
6 | import { ExtractorProps, EditorType, NodeType } from "./types"
7 |
8 | const stringRegex = /"([^\\"]|\\")*"/
9 |
10 | const expressionRootClass = "knit-ui_root-class"
11 | const expressionInputClass = "knit-ui_input-class"
12 |
13 | const validationFn = (val: any): boolean => {
14 | // mock api request
15 | const res = !isNaN(val) || stringRegex.test(val)
16 | // console.log(res)
17 | return res
18 | }
19 |
20 | const ExtractorWrapper = styled.div`
21 | width: 100%;
22 | display: flex;
23 | overflow: auto;
24 | align-items: center;
25 | border: 1px solid ${props => getThemeColor(props, "Azure80")};
26 | box-sizing: border-box;
27 | scrollbar-width: none; /* Firefox */
28 | -ms-overflow-style: none; /* IE 10+ */
29 | &::-webkit-scrollbar {
30 | display: none;
31 | }
32 |
33 | &:focus {
34 | box-shadow: 0 0 0.2rem #0066ff;
35 | }
36 | border-radius: 0.4rem;
37 | height: 3.2rem;
38 | font-size: 1.4rem;
39 | line-height: 2rem;
40 | padding-left: 1.4rem;
41 | input {
42 | border: 0;
43 | outline: 0;
44 | color: ${props => getThemeColor(props, "Neutral90")};
45 | height: 2rem;
46 | border-radius: 0.4rem;
47 | margin: 0 0.6rem;
48 | background-color: inherit;
49 | padding: 0 0.4rem;
50 | font-size: 1.4rem;
51 | line-height: 2rem;
52 | }
53 | input[data-value-type="dimension"] {
54 | background-color: ${props => getThemeColor(props, "Sun10")};
55 | }
56 | input[data-valid="false"] {
57 | border-bottom: 0.1rem dashed ${props => getThemeColor(props, "Crimson80")};
58 | border-radius: 0.2rem;
59 | box-sizing: border-box;
60 | }
61 | input[data-valid="true"] {
62 | border-bottom: 0;
63 | }
64 | [data-type="expression-root"] {
65 | color: ${props => getThemeColor(props, "Green80")};
66 | box-sizing: border-box;
67 | display: flex;
68 | padding: 0 0.4rem;
69 | &:focus {
70 | outline: 1px dashed ${props => getThemeColor(props, "Azure80")};
71 | box-sizing: border-box;
72 | background: rgba(209, 225, 250, 0.5);
73 | }
74 | &:not(:last-child):after {
75 | content: ",";
76 | }
77 | }
78 | [data-type="expression-input-root"] {
79 | display: flex;
80 | &:not(:last-child):after {
81 | content: ",";
82 | }
83 | & > ul {
84 | margin-top: 3rem;
85 | border: 1px solid ${props => getThemeColor(props, "Neutral20")};
86 | box-shadow: 0px 2px 7px rgba(153, 153, 153, 0.3);
87 | border-radius: 4px;
88 | padding: 1.4rem 0;
89 | width: 28rem;
90 | position: absolute;
91 | li {
92 | font-size: 1.4rem;
93 | line-height: 2rem;
94 | display: flex;
95 | justify-content: space-between;
96 | padding: 0.7rem 1.4rem;
97 | &:hover {
98 | background-color: ${props => getThemeColor(props, "Neutral10")};
99 | }
100 | span {
101 | &:nth-child(2) {
102 | color: ${props => getThemeColor(props, "Neutral45")};
103 | font-style: italic;
104 | }
105 | &:first-child {
106 | color: ${props => getThemeColor(props, "Green80")};
107 | }
108 | }
109 | }
110 | }
111 | }
112 | `
113 |
114 | const Extractor: React.FC = props => {
115 | const { onChangeFn, options } = props
116 | // initialize root node of the expression
117 | const rootNode: NodeType = new TreeNode(null)
118 | // initialize the editor state with root node
119 | const EditorData: EditorType = new EditorState(rootNode)
120 |
121 | return (
122 |
123 |
133 |
134 | )
135 | }
136 |
137 | export default Extractor
138 |
--------------------------------------------------------------------------------
/src/components/Extractor/stories/Extractor.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { storiesOf } from "@storybook/react"
3 | import { withKnobs } from "@storybook/addon-knobs"
4 | import Extractor from "../"
5 | import { functions, staticValues } from "../helpers"
6 |
7 | // functions for the extractor
8 | const options = [...functions, ...staticValues]
9 |
10 | const stories = storiesOf("Extractor", module)
11 | stories.addDecorator(withKnobs)
12 |
13 | const onChangeFn = editorState =>
14 | console.log(editorState.buildExpression(), editorState)
15 |
16 | stories
17 | .addParameters({
18 | readme: {
19 | // Show readme before story
20 | codeTheme: "shades-of-purple",
21 | // Show readme at the addons panel
22 | },
23 | })
24 | .add("Basic", () => )
25 |
--------------------------------------------------------------------------------
/src/components/Extractor/types.ts:
--------------------------------------------------------------------------------
1 | import { EditorState, TreeNode } from "./DataStructure"
2 |
3 | export interface ExtractorProps {
4 | onChangeFn: (EditorType) => void
5 | options: OptionType[]
6 | }
7 |
8 | export interface EditorType extends EditorState {}
9 |
10 | export interface NodeType extends TreeNode {}
11 |
12 | export interface OptionType {
13 | key: string
14 | type: string
15 | params?: string[]
16 | keyLabel?: string
17 | label: string
18 | }
19 |
20 | export interface TreeNodeValueType {
21 | data: OptionType
22 | type: string
23 | }
24 |
25 | export interface DropdownProps {
26 | inputRef?: React.RefObject
27 | inputPlaceholder?: string
28 | placeholder: string
29 | onKeyDown?: () => void
30 | initialFocus?: boolean
31 | node: NodeType
32 | EditorData: EditorType
33 | handleValueChange?: () => void
34 | options: OptionType[]
35 | validationFn: (val: any) => boolean
36 | inputValue?: any
37 | onChangeFn: (state: EditorType) => void
38 | expressionRootClass: string | undefined
39 | expressionInputClass: string | undefined
40 | }
41 |
42 | export interface SelectorPropTypes {
43 | inputRef: React.RefObject
44 | inputPlaceholder: string
45 | onKeyDown: (e) => void
46 | handleValueChange: (e) => void
47 | options: OptionType[]
48 | validationFn: (value: any) => boolean
49 | inputValue: string
50 | expressionInputClass: string | undefined
51 | }
52 |
53 | export interface ExpressionRootPropTypes {
54 | // function name that matches the input
55 | fname: string
56 | // parent node on which we append child dropdown as per the fn param
57 | node: NodeType
58 | // Editor data which needs to be passed down to every level
59 | EditorData: EditorType
60 | setExp: (isExpression: boolean) => void
61 | options: OptionType[]
62 | setValue: (value: string) => void
63 | onChangeFn: (state: EditorType) => void
64 | expressionRootClass: string | undefined
65 | expressionInputClass: string | undefined
66 | validationFn: (val: string) => boolean
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/Icon/Icon.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { storiesOf } from "@storybook/react"
3 | import Icon from "./index"
4 | import * as Icons from "./Icons"
5 | import { withKnobs, select } from "@storybook/addon-knobs"
6 |
7 | const stories = storiesOf("Icons", module)
8 | stories.addDecorator(withKnobs)
9 |
10 | const sizes = ["16px", "18px", "24px"]
11 | const colors = ["#000000", "#990000", "#036600"]
12 | const allIcons = Object.entries(Icons)
13 |
14 | stories
15 | .add("Icon", () => {
16 | const size = select("Size", sizes, "18px")
17 | const color = select("Color", colors, "#000000")
18 | return
19 | })
20 | .add("All icons", () => {
21 | const size = select("Size", sizes, "18px")
22 | const color = select("Color", colors, "#000000")
23 | const props = { width: size, height: size, fill: color }
24 | const allIconsDOM = allIcons.map(([iconKey, icon], index) => (
25 |
30 | {icon(props)}
31 |
32 | ))
33 | return (
34 |
40 | {allIconsDOM}
41 |
42 | )
43 | })
44 |
--------------------------------------------------------------------------------
/src/components/Icon/Icon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import * as Icons from "./Icons"
4 | import { withProps } from "../../common/_utils"
5 |
6 | export interface IIconProps {
7 | type: string
8 | size?: { width: string; height: string } | string
9 | fill?: string
10 | customStyles?: string
11 | }
12 |
13 | interface IIconWrapper {
14 | width: string
15 | height: string
16 | customStyles?: string
17 | }
18 |
19 | const StyledIconWrapper: any = withProps()(styled.span)`
20 | cursor: inherit;
21 | display: inline-block;
22 | height: ${({ height }) => height};
23 | width: ${({ width }) => width};
24 | & svg {
25 | ${({ customStyles }) => customStyles};
26 | }
27 | `
28 |
29 | const Icon: any = (props: IIconProps) => {
30 | const { type, size, fill, customStyles = "" } = props
31 | let width = "18px",
32 | height = "18px"
33 | if (typeof size === "string") {
34 | width = size
35 | height = size
36 | } else if (typeof size === "object") {
37 | width = size.width
38 | height = size.height
39 | }
40 |
41 | const svgStyles = { width, height, fill }
42 | // If type is not provided, then "oCheckBoxOutlineBlank" is used as placeholder icon
43 | const defaultType = "oCheckBoxOutlineBlank"
44 | const icon =
45 | type && Icons[type] ? Icons[type](svgStyles) : Icons[defaultType](svgStyles)
46 | return (
47 |
52 | {icon}
53 |
54 | )
55 | }
56 |
57 | export default Icon
58 |
--------------------------------------------------------------------------------
/src/components/Icon/index.tsx:
--------------------------------------------------------------------------------
1 | import Icon from "./Icon"
2 | export default Icon
3 |
--------------------------------------------------------------------------------
/src/components/Input/Input.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import InputRef from "./InputRef"
4 |
5 | import { IInputProps } from "./types"
6 |
7 | const Input = React.forwardRef((props, ref) => {
8 | return
9 | })
10 |
11 | export default Input
12 |
--------------------------------------------------------------------------------
/src/components/Input/InputAddons.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from "react"
2 | import _ from "lodash"
3 |
4 | import { IInputProps } from "./types"
5 | import { getIconSize } from "./utils"
6 |
7 | import {
8 | AddonBeforeSpan,
9 | AddonAfterSpan,
10 | AddonContainer,
11 | StyledLabel,
12 | NotificationContainer,
13 | } from "./styledComponents"
14 |
15 | const renderInputAddons = (
16 | children: React.ReactElement,
17 | props: IInputProps
18 | ) => {
19 | const {
20 | addonAfter,
21 | addonBefore,
22 | label,
23 | notification,
24 | inputSize,
25 | state,
26 | } = props
27 | const customProps = {
28 | inputSize,
29 | state,
30 | }
31 |
32 | let labelDOM: null | ReactNode = null
33 | let notificationDOM: null | ReactNode = null
34 | let addonDOM: ReactNode = children
35 |
36 | if (label) {
37 | labelDOM = label
38 | if (_.isString(label)) {
39 | labelDOM = {label}
40 | }
41 | }
42 |
43 | if (notification) {
44 | notificationDOM = notification
45 | if (_.isString(notification)) {
46 | notificationDOM = (
47 |
48 | {notification}
49 |
50 | )
51 | }
52 | }
53 |
54 | if (addonBefore) {
55 | addonDOM = (
56 |
57 | {addonBefore ? (
58 |
62 | {addonBefore}
63 |
64 | ) : null}
65 | {children}
66 |
67 | )
68 | }
69 | if (addonAfter) {
70 | addonDOM = (
71 |
72 | {children}
73 | {addonAfter ? (
74 |
78 | {addonAfter}
79 |
80 | ) : null}
81 |
82 | )
83 | }
84 | return (
85 | <>
86 | {labelDOM}
87 | {addonDOM}
88 | {notificationDOM}
89 | >
90 | )
91 | }
92 |
93 | export default renderInputAddons
94 |
--------------------------------------------------------------------------------
/src/components/Input/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Input
4 |
5 | ### Usage
6 |
7 | ```javascript
8 | import { Input } from "KnitUI"
9 |
10 | const InputWrapper = () => {
11 | const [value, setValue] = useState("")
12 |
13 | return (
14 |
18 | setValue(e.target.value)}
20 | placeholder="basic usage"
21 | />
22 |
23 | )
24 | }
25 | ```
26 |
27 | ### Props
28 |
29 | | Prop name | Type | Optional | Default | Description |
30 | | -------------- | ------------------------------------------------------------- | -------- | --------- | --------------------------------------------------------------------------- |
31 | | placeholder | string | Yes | None | This is a placeholder description |
32 | | value | string | Yes | None | This is a value of the input |
33 | | state | "success" \| "warning" \| "error" | Yes | None | To enable state of input (success, warning or error) |
34 | | label | string \| ReactNode | Yes | None | content to be shown above the input as a label |
35 | | notification | string \| ReactNode | Yes | None | content to be shown below the input as a notification |
36 | | onChange | Function | Yes | None | onChange handler |
37 | | addonAfter | string \| ReactNode | Yes | None | to show after input |
38 | | addonBefore | string \| ReactNode | Yes | None | to show before input |
39 | | inputSize | "default" \| "small" \| "lage" | Yes | "default" | the size of the inputbutton |
40 | | type | "default" \| "search" \| "number" \| "password" \| "textarea" | Yes | "default" | Type of input field to be displayed (Native HTML input types not supported) |
41 | | disabled | boolean | Yes | None | To disable the input |
42 | | textareaResize | "both" \| "horizontal" \| "vertical" \| "none" | Yes | "both" | To limit the resize for textarea |
43 |
44 | **Note: Default HTML attributes of the input element are supported.**
45 |
--------------------------------------------------------------------------------
/src/components/Input/RenderInput.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import { IInputProps } from "./types"
4 |
5 | interface IRenderInput extends IInputProps {
6 | isOverflow: boolean
7 | }
8 |
9 | import { StyledInput, StyledTextArea } from "./styledComponents"
10 |
11 | const RenderInput = React.forwardRef(
12 | (props, ref) => {
13 | const {
14 | placeholder,
15 | value,
16 | onChange,
17 | state,
18 | label,
19 | inputSize = "default",
20 | type = "default",
21 | textareaResize = "both",
22 | notification,
23 | className,
24 | style,
25 | addonAfter,
26 | addonBefore,
27 | disabled,
28 | ...rest
29 | } = props
30 | const customProps = {
31 | inputSize,
32 | state,
33 | }
34 |
35 | const commonProps = {
36 | ref,
37 | placeholder,
38 | customProps,
39 | className,
40 | style,
41 | value,
42 | onChange,
43 | disabled,
44 | ...rest,
45 | }
46 |
47 | if (props.type === "textarea") {
48 | return
49 | }
50 |
51 | return (
52 |
62 | )
63 | }
64 | )
65 |
66 | export default RenderInput
67 |
--------------------------------------------------------------------------------
/src/components/Input/index.tsx:
--------------------------------------------------------------------------------
1 | import Input from "./Input"
2 | export default Input
3 |
--------------------------------------------------------------------------------
/src/components/Input/styledComponents.tsx:
--------------------------------------------------------------------------------
1 | import styled, { css } from "styled-components"
2 |
3 | import Icon from "../Icon"
4 |
5 | import { IStyledInput } from "./types"
6 |
7 | import {
8 | getPadding,
9 | getInputBorder,
10 | getHeight,
11 | getFontSize,
12 | getLineHeight,
13 | getBackgroundColor,
14 | getBorderRadius,
15 | getColor,
16 | getBoxShadow,
17 | getSelectionBackground,
18 | getAddonPadding,
19 | } from "./utils"
20 |
21 | export const InputWrapper = styled.span`
22 | position: relative;
23 | display: flex;
24 | align-items: center;
25 | outline: none;
26 | `
27 |
28 | // Common styles for and