diff --git a/.storybook/main.js b/.storybook/main.js
new file mode 100644
index 0000000..33c28ab
--- /dev/null
+++ b/.storybook/main.js
@@ -0,0 +1,43 @@
+const path = require("path");
+
+module.exports = {
+ stories: ["../components/**/*.stories.@(js|jsx|ts|tsx)"],
+ addons: [
+ "@storybook/addon-links",
+ "@storybook/addon-essentials",
+ "@storybook/addon-interactions",
+ ],
+ features: {
+ interactionsDebugger: true,
+ },
+ framework: "@storybook/react",
+ webpackFinal: async (config, { configType }) => {
+ // Make whatever fine-grained changes you need
+ // config.module.rules.push({
+ // test: /\.scss$/,
+ // use: ["style-loader", "css-loader", "sass-loader"],
+ // include: path.resolve(__dirname, "../"),
+ // });
+
+ config.module.rules.push({
+ test: /\.pcss$/,
+ use: [
+ "style-loader",
+ {
+ loader: "css-loader",
+ options: {
+ modules: {
+ localIdentName: "[name]__[local]",
+ },
+ importLoaders: 1,
+ },
+ },
+ "sass-loader",
+ ],
+ include: path.resolve(__dirname, "../"),
+ });
+
+ // Return the altered config
+ return config;
+ },
+};
diff --git a/.storybook/preview.js b/.storybook/preview.js
new file mode 100644
index 0000000..48afd56
--- /dev/null
+++ b/.storybook/preview.js
@@ -0,0 +1,9 @@
+export const parameters = {
+ actions: { argTypesRegex: "^on[A-Z].*" },
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/,
+ },
+ },
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 0c79b40..c4a6bb1 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,13 @@
# RebelCode React Library
A library of React components, hooks, and other utilities that are used in RebelCode's projects.
+
+## Setup
+To install packages, run [ for best results use yarn ]
+- yarn
+
+To start storybook, run
+- yarn storybook
+
+To build package, run [ cmd throws some errors which is currently being worked on ]
+- yarn build
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000..26b57f5
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1,7 @@
+module.exports = {
+ presets: [
+ ['@babel/preset-env', {targets: {node: 'current'}}],
+ '@babel/preset-react',
+ '@babel/preset-typescript',
+ ],
+ };
\ No newline at end of file
diff --git a/components/.gitkeep b/components/.gitkeep
deleted file mode 100644
index 8b13789..0000000
--- a/components/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/components/Alert/Alert.pcss b/components/Alert/Alert.pcss
new file mode 100644
index 0000000..85b45ba
--- /dev/null
+++ b/components/Alert/Alert.pcss
@@ -0,0 +1,213 @@
+.message {
+ align-items: stretch;
+ justify-content: center;
+ position: relative;
+ padding: 5px 7px;
+ line-height: 18px;
+ font-size: 13px;
+ background: #fff;
+ border: 1px solid;
+ border-radius: 3px;
+
+ &:not(:first-child) {
+ margin-top: 10px;
+ }
+
+ &:not(:last-child) {
+ margin-bottom: 10px;
+ }
+}
+
+.centered {
+ padding: 5px 25px;
+}
+
+.shaking {
+ animation-name: shake-animation;
+ animation-duration: 0.2s;
+}
+
+.dashicon {
+ width: 16px !important;
+ height: 16px !important;
+ font-size: 16px !important;
+ line-height: 16px !important;
+ margin-top: 1px;
+}
+
+.icon {
+ composes: dashicon;
+ margin-right: 8px;
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+.logo {
+ composes: icon;
+
+ & img {
+ width: 18px;
+ height: 18px;
+ max-width: unset;
+ }
+}
+
+.content {
+ display: block;
+}
+
+.dismiss-btn {
+ position: absolute;
+ top: 5px;
+ right: 7px;
+ color: #333;
+ padding: 0 !important;
+ border: 0;
+ border-radius: 100px;
+ background: transparent;
+ box-sizing: border-box;
+ opacity: 0.5;
+
+ &:hover {
+ opacity: 1;
+ }
+
+ &:focus-visible {
+ box-shadow: 0 0 0 1px currentColor;
+ }
+
+ & :global(.dashicon) {
+ transform: translateX(1px);
+ }
+}
+
+
+@keyframes shake-animation {
+ 0%, 20%, 40%, 60%, 80%, 100% {
+ transform: translateX(0);
+ }
+ 30%, 70% {
+ transform: translateX(-2px);
+ }
+ 10%, 50%, 90% {
+ transform: translateX(2px);
+ }
+}
+
+/*
+ * COLOR VARIATIONS
+ *----------------------------
+ */
+
+/* SUCCESS */
+.success {
+ composes: message;
+ background: #ecf4eb;
+ box-shadow: 0 1px 2px rgba(37, 85, 31, 0.15);
+ border-color: rgba(61, 142, 52, 0.4);
+
+ & .icon, & .dismiss-btn {
+ color: #2e6b27;
+ }
+
+ & .content {
+ color: #122b10;
+ }
+}
+
+/* INFO */
+.info {
+ composes: message;
+ background: #e8f5f7;
+ box-shadow: 0 1px 2px rgba(14, 91, 107, 0.15);
+ border-color: rgba(24, 152, 178, 0.4);
+
+ & .icon, & .dismiss-btn {
+ color: #127286;
+ }
+
+ & .content {
+ color: #072e35;
+ }
+}
+
+/* WARNING */
+.warning {
+ composes: message;
+ background: #fff4e6;
+ box-shadow: 0 1px 2px rgba(153, 88, 0, 0.15);
+ border-color: rgba(255, 147, 0, 0.4);
+
+ & .icon, & .dismiss-btn {
+ color: #bf6e00;
+ }
+
+ & .content {
+ color: #4d2c00;
+ }
+}
+
+/* PREMIUM */
+.premium {
+ composes: message;
+ background: rgba(221, 35, 75, .1);
+ border-color: var(--sli-pro);
+
+ & .icon, & .dismiss-btn, & .content {
+ color: var(--sli-quasi-black);
+ }
+
+ & a {
+ color: #000 !important;
+ font-weight: 600;
+ }
+}
+
+/* PRO TIP */
+.pro-tip {
+ composes: message;
+ background: #eeeffa;
+ box-shadow: 0 1px 2px rgba(53, 57, 123, 0.15);
+ border-color: rgba(89, 95, 205, 0.4);
+
+ & .icon, & .dismiss-btn {
+ color: #43479a;
+ }
+
+ & .content {
+ color: #1b1d3e;
+ }
+}
+
+/* ERROR */
+.error {
+ composes: message;
+ background: #fbe9ec;
+ box-shadow: 0 1px 2px rgba(130, 22, 40, 0.15);
+ border-color: rgba(216, 36, 66, 0.4);
+
+ & .icon, & .dismiss-btn {
+ color: #a21b32;
+ }
+
+ & .content {
+ color: #410b14;
+ }
+}
+
+
+/* GREY */
+.grey {
+ composes: message;
+ background: var(--sli-wp-grey);
+ box-shadow: 0 1px 2px rgba(120, 120, 120, 0.15);
+ border-color: var(--sli-line-color);
+
+ & .icon, & .dismiss-btn {
+ color: #555;
+ }
+
+ & .content {
+ color: #222;
+ }
+}
\ No newline at end of file
diff --git a/components/Alert/index.tsx b/components/Alert/index.tsx
new file mode 100644
index 0000000..31f6c25
--- /dev/null
+++ b/components/Alert/index.tsx
@@ -0,0 +1,89 @@
+import React, { CSSProperties, ReactNode } from "react";
+import css from "./Alert.pcss";
+import { classList } from "../../utils/classes";
+import { Dashicon, DashiconTy } from "../Dashicon";
+
+type AlertType =
+ | "success"
+ | "info"
+ | "pro-tip"
+ | "premium"
+ | "warning"
+ | "error"
+ | "grey";
+
+export type Props = {
+ className?: string;
+ style?: CSSProperties;
+ children?: ReactNode;
+ type: AlertType;
+ showIcon?: boolean;
+ shake?: boolean;
+ centered?: boolean;
+ isDismissible?: boolean;
+ onDismiss?: () => void;
+};
+
+export function Alert({
+ className,
+ style,
+ children,
+ type,
+ showIcon,
+ shake,
+ centered,
+ isDismissible,
+ onDismiss,
+}: Props) {
+ const [dismissed, setDismissed] = React.useState(false);
+
+ const handleClick = () => {
+ if (isDismissible) {
+ setDismissed(true);
+ onDismiss && onDismiss();
+ }
+ };
+
+ const fullClassName = classList(
+ css[type],
+ (shake && css.shaking||""),
+ ( centered && css.centered||""),
+ className as string
+ );
+
+ return dismissed ? null : (
+
+ {showIcon && (
+
+ )}
+
+
{children}
+
+ {isDismissible && (
+
+
+
+ )}
+
+ );
+}
+
+/**
+ * Retrieves the appropriate dashicon for a given message type.
+ *
+ * @param type
+ */
+function getIconFor(type: AlertType): DashiconTy {
+ switch (type) {
+ case "success":
+ return "yes-alt";
+ case "pro-tip":
+ return "lightbulb";
+ case "error":
+ case "warning":
+ return "warning";
+ case "info":
+ default:
+ return "info";
+ }
+}
diff --git a/components/Alert/tests/__snapshots__/Alert.spec.js b/components/Alert/tests/__snapshots__/Alert.spec.js
new file mode 100644
index 0000000..d417dea
--- /dev/null
+++ b/components/Alert/tests/__snapshots__/Alert.spec.js
@@ -0,0 +1,30 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { Alert } from "../../index";
+
+afterEach(cleanup);
+
+describe("Alert", () => {
+ it("should take a snapshot", () => {
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment( )).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render( );
+
+ expect(queryByLabelText("Alert")).toBeTruthy();
+ });
+
+ it("renders text correctly", () => {
+ const { queryByText } = render(
+
+ );
+
+ expect(queryByText("Success")).toBeTruthy();
+ expect(queryByText("Failed")).toBeFalsy();
+ });
+});
diff --git a/components/Alert/tests/__snapshots__/Alert.stories.tsx b/components/Alert/tests/__snapshots__/Alert.stories.tsx
new file mode 100644
index 0000000..52b11dc
--- /dev/null
+++ b/components/Alert/tests/__snapshots__/Alert.stories.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+
+import { Alert } from '../../index';
+
+export default {
+ title: "Components/Alert",
+ component: Alert,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => ;
+
+export const Success = Template.bind({});
+Success.args = {
+ type: "success",
+ children: <>Pax Unlock more features today. Upgrade now. >
+};
+
+export const Info = Template.bind({});
+Info.args = {
+ type: "info",
+ children: <>Pax Unlock more features today. Upgrade now. >
+};
+
+export const ProTip = Template.bind({});
+ProTip.args = {
+ type: "pro-tip",
+ children: <>Pax Unlock more features today. Upgrade now. >
+};
+
+export const Premium = Template.bind({});
+Premium.args = {
+ type: "premium",
+ children: <>Pax Unlock more features today. Upgrade now. >
+};
+
+export const Warning = Template.bind({});
+Warning.args = {
+ type: "warning",
+ children: <>Pax Unlock more features today. Upgrade now. >
+};
+
+export const Error = Template.bind({});
+Error.args = {
+ type: "error",
+ children: <>Pax Unlock more features today. Upgrade now. >
+};
+
+export const Grey = Template.bind({});
+Grey.args = {
+ type: "grey",
+ children: <>Pax Unlock more features today. Upgrade now. >
+};
diff --git a/components/Alert/tests/__snapshots__/__snapshots__/Alert.spec.js.snap b/components/Alert/tests/__snapshots__/__snapshots__/Alert.spec.js.snap
new file mode 100644
index 0000000..57dc3e9
--- /dev/null
+++ b/components/Alert/tests/__snapshots__/__snapshots__/Alert.spec.js.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Alert should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/Checkbox/Checkbox.pcss b/components/Checkbox/Checkbox.pcss
new file mode 100644
index 0000000..cd6d52e
--- /dev/null
+++ b/components/Checkbox/Checkbox.pcss
@@ -0,0 +1,13 @@
+.checkbox-field {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ min-height: 40px;
+ padding: 10px 0;
+ box-sizing: border-box;
+}
+
+.aligner {
+ display: flex;
+ flex-direction: row;
+}
diff --git a/components/Checkbox/index.tsx b/components/Checkbox/index.tsx
new file mode 100644
index 0000000..525103a
--- /dev/null
+++ b/components/Checkbox/index.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+import {FlexRow, FlexColumn} from "../Flex"
+
+type Props = {
+ id?: string;
+ value?: boolean;
+ disabled?: boolean;
+ onChange: (value: boolean) => void;
+}
+
+export function Checkbox({id, value, onChange, disabled}: Props) {
+ return (
+
+
+ onChange(e.target.checked)}
+ disabled={disabled}
+ />
+
+
+ );
+}
diff --git a/components/Checkbox/tests/Checkbox.spec.js b/components/Checkbox/tests/Checkbox.spec.js
new file mode 100644
index 0000000..68478b7
--- /dev/null
+++ b/components/Checkbox/tests/Checkbox.spec.js
@@ -0,0 +1,27 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { Checkbox } from "../index";
+
+afterEach(cleanup);
+
+describe("Checkbox", () => {
+ it("should take a snapshot", () => {
+ const { asFragment } = render( {}} />);
+
+ expect(asFragment( {}} />)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render( {}} />);
+
+ expect(queryByLabelText("Checkbox")).toBeTruthy();
+ });
+
+ it("renders text correctly", () => {
+ const { queryByLabelText } = render(
+ {}} />
+ );
+
+ expect(queryByLabelText("Checkbox")).toBeTruthy();
+ });
+});
diff --git a/components/Checkbox/tests/Checkbox.stories.tsx b/components/Checkbox/tests/Checkbox.stories.tsx
new file mode 100644
index 0000000..056dc08
--- /dev/null
+++ b/components/Checkbox/tests/Checkbox.stories.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import { Checkbox } from "../index";
+
+export default {
+ title: "Components/Checkbox",
+ component: Checkbox,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Enabled = Template.bind({});
+Enabled.args = {
+ value: true,
+ disabled: false,
+};
+
+
+export const Disabled = Template.bind({});
+Disabled.args = {
+ value: true,
+ disabled: true,
+};
\ No newline at end of file
diff --git a/components/Checkbox/tests/__snapshots__/Checkbox.spec.js.snap b/components/Checkbox/tests/__snapshots__/Checkbox.spec.js.snap
new file mode 100644
index 0000000..ea1dd11
--- /dev/null
+++ b/components/Checkbox/tests/__snapshots__/Checkbox.spec.js.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Checkbox should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/CircleImage/CircleImage.pcss b/components/CircleImage/CircleImage.pcss
new file mode 100644
index 0000000..a6a1b88
--- /dev/null
+++ b/components/CircleImage/CircleImage.pcss
@@ -0,0 +1,7 @@
+.image {
+ border-radius: 100%;
+ background-size: cover;
+ background-position: center center;
+ background-repeat: no-repeat;
+ overflow: hidden;
+}
diff --git a/components/CircleImage/index.tsx b/components/CircleImage/index.tsx
new file mode 100644
index 0000000..b280a0f
--- /dev/null
+++ b/components/CircleImage/index.tsx
@@ -0,0 +1,12 @@
+import React, {forwardRef} from "react"
+import css from "./CircleImage.pcss"
+
+export type Props = React.HTMLProps & {
+ crossOrigin?: "" | "anonymous" | "use-credentials"
+}
+
+export const CircleImage = forwardRef(
+ function CircleImage({className = "", ...props}, ref) {
+ return
+ },
+)
diff --git a/components/CircleImage/tests/__snapshots__/CircleImage.spec.js b/components/CircleImage/tests/__snapshots__/CircleImage.spec.js
new file mode 100644
index 0000000..4013961
--- /dev/null
+++ b/components/CircleImage/tests/__snapshots__/CircleImage.spec.js
@@ -0,0 +1,29 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { CircleImage } from "../../index";
+
+afterEach(cleanup);
+
+describe("CircleImage", () => {
+ it("should take a snapshot", () => {
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment( )).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render( );
+
+ expect(queryByLabelText("CircleImage")).toBeTruthy();
+ });
+
+ it("renders text correctly", () => {
+ const { queryByLabelText } = render(
+
+ );
+
+ expect(queryByLabelText("CircleImage")).toBeTruthy();
+ });
+});
diff --git a/components/CircleImage/tests/__snapshots__/CircleImage.stories.tsx b/components/CircleImage/tests/__snapshots__/CircleImage.stories.tsx
new file mode 100644
index 0000000..0e19097
--- /dev/null
+++ b/components/CircleImage/tests/__snapshots__/CircleImage.stories.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+
+import { CircleImage } from '../../index';
+
+export default {
+ title: "Components/CircleImage",
+ component: CircleImage,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => ;
+
+export const Intial = Template.bind({});
+Intial.args = {
+ src: "https://www.w3schools.com/tags/smiley.gif",
+};
\ No newline at end of file
diff --git a/components/CircleImage/tests/__snapshots__/__snapshots__/CircleImage.spec.js.snap b/components/CircleImage/tests/__snapshots__/__snapshots__/CircleImage.spec.js.snap
new file mode 100644
index 0000000..3a8d9fd
--- /dev/null
+++ b/components/CircleImage/tests/__snapshots__/__snapshots__/CircleImage.spec.js.snap
@@ -0,0 +1,10 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CircleImage should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/ColorPicker/ColorPicker.pcss b/components/ColorPicker/ColorPicker.pcss
new file mode 100644
index 0000000..e875bd6
--- /dev/null
+++ b/components/ColorPicker/ColorPicker.pcss
@@ -0,0 +1,41 @@
+:root {
+ --sli-color-picker-padding: 5px;
+ --sli-color-picker-alpha-grid-size: 14px;
+ --sli-color-picker-inner-shadow: 0 0 0 5px #fff inset;
+}
+
+.button {
+ position: relative;
+ padding: 7px 12px;
+ width: 100%;
+ height: 36px;
+ border: 1px solid var(--sli-line-color);
+ border-radius: 3px;
+ cursor: pointer;
+
+ &, &:hover, &:focus-visible, &:active {
+ background-size: var(--sli-color-picker-alpha-grid-size);
+ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpSIVUYuoOGSoThZERRy1CkWoEGqFVh1MLv2CJg1Jiouj4Fpw8GOx6uDirKuDqyAIfoA4OTopukiJ/0sKLWI9OO7Hu3uPu3eAUC0yzWobBzTdNhOxqJhKr4qBVwjoRQ/6MSgzy5iTpDhajq97+Ph6F+FZrc/9ObrUjMUAn0g8ywzTJt4gnt60Dc77xCGWl1Xic+Ixky5I/Mh1xeM3zjmXBZ4ZMpOJeeIQsZhrYqWJWd7UiKeIw6qmU76Q8ljlvMVZK5ZZ/Z78hcGMvrLMdZrDiGERS5AgQkEZBRRhI0KrToqFBO1HW/iHXL9ELoVcBTByLKAEDbLrB/+D391a2ckJLykYBdpfHOdjBAjsArWK43wfO07tBPA/A1d6w1+qAjOfpFcaWvgI6N4GLq4bmrIHXO4AA0+GbMqu5KcpZLPA+xl9UxrouwU617ze6vs4fQCS1FX8Bjg4BEZzlL3e4t0dzb39e6be3w88D3KRJNOW/QAAAAZiS0dEACcAAAAB/aV4/QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+QCEhEhKGUfSx4AAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMElEQVQ4y2M8cuTIfwY8INnGlhGfPBMDhWDUgMFgAKM6A95oZph75PD/0UAc9gYAAER7B5JZPHhAAAAAAElFTkSuQmCC') var(--sli-color-picker-padding), var(--sli-color-picker-padding);
+ }
+
+ &, &:focus-visible, &:active {
+ box-shadow: var(--sli-color-picker-inner-shadow);
+ }
+
+ &:focus-visible {
+ outline: 0;
+ box-shadow: var(--sli-color-picker-inner-shadow), 0 0 0 2px var(--sli-focus-color) !important;
+ }
+}
+
+.color-preview {
+ position: absolute;
+ top: calc(var(--sli-color-picker-padding) - 1px);
+ bottom: calc(var(--sli-color-picker-padding) - 1px);
+ left: calc(var(--sli-color-picker-padding) - 1px);
+ right: calc(var(--sli-color-picker-padding) - 1px);
+}
+
+.popper {
+ z-index: 100;
+}
diff --git a/components/ColorPicker/index.tsx b/components/ColorPicker/index.tsx
new file mode 100644
index 0000000..ec9a0d0
--- /dev/null
+++ b/components/ColorPicker/index.tsx
@@ -0,0 +1,101 @@
+import React, { MutableRefObject, useEffect } from "react";
+import css from "./ColorPicker.pcss";
+import ChromePicker from "react-color/lib/components/chrome/Chrome";
+import { Color, MultiColor } from "react-color-types";
+import { useDetectTabOut } from "../../hooks/useDetectTabOut";
+import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick";
+import { useDocumentEventListener } from "../../hooks/useEventListener";
+import { colorToString } from "../../utils/colorToString";
+import { Manager, Popper, Reference } from "react-popper";
+import { mergeRefs } from "../../utils/mergeRefs";
+
+type Props = {
+ id?: string;
+ value?: Color;
+ onChange?: (c: MultiColor) => void;
+ disableAlpha?: boolean;
+};
+
+export function ColorPicker({ id, value, disableAlpha, onChange }: Props) {
+ value = value ?? "#fff";
+
+ const [color, setColor] = React.useState(value);
+ const [isOpen, setOpen] = React.useState(false);
+ const btn = React.useRef();
+ const picker = React.useRef();
+
+ const close = React.useCallback(() => setOpen(false), []);
+ const toggle = React.useCallback(() => setOpen((v) => !v), []);
+
+ const handleChange = React.useCallback(
+ (color: MultiColor) => {
+ document.getSelection() &&
+ (document.getSelection() as Selection).removeAllRanges();
+ setColor(color.rgb);
+ onChange && onChange(color);
+ },
+ [onChange]
+ );
+
+ const onKeyDown = React.useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === "Escape" && isOpen) {
+ close();
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ },
+ [isOpen]
+ );
+
+ useEffect(() => setColor(value as Color), [value]);
+ useDetectOutsideClick(btn as any, close, [picker as any]);
+ useDetectTabOut([btn as any, picker], close);
+ useDocumentEventListener("keydown", onKeyDown, [isOpen]);
+
+ const modifiers = {
+ preventOverflow: {
+ boundariesElement: document.body,
+ padding: 5,
+ },
+ };
+
+ return (
+
+
+ {({ ref }) => (
+
+
+
+ )}
+
+
+ {({ ref, style }) =>
+ isOpen && (
+
+
+
+ )
+ }
+
+
+ );
+}
diff --git a/components/ColorPicker/tests/ColorPicker.spec.js b/components/ColorPicker/tests/ColorPicker.spec.js
new file mode 100644
index 0000000..c300a41
--- /dev/null
+++ b/components/ColorPicker/tests/ColorPicker.spec.js
@@ -0,0 +1,19 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { ColorPicker } from "../index";
+
+afterEach(cleanup);
+
+describe("ColorPicker", () => {
+ it("should take a snapshot", () => {
+ const { asFragment } = render( );
+
+ expect(asFragment( )).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render( );
+
+ expect(queryByLabelText("ColorPicker")).toBeTruthy();
+ });
+});
diff --git a/components/ColorPicker/tests/ColorPicker.stories.tsx b/components/ColorPicker/tests/ColorPicker.stories.tsx
new file mode 100644
index 0000000..400b583
--- /dev/null
+++ b/components/ColorPicker/tests/ColorPicker.stories.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+import { within, userEvent } from '@storybook/testing-library';
+import { ColorPicker } from "../index";
+
+export default {
+ title: "Components/ColorPicker",
+ component: ColorPicker,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Picker = Template.bind({});
+Picker.args = {
+ value: 'blue'
+};
+
+export const ShowPicker = Template.bind({});
+ShowPicker.play = async ({ canvasElement }) => {
+ // Starts querying the component from its root element
+ const canvas = within(canvasElement);
+
+ await userEvent.click(canvas.getByTestId('colorpicker'))
+};
diff --git a/components/ColorPicker/tests/__snapshots__/ColorPicker.spec.js.snap b/components/ColorPicker/tests/__snapshots__/ColorPicker.spec.js.snap
new file mode 100644
index 0000000..ebb930c
--- /dev/null
+++ b/components/ColorPicker/tests/__snapshots__/ColorPicker.spec.js.snap
@@ -0,0 +1,16 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ColorPicker should take a snapshot 1`] = `
+
+
+
+
+
+`;
diff --git a/components/Dashicon/index.tsx b/components/Dashicon/index.tsx
new file mode 100644
index 0000000..b3f886c
--- /dev/null
+++ b/components/Dashicon/index.tsx
@@ -0,0 +1,284 @@
+import React, {HTMLAttributes} from "react";
+import {classList} from "../../utils/classes";
+
+export const Dashicon = ({icon, className, ...rest}: Props) => (
+
+);
+
+export interface Props extends HTMLAttributes {
+ icon: DashiconTy;
+}
+
+export type DashiconTy =
+ "menu"
+ | "menu-alt"
+ | "menu-alt2"
+ | "menu-alt3"
+ | "admin-site"
+ | "admin-site-alt"
+ | "admin-site-alt2"
+ | "admin-site-alt3"
+ | "dashboard"
+ | "admin-post"
+ | "admin-media"
+ | "admin-links"
+ | "admin-page"
+ | "admin-comments"
+ | "admin-appearance"
+ | "admin-plugins"
+ | "plugins-checked"
+ | "admin-users"
+ | "admin-tools"
+ | "admin-settings"
+ | "admin-network"
+ | "admin-home"
+ | "admin-generic"
+ | "admin-collapse"
+ | "filter"
+ | "admin-customizer"
+ | "admin-multisite"
+ | "welcome-write-blog"
+ | "welcome-add-page"
+ | "welcome-view-site"
+ | "welcome-widgets-menus"
+ | "welcome-comments"
+ | "welcome-learn-more"
+ | "format-aside"
+ | "format-image"
+ | "format-gallery"
+ | "format-video"
+ | "format-status"
+ | "format-quote"
+ | "format-chat"
+ | "format-audio"
+ | "camera"
+ | "camera-alt"
+ | "images-alt"
+ | "images-alt2"
+ | "video-alt"
+ | "video-alt2"
+ | "video-alt3"
+ | "media-archive"
+ | "media-audio"
+ | "media-code"
+ | "media-default"
+ | "media-document"
+ | "media-interactive"
+ | "media-spreadsheet"
+ | "media-text"
+ | "media-video"
+ | "playlist-audio"
+ | "playlist-video"
+ | "controls-play"
+ | "controls-pause"
+ | "controls-forward"
+ | "controls-skipforward"
+ | "controls-back"
+ | "controls-skipback"
+ | "controls-repeat"
+ | "controls-volumeon"
+ | "controls-volumeoff"
+ | "image-crop"
+ | "image-rotate"
+ | "image-rotate-left"
+ | "image-rotate-right"
+ | "image-flip-vertical"
+ | "image-flip-horizontal"
+ | "image-filter"
+ | "undo"
+ | "redo"
+ | "editor-bold"
+ | "editor-italic"
+ | "editor-ul"
+ | "editor-ol"
+ | "editor-ol-rtl"
+ | "editor-quote"
+ | "editor-alignleft"
+ | "editor-aligncenter"
+ | "editor-alignright"
+ | "editor-insertmore"
+ | "editor-spellcheck"
+ | "editor-expand"
+ | "editor-contract"
+ | "editor-kitchensink"
+ | "editor-underline"
+ | "editor-justify"
+ | "editor-textcolor"
+ | "editor-paste-word"
+ | "editor-paste-text"
+ | "editor-removeformatting"
+ | "editor-video"
+ | "editor-customchar"
+ | "editor-outdent"
+ | "editor-indent"
+ | "editor-help"
+ | "editor-strikethrough"
+ | "editor-unlink"
+ | "editor-rtl"
+ | "editor-ltr"
+ | "editor-break"
+ | "editor-code"
+ | "editor-paragraph"
+ | "editor-table"
+ | "align-left"
+ | "align-right"
+ | "align-center"
+ | "align-none"
+ | "lock"
+ | "unlock"
+ | "calendar"
+ | "calendar-alt"
+ | "visibility"
+ | "hidden"
+ | "post-status"
+ | "edit"
+ | "trash"
+ | "sticky"
+ | "external"
+ | "arrow-up"
+ | "arrow-down"
+ | "arrow-right"
+ | "arrow-left"
+ | "arrow-up-alt"
+ | "arrow-down-alt"
+ | "arrow-right-alt"
+ | "arrow-left-alt"
+ | "arrow-up-alt2"
+ | "arrow-down-alt2"
+ | "arrow-right-alt2"
+ | "arrow-left-alt2"
+ | "sort"
+ | "leftright"
+ | "randomize"
+ | "list-view"
+ | "excerpt-view"
+ | "grid-view"
+ | "move"
+ | "share"
+ | "share-alt"
+ | "share-alt2"
+ | "twitter"
+ | "rss"
+ | "email"
+ | "email-alt"
+ | "email-alt2"
+ | "facebook"
+ | "facebook-alt"
+ | "googleplus"
+ | "networking"
+ | "instagram"
+ | "hammer"
+ | "art"
+ | "migrate"
+ | "performance"
+ | "universal-access"
+ | "universal-access-alt"
+ | "tickets"
+ | "nametag"
+ | "clipboard"
+ | "heart"
+ | "megaphone"
+ | "schedule"
+ | "tide"
+ | "rest-api"
+ | "code-standards"
+ | "buddicons-activity"
+ | "buddicons-bbpress-logo"
+ | "buddicons-buddypress-logo"
+ | "buddicons-community"
+ | "buddicons-forums"
+ | "buddicons-friends"
+ | "buddicons-groups"
+ | "buddicons-pm"
+ | "buddicons-replies"
+ | "buddicons-topics"
+ | "buddicons-tracking"
+ | "wordpress"
+ | "wordpress-alt"
+ | "pressthis"
+ | "update"
+ | "update-alt"
+ | "screenoptions"
+ | "info"
+ | "cart"
+ | "feedback"
+ | "cloud"
+ | "translation"
+ | "tag"
+ | "category"
+ | "archive"
+ | "tagcloud"
+ | "text"
+ | "yes"
+ | "yes-alt"
+ | "no"
+ | "no-alt"
+ | "plus"
+ | "plus-alt"
+ | "plus-alt2"
+ | "minus"
+ | "dismiss"
+ | "marker"
+ | "star-filled"
+ | "star-half"
+ | "star-empty"
+ | "flag"
+ | "warning"
+ | "location"
+ | "location-alt"
+ | "vault"
+ | "shield"
+ | "shield-alt"
+ | "sos"
+ | "search"
+ | "slides"
+ | "text-page"
+ | "analytics"
+ | "chart-pie"
+ | "chart-bar"
+ | "chart-line"
+ | "chart-area"
+ | "groups"
+ | "businessman"
+ | "businesswoman"
+ | "businessperson"
+ | "id"
+ | "id-alt"
+ | "products"
+ | "awards"
+ | "forms"
+ | "testimonial"
+ | "portfolio"
+ | "book"
+ | "book-alt"
+ | "download"
+ | "upload"
+ | "backup"
+ | "clock"
+ | "lightbulb"
+ | "microphone"
+ | "desktop"
+ | "laptop"
+ | "tablet"
+ | "smartphone"
+ | "phone"
+ | "index-card"
+ | "carrot"
+ | "building"
+ | "store"
+ | "album"
+ | "palmtree"
+ | "tickets-alt"
+ | "money"
+ | "smiley"
+ | "thumbs-up"
+ | "thumbs-down"
+ | "layout"
+ | "paperclip"
+ | "database"
+ | "database-add"
+ | "database-remove"
+ | "database-view"
+ | "database-import"
+ | "database-export"
+;
diff --git a/components/Dashicon/tests/Dashicon.spec.js b/components/Dashicon/tests/Dashicon.spec.js
new file mode 100644
index 0000000..40261df
--- /dev/null
+++ b/components/Dashicon/tests/Dashicon.spec.js
@@ -0,0 +1,19 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { Dashicon } from "../index";
+
+afterEach(cleanup);
+
+describe("Dashicon", () => {
+ it("should take a snapshot", () => {
+ const { asFragment } = render( );
+
+ expect(asFragment( )).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render( );
+
+ expect(queryByLabelText("Dashicon")).toBeTruthy();
+ });
+});
diff --git a/components/Dashicon/tests/Dashicon.stories.tsx b/components/Dashicon/tests/Dashicon.stories.tsx
new file mode 100644
index 0000000..a4663a8
--- /dev/null
+++ b/components/Dashicon/tests/Dashicon.stories.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import { Dashicon } from "../index";
+
+export default {
+ title: "Components/Dashicon",
+ component: Dashicon,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Picker = Template.bind({});
+Picker.args = {
+ icon: 'menu'
+};
+
diff --git a/components/Dashicon/tests/__snapshots__/Dashicon.spec.js.snap b/components/Dashicon/tests/__snapshots__/Dashicon.spec.js.snap
new file mode 100644
index 0000000..8737042
--- /dev/null
+++ b/components/Dashicon/tests/__snapshots__/Dashicon.spec.js.snap
@@ -0,0 +1,10 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Dashicon should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/ErrorBoundary/index.tsx b/components/ErrorBoundary/index.tsx
new file mode 100644
index 0000000..af691a1
--- /dev/null
+++ b/components/ErrorBoundary/index.tsx
@@ -0,0 +1,29 @@
+import React, { ReactNode } from "react";
+
+type Props = {
+ errorContents: ReactNode;
+ children?: ReactNode;
+};
+
+type State = {
+ error: any;
+};
+
+export class ErrorBoundary extends React.Component {
+ constructor(props: Props | Readonly) {
+ super(props);
+ this.state = { error: null };
+ }
+
+ static getDerivedStateFromError(error: any) {
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error: any, errorInfo: any) {
+ console.error(error, errorInfo);
+ }
+
+ render() {
+ return this.props.errorContents;
+ }
+}
diff --git a/components/ErrorBoundary/tests/__snapshots__/ErrorBoundary.spec.js b/components/ErrorBoundary/tests/__snapshots__/ErrorBoundary.spec.js
new file mode 100644
index 0000000..0f12775
--- /dev/null
+++ b/components/ErrorBoundary/tests/__snapshots__/ErrorBoundary.spec.js
@@ -0,0 +1,15 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { ErrorBoundary } from "../../index";
+
+afterEach(cleanup);
+
+describe("ErrorBoundary", () => {
+ it("should take a snapshot", () => {
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment( )).toMatchSnapshot();
+ });
+});
diff --git a/components/ErrorBoundary/tests/__snapshots__/ErrorBoundary.stories.tsx b/components/ErrorBoundary/tests/__snapshots__/ErrorBoundary.stories.tsx
new file mode 100644
index 0000000..cac40d9
--- /dev/null
+++ b/components/ErrorBoundary/tests/__snapshots__/ErrorBoundary.stories.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+
+import { ErrorBoundary } from '../../index';
+
+export default {
+ title: "Components/ErrorBoundary",
+ component: ErrorBoundary,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => ;
+
+export const Intial = Template.bind({});
+Intial.args = {
+ errorContents: "Cannot render component",
+};
\ No newline at end of file
diff --git a/components/ErrorBoundary/tests/__snapshots__/__snapshots__/ErrorBoundary.spec.js.snap b/components/ErrorBoundary/tests/__snapshots__/__snapshots__/ErrorBoundary.spec.js.snap
new file mode 100644
index 0000000..ea6d0e3
--- /dev/null
+++ b/components/ErrorBoundary/tests/__snapshots__/__snapshots__/ErrorBoundary.spec.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ErrorBoundary should take a snapshot 1`] = ` `;
diff --git a/components/FilteredListField/index.tsx b/components/FilteredListField/index.tsx
new file mode 100644
index 0000000..adac23c
--- /dev/null
+++ b/components/FilteredListField/index.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+import {ListField, Props as InnerProps} from "../ListField";
+
+export interface Props extends InnerProps {
+ filter: Function;
+ excludeMsg?: string;
+}
+
+export function FilteredListField(props: Props) {
+ const [excluded, setExcluded] = React.useState("");
+
+ if (props.filter) {
+ setExcluded("");
+ }
+ // TODO: fix come back n fix this
+ const onChange = (values: Array) => {
+ const excludedIdx = props.filter ?
+ values.findIndex((val) => props.filter((s:string) => !values.includes(s)))
+
+ // values.findIndex((val) => props.exclude.includes(val))
+ : -1;
+
+ if (excludedIdx > -1) {
+ setExcluded(values[excludedIdx]);
+ } else {
+ props.onChange(values);
+ }
+ };
+
+ let message = undefined;
+ if (excluded.length > 0) {
+ const token = "%s";
+ const tokenIdx = props.excludeMsg && props.excludeMsg.indexOf("%s");
+
+ const before = props.excludeMsg && props.excludeMsg.substring(0, tokenIdx as number);
+ const after = props.excludeMsg&&props.excludeMsg.substring((tokenIdx as number) + token.length);
+
+ message = <>{before}{excluded}{after}>;
+ }
+
+ const newProps = {
+ ...props,
+ message,
+ onChange,
+ };
+
+ return ;
+}
diff --git a/components/FilteredListField/tests/FilteredListField.spec.js b/components/FilteredListField/tests/FilteredListField.spec.js
new file mode 100644
index 0000000..000aae1
--- /dev/null
+++ b/components/FilteredListField/tests/FilteredListField.spec.js
@@ -0,0 +1,19 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { FilteredListField } from "../index";
+
+afterEach(cleanup);
+
+describe("FilteredListField", () => {
+ it("should take a snapshot", () => {
+ const { asFragment } = render( );
+
+ expect(asFragment( )).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render( );
+
+ expect(queryByLabelText("ListField")).toBeTruthy();
+ });
+});
diff --git a/components/FilteredListField/tests/FilteredListField.stories.tsx b/components/FilteredListField/tests/FilteredListField.stories.tsx
new file mode 100644
index 0000000..c78fea3
--- /dev/null
+++ b/components/FilteredListField/tests/FilteredListField.stories.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import { FilteredListField } from "../index";
+
+export default {
+ title: "Components/FilteredListField",
+ component: FilteredListField,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const FilteredList = Template.bind({});
+FilteredList.args = {};
+
diff --git a/components/FilteredListField/tests/__snapshots__/FilteredListField.spec.js.snap b/components/FilteredListField/tests/__snapshots__/FilteredListField.spec.js.snap
new file mode 100644
index 0000000..0f43c17
--- /dev/null
+++ b/components/FilteredListField/tests/__snapshots__/FilteredListField.spec.js.snap
@@ -0,0 +1,57 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FilteredListField should take a snapshot 1`] = `
+
+
+
+
+
+
+ Type something and press enter...
+
+
+
+
+
+
+
+`;
diff --git a/components/Flex/FlexRow.stories.tsx b/components/Flex/FlexRow.stories.tsx
new file mode 100644
index 0000000..00c7bba
--- /dev/null
+++ b/components/Flex/FlexRow.stories.tsx
@@ -0,0 +1,35 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import { FlexRow, FlexColumn } from "./index";
+
+export default {
+ title: "Components/FlexRow",
+ component: FlexRow,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Row = Template.bind({});
+Row.args = {
+ justify: "flex-start",
+ children: (
+ <>
+
+ One
+ Two
+
+
+ Three
+ Four
+
+ >
+ ),
+};
+
diff --git a/components/Flex/index.tsx b/components/Flex/index.tsx
new file mode 100644
index 0000000..7444714
--- /dev/null
+++ b/components/Flex/index.tsx
@@ -0,0 +1,47 @@
+import React, {CSSProperties, forwardRef} from "react"
+
+type BaseProps = React.HTMLAttributes
+
+type FlexProps = BaseProps & {
+ dir?: "column" | "row",
+ wrap?: boolean,
+ justify?: CSSProperties["justifyContent"],
+ align?: CSSProperties["alignItems"],
+ justifySelf?: CSSProperties["justifySelf"],
+ alignSelf?: CSSProperties["alignSelf"],
+}
+
+export const Flex = forwardRef(
+ function Flex(
+ {
+ dir = "column",
+ justify = "flex-start",
+ align = "center",
+ wrap,
+ justifySelf,
+ alignSelf,
+ style,
+ ...props
+ },
+ ref,
+ ) {
+ const styles: CSSProperties = {
+ ...style,
+ display: "flex",
+ flexFlow: dir + " " + (wrap ? "wrap" : "nowrap"),
+ justifyContent: justify,
+ alignItems: align,
+ justifySelf,
+ alignSelf,
+ }
+
+ return
+ },
+)
+
+type PropsWithoutDir = Omit
+
+// @ts-ignore
+export const FlexColumn = (props: PropsWithoutDir) =>
+// @ts-ignore
+export const FlexRow = (props: PropsWithoutDir) =>
diff --git a/components/Flex/tests/FlexColumn.spec.js b/components/Flex/tests/FlexColumn.spec.js
new file mode 100644
index 0000000..5b682b0
--- /dev/null
+++ b/components/Flex/tests/FlexColumn.spec.js
@@ -0,0 +1,26 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { FlexColumn } from "../index";
+
+afterEach(cleanup);
+
+describe("FlexColumn", () => {
+ const Comp = (
+
+ Go to Posts
+ Go to Posts
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp)
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("FlexColumn")).toBeTruthy();
+ });
+});
diff --git a/components/Flex/tests/FlexColumn.stories.tsx b/components/Flex/tests/FlexColumn.stories.tsx
new file mode 100644
index 0000000..5c8e07c
--- /dev/null
+++ b/components/Flex/tests/FlexColumn.stories.tsx
@@ -0,0 +1,29 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import { FlexColumn, FlexRow } from "../index";
+
+export default {
+ title: "Components/FlexColumn",
+ component: FlexColumn,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Column = Template.bind({});
+Column.args = {
+ align: "flex-start",
+ style: { flex: 1 },
+ children: (
+ <>
+ Go to Posts
+ Go to Posts
+ >
+ ),
+};
diff --git a/components/Flex/tests/FlexRow.spec.js b/components/Flex/tests/FlexRow.spec.js
new file mode 100644
index 0000000..0ab9743
--- /dev/null
+++ b/components/Flex/tests/FlexRow.spec.js
@@ -0,0 +1,32 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { FlexRow, FlexColumn } from "../index";
+
+afterEach(cleanup);
+
+describe("FlexRow", () => {
+ const Comp = (
+
+
+ One
+ Two
+
+
+ Three
+ Four
+
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("FlexRow")).toBeTruthy();
+ });
+});
diff --git a/components/Flex/tests/__snapshots__/FlexColumn.spec.js.snap b/components/Flex/tests/__snapshots__/FlexColumn.spec.js.snap
new file mode 100644
index 0000000..9958257
--- /dev/null
+++ b/components/Flex/tests/__snapshots__/FlexColumn.spec.js.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FlexColumn should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/Flex/tests/__snapshots__/FlexRow.spec.js.snap b/components/Flex/tests/__snapshots__/FlexRow.spec.js.snap
new file mode 100644
index 0000000..8125825
--- /dev/null
+++ b/components/Flex/tests/__snapshots__/FlexRow.spec.js.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FlexRow should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/HelpTooltip/HelpTooltip.pcss b/components/HelpTooltip/HelpTooltip.pcss
new file mode 100644
index 0000000..74d44c3
--- /dev/null
+++ b/components/HelpTooltip/HelpTooltip.pcss
@@ -0,0 +1,60 @@
+.root {
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.tooltip {
+ composes: z-high from "../../styles/layout.pcss";
+}
+
+.tooltip-container {
+ text-align: left;
+ padding-top: 7px;
+ padding-bottom: 7px;
+}
+
+.tooltip-content {
+ & p {
+ margin: 0 0 5px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ & img {
+ max-width: 100%;
+ margin: 5px 0;
+ border-radius: 2px;
+
+ &:first-child {
+ margin-top: 0;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
+
+.icon {
+ height: 18px;
+ line-height: 18px;
+
+ & :global .dashicons {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+ line-height: 16px;
+ vertical-align: bottom;
+ }
+}
+
+.icon-visible {
+ opacity: 1;
+}
+
+.icon-notvisible {
+ opcaity: 0.7;
+}
\ No newline at end of file
diff --git a/components/HelpTooltip/index.tsx b/components/HelpTooltip/index.tsx
new file mode 100644
index 0000000..653f25d
--- /dev/null
+++ b/components/HelpTooltip/index.tsx
@@ -0,0 +1,45 @@
+import React, {ReactNode} from "react";
+import css from "./HelpTooltip.pcss";
+import {Dashicon} from "../Dashicon";
+import Tooltip from "../Tooltip";
+import { classList } from "../../utils/classes";
+
+type Props = {
+ maxWidth?: number;
+ children: ReactNode;
+}
+
+export default function HelpTooltip({maxWidth, children}: Props) {
+ maxWidth = maxWidth ?? 300;
+
+ const [isTooltipVisible, setIsTooltipVisible] = React.useState(false);
+
+ const handleMouseOver = () => setIsTooltipVisible(true);
+ const handleMouseOut = () => setIsTooltipVisible(false);
+
+ const tooltipTheme = {
+ content: css.tooltipContent,
+ container: css.tooltipContainer,
+ };
+
+ const all = classList(css.icon, isTooltipVisible ? css.iconVisible : css.iconNotvisible)
+
+ return (
+
+
+ {
+ ({ref}) => (
+
+
+
+ )
+ }
+
+ {children}
+
+
+ );
+};
diff --git a/components/HelpTooltip/tests/HelpTooltip.spec.js b/components/HelpTooltip/tests/HelpTooltip.spec.js
new file mode 100644
index 0000000..c7d081d
--- /dev/null
+++ b/components/HelpTooltip/tests/HelpTooltip.spec.js
@@ -0,0 +1,25 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import HelpTooltip from "../index";
+
+afterEach(cleanup);
+
+describe("HelpTooltip", () => {
+ const Comp = (
+
+ One, Two. Three
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("HelpTooltip")).toBeTruthy();
+ });
+});
diff --git a/components/HelpTooltip/tests/HelpTooltip.stories.tsx b/components/HelpTooltip/tests/HelpTooltip.stories.tsx
new file mode 100644
index 0000000..9847c6f
--- /dev/null
+++ b/components/HelpTooltip/tests/HelpTooltip.stories.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import HelpTooltip from "../index";
+
+export default {
+ title: "Components/HelpTooltip",
+ component: HelpTooltip,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Help = Template.bind({});
+Help.args = {
+ maxWidth: 200,
+ children: <>One, Two. Three>
+};
+
diff --git a/components/HelpTooltip/tests/__snapshots__/HelpTooltip.spec.js.snap b/components/HelpTooltip/tests/__snapshots__/HelpTooltip.spec.js.snap
new file mode 100644
index 0000000..fff2442
--- /dev/null
+++ b/components/HelpTooltip/tests/__snapshots__/HelpTooltip.spec.js.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`HelpTooltip should take a snapshot 1`] = `
+
+
+
+
+
+
+
+`;
diff --git a/components/Icon/index.tsx b/components/Icon/index.tsx
new file mode 100644
index 0000000..d77991c
--- /dev/null
+++ b/components/Icon/index.tsx
@@ -0,0 +1,21 @@
+import React, {forwardRef, ReactNode} from "react"
+
+type BaseProps = Omit, "children">;
+
+type Props = BaseProps & {
+ icon: ReactNode;
+ width?: string;
+ height?: string;
+ viewBox?: string;
+ fill?: string;
+ style?: object;
+ std?: boolean;
+};
+
+export const Icon = forwardRef(
+ ({ icon, std, viewBox, ...props }, ref) => (
+
+ {icon}
+
+ )
+);
diff --git a/components/Icon/tests/__snapshots__/Icon.spec.js b/components/Icon/tests/__snapshots__/Icon.spec.js
new file mode 100644
index 0000000..8cfa816
--- /dev/null
+++ b/components/Icon/tests/__snapshots__/Icon.spec.js
@@ -0,0 +1,23 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { Icon } from "../../index";
+
+afterEach(cleanup);
+
+describe("Icon", () => {
+ const Comp = (
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment( )).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("Icon")).toBeTruthy();
+ });
+});
diff --git a/components/Icon/tests/__snapshots__/Icon.stories.tsx b/components/Icon/tests/__snapshots__/Icon.stories.tsx
new file mode 100644
index 0000000..4442b8f
--- /dev/null
+++ b/components/Icon/tests/__snapshots__/Icon.stories.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+
+import { Icon } from '../../index';
+
+export default {
+ title: "Components/Icon",
+ component: Icon,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => ;
+
+export const Intial = Template.bind({});
+Intial.args = {};
\ No newline at end of file
diff --git a/components/Icon/tests/__snapshots__/__snapshots__/Icon.spec.js.snap b/components/Icon/tests/__snapshots__/__snapshots__/Icon.spec.js.snap
new file mode 100644
index 0000000..4466661
--- /dev/null
+++ b/components/Icon/tests/__snapshots__/__snapshots__/Icon.spec.js.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Icon should take a snapshot 1`] = `
+
+
+
+
+
+`;
diff --git a/components/ListField/index.tsx b/components/ListField/index.tsx
new file mode 100644
index 0000000..a4e7162
--- /dev/null
+++ b/components/ListField/index.tsx
@@ -0,0 +1,129 @@
+import React, {KeyboardEvent, ReactElement, useEffect} from "react";
+import CreatableSelect from "react-select";
+import {Alert} from "../Alert";
+import {SelectStyles} from "../Select";
+
+const components = {
+ DropdownIndicator: null,
+};
+
+const createOption = (label: string) => ({
+ label,
+ value: label,
+});
+
+export type Props = {
+ id?: string;
+ value: Array;
+ onChange: (value: Array) => void;
+ sanitize?: (value: string) => string;
+ autoFocus?: boolean;
+ message?: string | ReactElement;
+}
+
+export function ListField({id, value, onChange, sanitize, autoFocus, message}: Props) {
+ const [inputValue, setInputValue] = React.useState("");
+ const [duplicate, setDuplicate] = React.useState(-1);
+ const [currMessage, setCurrMessage] = React.useState();
+
+ useEffect(() => {
+ setCurrMessage(message);
+ }, [message]);
+
+ value = Array.isArray(value) ? value : [];
+ const values = value.map((v) => createOption(v));
+
+ const addValue = () => {
+ if (inputValue.length) {
+ setInputValue("");
+ handleChange([...values, createOption(inputValue)]);
+ }
+ };
+
+ const handleChange = (value: any) => {
+ if (!onChange) return;
+
+ let dupeIdx = -1;
+
+ if (!value) {
+ value = [];
+ } else {
+ value = value
+ .map((opt: { value: string; }) => (opt && sanitize) ? sanitize(opt.value) : opt.value)
+ .filter((val: any, idx: any, list: string | any[]) => {
+ const firstIndex = list.indexOf(val);
+
+ if (firstIndex !== idx) {
+ dupeIdx = firstIndex;
+
+ return false;
+ }
+
+ return !!val;
+ });
+ }
+
+ setDuplicate(dupeIdx);
+
+ if (dupeIdx === -1) {
+ onChange(value);
+ }
+ };
+
+ const handleInputChange = (inputValue: string) => {
+ setInputValue(inputValue);
+ };
+
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (!inputValue) return;
+
+ switch (event.key) {
+ case ",":
+ case "Enter":
+ case "Tab":
+ addValue();
+ event.preventDefault();
+ break;
+ }
+ };
+
+ const styles = SelectStyles();
+
+ return (
+ <>
+
+ {
+ (duplicate < 0 || values.length === 0) ? null : (
+
+ {values[duplicate].label} is already in the list
+
+ )
+ }
+ {
+ (!currMessage) ? null : (
+
+ {currMessage}
+
+ )
+ }
+ >
+ );
+}
diff --git a/components/ListField/tests/ListField.spec.js b/components/ListField/tests/ListField.spec.js
new file mode 100644
index 0000000..f6fd2e7
--- /dev/null
+++ b/components/ListField/tests/ListField.spec.js
@@ -0,0 +1,23 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import {ListField} from "../index";
+
+afterEach(cleanup);
+
+describe("ListField", () => {
+ const Comp = (
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("ListField")).toBeTruthy();
+ });
+});
diff --git a/components/ListField/tests/ListField.stories.tsx b/components/ListField/tests/ListField.stories.tsx
new file mode 100644
index 0000000..1da0b11
--- /dev/null
+++ b/components/ListField/tests/ListField.stories.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import {ListField} from "../index";
+
+export default {
+ title: "Components/ListField",
+ component: ListField,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Empty = Template.bind({});
+Empty.args = {};
+
+
+export const Initial = Template.bind({});
+Initial.args = {
+ value: ["ampo", "shoe"],
+ autoFocus: true
+};
+
diff --git a/components/ListField/tests/__snapshots__/ListField.spec.js.snap b/components/ListField/tests/__snapshots__/ListField.spec.js.snap
new file mode 100644
index 0000000..641a12d
--- /dev/null
+++ b/components/ListField/tests/__snapshots__/ListField.spec.js.snap
@@ -0,0 +1,102 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ListField should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/Masonry/Masonry.pcss b/components/Masonry/Masonry.pcss
new file mode 100644
index 0000000..57357bb
--- /dev/null
+++ b/components/Masonry/Masonry.pcss
@@ -0,0 +1,28 @@
+.root {
+ display: grid;
+ justify-content: flex-start;
+ align-items: stretch;
+}
+
+.column {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: stretch;
+
+ &:last-of-type {
+ margin-right: 0 !important;
+ }
+}
+
+.cell {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: stretch;
+
+ &:last-of-type {
+ margin-bottom: 0 !important;
+ }
+}
diff --git a/components/Masonry/index.tsx b/components/Masonry/index.tsx
new file mode 100644
index 0000000..5991454
--- /dev/null
+++ b/components/Masonry/index.tsx
@@ -0,0 +1,36 @@
+import React, {CSSProperties} from "react";
+import css from "./Masonry.pcss";
+import {splitArray} from "../../utils/splitArray"
+
+export type Props = {
+ columns: number;
+ gap?: string | number;
+ children: any;
+}
+
+export function Masonry({columns, gap, children}: Props) {
+ gap = gap ?? 0;
+
+ const style: CSSProperties = {
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
+ columnGap: gap,
+ };
+
+ const cellCss: CSSProperties = {
+ marginBottom: gap,
+ };
+
+ return (
+
+ {splitArray(children, columns).map((column, colIdx) => (
+
+ {column.map((child: any, rowIdx) => (
+
+ {child}
+
+ ))}
+
+ ))}
+
+ );
+}
diff --git a/components/Masonry/tests/__snapshots__/Masonry.spec.js b/components/Masonry/tests/__snapshots__/Masonry.spec.js
new file mode 100644
index 0000000..db5a290
--- /dev/null
+++ b/components/Masonry/tests/__snapshots__/Masonry.spec.js
@@ -0,0 +1,26 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { Masonry } from "../../index";
+
+afterEach(cleanup);
+
+describe("Skeleton", () => {
+ const Comp = (
+
+ One
+ Two
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("Masonry")).toBeTruthy();
+ });
+});
diff --git a/components/Masonry/tests/__snapshots__/Masonry.stories.tsx b/components/Masonry/tests/__snapshots__/Masonry.stories.tsx
new file mode 100644
index 0000000..b268e74
--- /dev/null
+++ b/components/Masonry/tests/__snapshots__/Masonry.stories.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+
+import { Masonry } from '../../index';
+
+export default {
+ title: "Components/Masonry",
+ component: Masonry,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => ;
+
+export const Intial = Template.bind({});
+Intial.args = {
+ children: [One , Two ],
+ columns:2
+};
\ No newline at end of file
diff --git a/components/Masonry/tests/__snapshots__/__snapshots__/Masonry.spec.js.snap b/components/Masonry/tests/__snapshots__/__snapshots__/Masonry.spec.js.snap
new file mode 100644
index 0000000..4496116
--- /dev/null
+++ b/components/Masonry/tests/__snapshots__/__snapshots__/Masonry.spec.js.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Skeleton should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/OrSeparator/OrSeperator.pcss b/components/OrSeparator/OrSeperator.pcss
new file mode 100644
index 0000000..ca69da4
--- /dev/null
+++ b/components/OrSeparator/OrSeperator.pcss
@@ -0,0 +1,14 @@
+.container {
+ margin: 25px 0;
+}
+
+.line {
+ flex: 1;
+ height: 1px;
+ background: #d3d8dc;
+}
+
+.text {
+ margin: 0 12px;
+ font-size: 16px;
+}
diff --git a/components/OrSeparator/index.tsx b/components/OrSeparator/index.tsx
new file mode 100644
index 0000000..8ab2e91
--- /dev/null
+++ b/components/OrSeparator/index.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+import css from "./OrSeperator.pcss";
+import { FlexRow } from "../Flex";
+
+export function OrSeparator() {
+ return (
+
+
+ OR
+
+
+ );
+}
diff --git a/components/OrSeparator/tests/OrSeparator.spec.js b/components/OrSeparator/tests/OrSeparator.spec.js
new file mode 100644
index 0000000..2a4714b
--- /dev/null
+++ b/components/OrSeparator/tests/OrSeparator.spec.js
@@ -0,0 +1,23 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import {OrSeparator} from "../index";
+
+afterEach(cleanup);
+
+describe("OrSeparator", () => {
+ const Comp = (
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("OrSeparator")).toBeTruthy();
+ });
+});
diff --git a/components/OrSeparator/tests/OrSeparator.stories.tsx b/components/OrSeparator/tests/OrSeparator.stories.tsx
new file mode 100644
index 0000000..2fedcc0
--- /dev/null
+++ b/components/OrSeparator/tests/OrSeparator.stories.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import {OrSeparator} from "../index";
+
+export default {
+ title: "Components/OrSeparator",
+ component: OrSeparator,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Initial = Template.bind({});
+Initial.args = {};
+
diff --git a/components/OrSeparator/tests/__snapshots__/OrSeparator.spec.js.snap b/components/OrSeparator/tests/__snapshots__/OrSeparator.spec.js.snap
new file mode 100644
index 0000000..a183b1a
--- /dev/null
+++ b/components/OrSeparator/tests/__snapshots__/OrSeparator.spec.js.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`OrSeparator should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/RadioGroup/RadioGroup.pcss b/components/RadioGroup/RadioGroup.pcss
new file mode 100644
index 0000000..1a53abc
--- /dev/null
+++ b/components/RadioGroup/RadioGroup.pcss
@@ -0,0 +1,15 @@
+.radio-group {
+ display: flex;
+ flex-direction: column;
+}
+
+.disabled {
+ composes: radio-group;
+ composes: disabled from "../../styles/theme.pcss";
+}
+
+.option {
+ display: flex;
+ flex-direction: row;
+ margin: 5px 0;
+}
diff --git a/components/RadioGroup/index.tsx b/components/RadioGroup/index.tsx
new file mode 100644
index 0000000..6b3dc0d
--- /dev/null
+++ b/components/RadioGroup/index.tsx
@@ -0,0 +1,44 @@
+import React, {ChangeEvent} from "react";
+// import "../../styles/fields.pcss";
+import css from "./RadioGroup.pcss";
+
+export type RadioOption = {
+ value: string | number,
+ label: string
+}
+
+type Props = {
+ name?: string;
+ className?: string;
+ value: string | number;
+ onChange?: (value: any) => void;
+ options: Array;
+ disabled?: boolean;
+}
+
+export function RadioGroup({name, className, disabled, value, onChange, options}: Props) {
+ const handleChange = (e: ChangeEvent) => {
+ (!disabled && e.target.checked && onChange) && onChange(e.target.value);
+ };
+
+ className = (disabled ? css.disabled : css.radioGroup) + " " + (className ?? "");
+
+ return (
+
+ {
+ options.map((option, idx) => (
+
+
+ {option.label}
+
+ ))
+ }
+
+ );
+}
diff --git a/components/RadioGroup/tests/RadioGroup.spec.js b/components/RadioGroup/tests/RadioGroup.spec.js
new file mode 100644
index 0000000..63f8fdd
--- /dev/null
+++ b/components/RadioGroup/tests/RadioGroup.spec.js
@@ -0,0 +1,32 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { RadioGroup } from "../index";
+
+afterEach(cleanup);
+
+describe("RadioGroup", () => {
+ const Comp = (
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("RadioGroup")).toBeTruthy();
+ });
+});
diff --git a/components/RadioGroup/tests/RadioGroup.stories.tsx b/components/RadioGroup/tests/RadioGroup.stories.tsx
new file mode 100644
index 0000000..9b689ab
--- /dev/null
+++ b/components/RadioGroup/tests/RadioGroup.stories.tsx
@@ -0,0 +1,67 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import { RadioGroup } from "../index";
+
+export default {
+ title: "Components/RadioGroup",
+ component: RadioGroup,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Initial = Template.bind({});
+Initial.args = {
+ name: "multiChoice",
+ value: "multipleDaily",
+ options: [
+ {
+ value: "multipleDaily",
+ label: "Multiple times a day.",
+ },
+ {
+ value: "onceDaily",
+ label: "At least once a day.",
+ },
+ {
+ value: "fewTimesWeekly",
+ label: "A few times a week.",
+ },
+ {
+ value: "onceWeekly",
+ label: "Once a week or less often.",
+ },
+ ],
+};
+
+
+export const Disabled = Template.bind({});
+Disabled.args = {
+ disabled: true,
+ name: "multiChoice",
+ value: "multipleDaily",
+ options: [
+ {
+ value: "multipleDaily",
+ label: "Multiple times a day.",
+ },
+ {
+ value: "onceDaily",
+ label: "At least once a day.",
+ },
+ {
+ value: "fewTimesWeekly",
+ label: "A few times a week.",
+ },
+ {
+ value: "onceWeekly",
+ label: "Once a week or less often.",
+ },
+ ],
+};
\ No newline at end of file
diff --git a/components/RadioGroup/tests/__snapshots__/RadioGroup.spec.js.snap b/components/RadioGroup/tests/__snapshots__/RadioGroup.spec.js.snap
new file mode 100644
index 0000000..67d2f1d
--- /dev/null
+++ b/components/RadioGroup/tests/__snapshots__/RadioGroup.spec.js.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RadioGroup should take a snapshot 1`] = `
+
+
+
+
+
+ Multiple times a day.
+
+
+
+
+`;
diff --git a/components/Select/Select.pcss b/components/Select/Select.pcss
new file mode 100644
index 0000000..bccd9e7
--- /dev/null
+++ b/components/Select/Select.pcss
@@ -0,0 +1,11 @@
+$wp-blue: #007cba;
+$primary-color: $wp-blue;
+$washed-color: mix(desaturate($primary-color, 50%), #fff, 10%);
+$shadow-color: rgba(20, 25, 60, 0.32);
+
+:export {
+ primaryColor: $primary-color;
+ shadowColor: $shadow-color;
+ washedColor: $washed-color;
+ }
+
\ No newline at end of file
diff --git a/components/Select/index.tsx b/components/Select/index.tsx
new file mode 100644
index 0000000..8264245
--- /dev/null
+++ b/components/Select/index.tsx
@@ -0,0 +1,135 @@
+import React, {ForwardedRef, MutableRefObject, ReactElement} from "react";
+import ReactSelect from "react-select";
+import CreatableSelect from "react-select/creatable";
+import AsyncSelect from "react-select/async";
+import {classList} from "../../utils/classes";
+import "../../styles/fields.pcss";
+import css from "./Select.pcss";
+
+export type SelectOption = {
+ value: any;
+ label: string | ReactElement;
+}
+
+export declare type SelectChangeHandler = (option: SelectOption) => void;
+
+type Props = {
+ id?: string;
+ className?: string;
+ value?: string;
+ placeholder?: string;
+ onChange?: SelectChangeHandler;
+ options?: Array;
+ isSearchable?: boolean;
+ isMulti?: boolean;
+ isClearable?: boolean;
+ isCreatable?: boolean;
+ width?: string | number;
+ menuIsOpen?: boolean;
+ isValidNewOption?: (value: string) => boolean;
+
+ [k: string]: any;
+}
+
+export const SelectStyles = (props: Record = {}) => ({
+ option: (prev: any, state: any) => ({
+ ...prev,
+ cursor: "pointer",
+ lineHeight: "24px",
+ }),
+ menu: (prev: any, state: any) => ({
+ ...prev,
+ margin: "6px 0",
+ boxShadow: "0 2px 8px " + css.shadowColor,
+ overflow: "hidden",
+ }),
+ menuList: (prev: any, state: any) => ({
+ padding: "0px",
+ }),
+ control: (prev: any, state: { isFocused: any; }) => {
+ let style = {
+ ...prev,
+ cursor: "pointer",
+ lineHeight: "2",
+ minHeight: "40px",
+ };
+
+ if (state.isFocused) {
+ style.borderColor = css.primaryColor;
+ style.boxShadow = `0 0 0 1px ${css.primaryColor}`;
+ }
+
+ return style;
+ },
+ valueContainer: (prev: any, state: any) => ({
+ ...prev,
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingRight: 0,
+ }),
+ container: (prev: any, state: any) => ({
+ ...prev,
+ width: props.width || "100%",
+ }),
+ multiValue: (prev: any, state: any) => ({
+ ...prev,
+ padding: "0 6px",
+ }),
+ input: (prev: any, state: any) => ({
+ ...prev,
+ outline: "0 transparent !important",
+ border: "0 transparent !important",
+ boxShadow: "0 0 0 transparent !important",
+ }),
+ indicatorSeparator: (prev: any, state: any) => ({
+ ...prev,
+ margin: "0",
+ backgroundColor: "transparent",
+ }),
+ menuPortal: (base: any) => ({
+ ...base,
+ zIndex: 9999999
+ })
+});
+
+export const Select = React.forwardRef((props: Props, ref: ForwardedRef) => {
+ const options = props.options ?? [];
+ const value = options.find((opt) => opt.value === props.value);
+
+ props = {
+ ...props,
+ id: undefined,
+ className: classList("react-select", props.className as string),
+ classNamePrefix: "react-select",
+ inputId: props.id,
+ menuPosition: "absolute",
+ };
+
+ const styles = SelectStyles(props);
+
+ const theme = (theme: { colors: any; }) => ({
+ ...theme,
+ borderRadius: 3,
+ colors: {
+ ...theme.colors,
+ primary: css.primaryColor,
+ primary25: css.washedColor,
+ },
+ });
+
+ const Component = props.isCreatable ? CreatableSelect : props.async ? AsyncSelect : ReactSelect;
+
+ return (
+
+ );
+});
diff --git a/components/Select/tests/Select.spec.js b/components/Select/tests/Select.spec.js
new file mode 100644
index 0000000..dee9a6f
--- /dev/null
+++ b/components/Select/tests/Select.spec.js
@@ -0,0 +1,35 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { Select } from "../index";
+
+afterEach(cleanup);
+
+describe("Select", () => {
+ const Comp = (
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("Select")).toBeTruthy();
+ });
+});
diff --git a/components/Select/tests/Select.stories.tsx b/components/Select/tests/Select.stories.tsx
new file mode 100644
index 0000000..bc7da5e
--- /dev/null
+++ b/components/Select/tests/Select.stories.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import { Select } from "../index";
+
+export default {
+ title: "Components/Select",
+ component: Select,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Initial = Template.bind({});
+Initial.args = {
+ name: "multiChoice",
+ value: "multipleDaily"
+};
+
+export const Options = Template.bind({});
+Options.args = {
+ name: "country",
+ options: [
+ {
+ value: "Malta",
+ label: "Malta",
+ },
+ {
+ value: "France",
+ label: "France",
+ },
+ {
+ value: "Ghana",
+ label: "Ghana",
+ },
+ ],
+};
+
diff --git a/components/Select/tests/__snapshots__/Select.spec.js.snap b/components/Select/tests/__snapshots__/Select.spec.js.snap
new file mode 100644
index 0000000..ec7cfde
--- /dev/null
+++ b/components/Select/tests/__snapshots__/Select.spec.js.snap
@@ -0,0 +1,83 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Select should take a snapshot 1`] = `
+
+
+
+`;
diff --git a/components/Skeleton/SkeletonMedia.pcss b/components/Skeleton/SkeletonMedia.pcss
new file mode 100644
index 0000000..8806204
--- /dev/null
+++ b/components/Skeleton/SkeletonMedia.pcss
@@ -0,0 +1,35 @@
+.loading {
+ position: relative;
+ padding-bottom: 100%;
+ border-radius: 2px;
+ background-color: var(--sli-grey);
+ animation-name: flashing;
+ animation-duration: 900ms;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+
+ &::before, &::after {
+ content: ' ';
+ position: absolute;
+ inset: 0;
+ }
+
+ &::before {
+ background: #fff;
+ z-index: 1;
+ }
+
+ &::after {
+ background: #e5e5e5;
+ z-index: 2;
+ }
+}
+
+@keyframes flashing {
+ from, to {
+ opacity: .4;
+ }
+ 50% {
+ opacity: 1;
+ }
+}
diff --git a/components/Skeleton/index.tsx b/components/Skeleton/index.tsx
new file mode 100644
index 0000000..6c53984
--- /dev/null
+++ b/components/Skeleton/index.tsx
@@ -0,0 +1,11 @@
+import React, {forwardRef} from "react";
+import { classList } from "../../utils/classes";
+import css from "./SkeletonMedia.pcss";
+
+export type Props = React.ComponentPropsWithoutRef<"div">
+
+export const Skeleton = forwardRef(
+ function Skeleton({className, ...props}, ref) {
+ return
+ },
+)
diff --git a/components/Skeleton/tests/__snapshots__/Skeleton.spec.js b/components/Skeleton/tests/__snapshots__/Skeleton.spec.js
new file mode 100644
index 0000000..4892759
--- /dev/null
+++ b/components/Skeleton/tests/__snapshots__/Skeleton.spec.js
@@ -0,0 +1,26 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { Skeleton } from "../../index";
+
+afterEach(cleanup);
+
+describe("Skeleton", () => {
+ const Comp = (
+
+ One
+ Two
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("Skeleton")).toBeTruthy();
+ });
+});
diff --git a/components/Skeleton/tests/__snapshots__/Skeleton.stories.tsx b/components/Skeleton/tests/__snapshots__/Skeleton.stories.tsx
new file mode 100644
index 0000000..a88c95c
--- /dev/null
+++ b/components/Skeleton/tests/__snapshots__/Skeleton.stories.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+
+import { Skeleton } from '../../index';
+
+export default {
+ title: "Components/Skeleton",
+ component: Skeleton,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => ;
+
+export const Intial = Template.bind({});
+Intial.args = {};
\ No newline at end of file
diff --git a/components/Skeleton/tests/__snapshots__/__snapshots__/Skeleton.spec.js.snap b/components/Skeleton/tests/__snapshots__/__snapshots__/Skeleton.spec.js.snap
new file mode 100644
index 0000000..cc4a222
--- /dev/null
+++ b/components/Skeleton/tests/__snapshots__/__snapshots__/Skeleton.spec.js.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Skeleton should take a snapshot 1`] = `
+
+
+
+ One
+
+
+ Two
+
+
+
+`;
diff --git a/components/Square/Square.pcss b/components/Square/Square.pcss
new file mode 100644
index 0000000..4aa9f14
--- /dev/null
+++ b/components/Square/Square.pcss
@@ -0,0 +1,18 @@
+.filler {
+ position: relative;
+ padding-bottom: 100%;
+ box-sizing: border-box;
+}
+
+.positioner {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ z-index: 0;
+
+ & > *:first-child {
+ inset: 0;
+ flex: 1;
+ }
+}
diff --git a/components/Square/index.tsx b/components/Square/index.tsx
new file mode 100644
index 0000000..bb53c60
--- /dev/null
+++ b/components/Square/index.tsx
@@ -0,0 +1,15 @@
+import React, {HTMLAttributes} from "react";
+import css from "./Square.pcss";
+import {classList} from "../../utils/classes";
+
+export type Props = HTMLAttributes & {};
+
+export function Square({className, children, ...props}: Props) {
+ return (
+
+ );
+}
diff --git a/components/Square/tests/Square.spec.js b/components/Square/tests/Square.spec.js
new file mode 100644
index 0000000..19b1599
--- /dev/null
+++ b/components/Square/tests/Square.spec.js
@@ -0,0 +1,31 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import { Square } from "../index";
+
+afterEach(cleanup);
+
+describe("Square", () => {
+ const Comp = (
+
+
+
+ Lorem Ipsum is simply dummy text of the printing and typesetting
+ industry. Lorem Ipsum has been the industry's standard dummy text ever
+ since the 1500s
+
+
+
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("Square")).toBeTruthy();
+ });
+});
diff --git a/components/Square/tests/Square.stories.tsx b/components/Square/tests/Square.stories.tsx
new file mode 100644
index 0000000..e86176f
--- /dev/null
+++ b/components/Square/tests/Square.stories.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+import { ComponentStory, ComponentMeta } from "@storybook/react";
+
+import { Square } from "../index";
+
+export default {
+ title: "Components/Square",
+ component: Square,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen",
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+export const Initial = Template.bind({});
+Initial.args = {
+ children: (
+
+
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s
+
+ ),
+ style: {
+ textAlign: "center",
+ padding: "20px"
+ }
+};
diff --git a/components/Square/tests/__snapshots__/Square.spec.js.snap b/components/Square/tests/__snapshots__/Square.spec.js.snap
new file mode 100644
index 0000000..8980c29
--- /dev/null
+++ b/components/Square/tests/__snapshots__/Square.spec.js.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Square should take a snapshot 1`] = `
+
+
+
+
+
+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s
+
+
+
+
+
+`;
diff --git a/components/Table/Table.pcss b/components/Table/Table.pcss
new file mode 100644
index 0000000..76ca58e
--- /dev/null
+++ b/components/Table/Table.pcss
@@ -0,0 +1,48 @@
+.table {
+ composes: subtle-drop-shadow slightly-rounded from '../../styles/theme.pcss';
+ width: 100%;
+ margin-bottom: 20px;
+ background: #fff;
+ box-sizing: border-box;
+ border-collapse: collapse;
+ overflow: hidden;
+}
+
+.header {
+ border-bottom: 1px solid var(--sli-line-color);
+}
+
+.footer {
+ border-top: 1px solid var(--sli-line-color);
+}
+
+.cell {
+ padding: 10px 14px;
+ box-sizing: border-box;
+}
+
+.col-heading {
+ composes: cell;
+ font-size: 14px;
+ font-weight: normal;
+}
+
+.row {
+ background: #fff;
+}
+
+.row:nth-child(odd) {
+ background: var(--sli-wp-light-grey);
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-right {
+ text-align: right;
+}
+
+.align-center {
+ text-align: center;
+}
diff --git a/components/Table/index.tsx b/components/Table/index.tsx
new file mode 100644
index 0000000..6984454
--- /dev/null
+++ b/components/Table/index.tsx
@@ -0,0 +1,105 @@
+import React, {ReactNode} from "react";
+import { classList } from "../../utils/classes";
+import "../../styles/colors.pcss";
+import css from "./Table.pcss";
+
+interface Props {
+ className?: string;
+ cols: Array>;
+ rows: Array;
+ footerCols?: boolean;
+ styleMap?: TableStyleMap;
+}
+
+export interface Column {
+ id: string;
+ label: string;
+ align?: "left" | "right" | "center";
+ render: (row: T, idx: number) => ReactNode;
+}
+
+export interface TableStyleMap {
+ cols: { [k: string]: string };
+ cells: { [k: string]: string };
+}
+
+export default function Table({className, cols, rows, footerCols, styleMap}: Props) {
+ styleMap = styleMap ?? {cols: {}, cells: {}};
+
+ return (
+
+
+
+
+
+
+ {rows.map((row, idx) =>
)}
+
+
+ {footerCols && (
+
+
+
+ )}
+
+ );
+}
+
+interface RowProps {
+ idx: number;
+ row: T;
+ cols: Array>;
+ styleMap: TableStyleMap;
+}
+
+function Row({idx, row, cols, styleMap}: RowProps) {
+ return (
+
+ {cols.map(col => {
+ return (
+
+ {col.render(row, idx)}
+
+ );
+ })}
+
+ );
+}
+
+interface ColHeadersProps {
+ cols: Array>;
+ styleMap: TableStyleMap;
+}
+
+function ColHeaders({cols, styleMap}: ColHeadersProps) {
+ return (
+
+ {cols.map(col => {
+ const className = classList(css.colHeading, alignClass(col), styleMap.cols[col.id]);
+
+ return (
+
+ {col.label}
+
+ );
+ })}
+
+ );
+}
+
+/**
+ * Determines the correct class to use for alignment.
+ *
+ * @param col The column config.
+ */
+function alignClass(col: Column) {
+ if (col.align === "center") {
+ return css.alignCenter;
+ }
+
+ if (col.align === "right") {
+ return css.alignRight;
+ }
+
+ return css.alignLeft;
+}
diff --git a/components/Table/tests/__snapshots__/Table.spec.js b/components/Table/tests/__snapshots__/Table.spec.js
new file mode 100644
index 0000000..112d506
--- /dev/null
+++ b/components/Table/tests/__snapshots__/Table.spec.js
@@ -0,0 +1,38 @@
+import React from "react";
+import { render, cleanup } from "@testing-library/react";
+import Table from "../../index";
+
+afterEach(cleanup);
+
+describe("Table", () => {
+ const Comp = (
+ col ,
+ },
+ ]}
+ rows={[
+ {
+ id: "name",
+ label: "Name",
+ render: (feed) => rows ,
+ },
+ ]}
+ />
+ );
+
+ it("should take a snapshot", () => {
+ const { asFragment } = render(Comp);
+
+ expect(asFragment(Comp)).toMatchSnapshot();
+ });
+
+ it("renders correctly", () => {
+ const { queryByLabelText } = render(Comp);
+
+ expect(queryByLabelText("Table")).toBeTruthy();
+ });
+});
diff --git a/components/Table/tests/__snapshots__/Table.stories.tsx b/components/Table/tests/__snapshots__/Table.stories.tsx
new file mode 100644
index 0000000..d9db4a5
--- /dev/null
+++ b/components/Table/tests/__snapshots__/Table.stories.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+
+import Table from '../../index';
+
+export default {
+ title: "Components/Table",
+ component: Table,
+ parameters: {
+ // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
+ layout: 'fullscreen',
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => ;
+
+export const Intial = Template.bind({});
+Intial.args = {
+ cols: [
+ {
+ id: "name",
+ label: "Name",
+ render: () => col ,
+ },
+ ],
+ rows: [
+ {
+ id: "name",
+ label: "Name",
+ render: () => rows ,
+ }]
+};
\ No newline at end of file
diff --git a/components/Table/tests/__snapshots__/__snapshots__/Table.spec.js.snap b/components/Table/tests/__snapshots__/__snapshots__/Table.spec.js.snap
new file mode 100644
index 0000000..690ecb8
--- /dev/null
+++ b/components/Table/tests/__snapshots__/__snapshots__/Table.spec.js.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Table should take a snapshot 1`] = `
+
+
+
+
+
+
+
+ col
+
+
+
+
+
+
+`;
diff --git a/styles/.gitkeep b/components/TextArea/TextArea.pcss
similarity index 100%
rename from styles/.gitkeep
rename to components/TextArea/TextArea.pcss
diff --git a/components/TextArea/index.tsx b/components/TextArea/index.tsx
new file mode 100644
index 0000000..bf43490
--- /dev/null
+++ b/components/TextArea/index.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+// import "../../styles/fields.pcss";
+import "./TextArea.pcss"
+
+type Props = {
+ id?: string;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export function TextArea({id, value, onChange}: Props) {
+ return (
+