add storybook

This commit is contained in:
Matthias Nannt
2022-11-18 11:53:23 +01:00
parent 212fa99ab4
commit ce6269fa6f
16 changed files with 9228 additions and 381 deletions

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["formbricks"],
};

View File

@@ -0,0 +1,29 @@
const path = require("path");
module.exports = {
stories: [
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.tsx",
"../../../packages/ui/src/components/**/*.stories.mdx",
"../../../packages/ui/src/components/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
framework: "@storybook/react",
core: {
builder: "@storybook/builder-vite",
},
async viteFinal(config, { configType }) {
// customize the Vite config here
return {
...config,
resolve: {
alias: [
{
find: "@formbricks/ui",
replacement: path.resolve(__dirname, "../../../packages/ui/"),
},
],
},
};
},
};

View File

@@ -0,0 +1 @@
import "../styles.css";

View File

@@ -0,0 +1,35 @@
{
"name": "@formbricks/storybook",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "start-storybook -p 6006",
"build": "build-storybook --docs",
"preview-storybook": "serve storybook-static",
"clean": "rm -rf .turbo && rm -rf node_modules"
},
"dependencies": {
"@formbricks/ui": "workspace:*",
"next": "^13.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@formbricks/tailwind-config": "workspace:*",
"@formbricks/tsconfig": "workspace:*",
"@mdx-js/react": "^1.6.22",
"@storybook/addon-actions": "^6.5.13",
"@storybook/addon-docs": "^6.5.13",
"@storybook/addon-essentials": "^6.5.13",
"@storybook/addon-links": "^6.5.13",
"@storybook/builder-vite": "^0.2.5",
"@storybook/react": "^6.5.13",
"@vitejs/plugin-react": "^2.2.0",
"eslint-config-formbricks": "workspace:*",
"postcss": "^8.4.18",
"serve": "^14.1.1",
"tailwindcss": "^3.2.2",
"typescript": "^4.9.3",
"vite": "^3.2.4"
}
}

View File

@@ -0,0 +1,13 @@
// If you want to use other PostCSS plugins, see the following:
// https://tailwindcss.com/docs/using-with-preprocessors
const config = require("@formbricks/tailwind-config/tailwind.config.js");
module.exports = {
plugins: {
// Specifying the config is not necessary in most cases, but it is included
// here to share the same config across the entire monorepo
tailwindcss: { config },
autoprefixer: {},
},
};

View File

@@ -0,0 +1,8 @@
import { Meta } from "@storybook/addon-docs";
<Meta title="Introduction" />
<div className="text-center flex flex-col items-center">
<h1 style={{ marginBottom: "24px", marginTop: "36px" }}>Welcome to Formbricks UI</h1>
<p>Here you can find all the UI components used within the Formbricks Stack.</p>
</div>

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,3 @@
const config = require("@formbricks/tailwind-config/tailwind.config.js");
module.exports = config;

View File

@@ -0,0 +1,8 @@
/** @type {import('vite').UserConfig} */
export default defineConfig({
plugins: [react()],
resolve: {
preserveSymlinks: true,
},
});

View File

@@ -10,8 +10,17 @@ module.exports = {
theme: {
extend: {
colors: {
brandblue: colors.blue[500],
brandred: colors.red[500],
brand: {
DEFAULT: "#00E6CA",
light: "#00E6CA",
dark: "#00C4B8",
},
black: {
DEFAULT: "#0F172A",
},
},
maxWidth: {
"8xl": "88rem",
},
},
},

View File

@@ -15,16 +15,16 @@
"clean": "rm -rf dist"
},
"devDependencies": {
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"concurrently": "^7.2.2",
"eslint": "^7.32.0",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"concurrently": "^7.5.0",
"eslint": "^8.27.0",
"eslint-config-formbricks": "workspace:*",
"react": "^17.0.2",
"react": "^18.2.0",
"@formbricks/tailwind-config": "workspace:*",
"tailwindcss": "^3.1.5",
"tailwindcss": "^3.2.4",
"@formbricks/tsconfig": "workspace:*",
"tsup": "^6.1.3",
"typescript": "^4.5.2"
"tsup": "^6.5.0",
"typescript": "^4.9.3"
}
}

View File

@@ -1,44 +0,0 @@
import React from "react";
import { classNames } from "./utils";
interface Props {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
fullwidth?: boolean;
small?: boolean;
icon?: boolean;
secondary?: boolean;
[key: string]: any;
}
// button component, consuming props
const Button: React.FC<Props> = ({
children,
onClick = () => {},
disabled = false,
fullwidth = false,
small = false,
icon = false,
secondary = false,
...rest
}) => {
return (
<button
className={classNames(
`inline-flex items-center rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2`,
disabled ? "disabled:opacity-50" : "",
fullwidth ? " w-full justify-center " : "",
small ? "px-2.5 py-1.5 text-xs" : "px-5 py-3 text-sm",
icon ? "px-1.5 py-1.5 text-xs" : "px-5 py-3 text-sm",
secondary ? "bg-ui-gray-light text-red" : "bg-snoopfade text-white"
)}
onClick={onClick}
disabled={disabled}
{...rest}>
{children}
</button>
);
};
export default Button;

View File

@@ -0,0 +1,149 @@
import Link, { LinkProps } from "next/link";
import React, { forwardRef } from "react";
import clsx from "clsx";
type SVGComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
export type ButtonBaseProps = {
variant?: "highlight" | "primary" | "secondary" | "minimal" | "warn" | "alert";
size?: "base" | "sm" | "lg" | "fab" | "icon";
loading?: boolean;
disabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
StartIcon?: SVGComponent;
startIconClassName?: string;
EndIcon?: SVGComponent;
endIconClassName?: string;
shallow?: boolean;
};
export type ButtonProps = ButtonBaseProps &
(
| (Omit<JSX.IntrinsicElements["a"], "href" | "onClick"> & LinkProps)
| (Omit<JSX.IntrinsicElements["button"], "onClick"> & { href?: never })
);
export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonProps>(function Button(
props: ButtonProps,
forwardedRef
) {
const {
loading = false,
variant = "primary",
size = "base",
StartIcon,
startIconClassName,
endIconClassName,
EndIcon,
shallow,
// attributes propagated from `HTMLAnchorProps` or `HTMLButtonProps`
...passThroughProps
} = props;
// Buttons are **always** disabled if we're in a `loading` state
const disabled = props.disabled || loading;
// If pass an `href`-attr is passed it's `<a>`, otherwise it's a `<button />`
const isLink = typeof props.href !== "undefined";
const elementType = isLink ? "a" : "button";
const element = React.createElement(
elementType,
{
...passThroughProps,
disabled,
ref: forwardedRef,
className: clsx(
// base styles independent what type of button it is
"inline-flex items-center appearance-none",
// different styles depending on size
size === "sm" && "px-3 py-2 text-sm leading-4 font-medium rounded-lg",
size === "base" && "px-6 py-2 text-sm font-medium rounded-xl",
size === "lg" && "px-4 py-2 text-base font-medium rounded-lg",
size === "icon" &&
"w-10 h-10 justify-center group p-2 border rounded-lg border-transparent text-neutral-400 hover:border-gray-200 transition",
// turn button into a floating action button (fab)
size === "fab" ? "fixed" : "relative",
size === "fab" && "justify-center bottom-20 right-8 rounded-full p-4 w-14 h-14",
// different styles depending on variant
variant === "highlight" &&
(disabled
? "border border-transparent bg-gray-400 text-white"
: "text-slate-900 bg-gradient-to-b from-brand-light to-brand-dark hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-slate-900 transition ease-in-out delay-50 hover:scale-105"),
variant === "primary" &&
(disabled
? "border border-transparent bg-gray-400 text-white"
: "text-slate-900 bg-gradient-to-b from-brand-light to-brand-dark hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-slate-900"),
variant === "secondary" &&
(disabled
? "border border-gray-200 text-gray-400 bg-white"
: "hover:text-slate-600 hover:bg-slate-300 bg-slate-200 text-slate-700 hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900 dark:text-slate-400 dark:hover:text-slate-300 dark:bg-slate-800 dark:hover:bg-slate-700 transition ease-in-out delay-50 hover:scale-105"),
variant === "alert" &&
(disabled
? "border border-transparent bg-gray-400 text-white"
: "border border-transparent dark:text-darkmodebrandcontrast text-brandcontrast bg-red-600 dark:bg-darkmodebrand hover:bg-opacity-90 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-neutral-900"),
variant === "minimal" &&
(disabled
? "text-slate-400 dark:text-slate-500 bg-slate-200 dark:bg-slate-800"
: "text-slate-600 hover:text-slate-500 bg-slate-200 hover:bg-slate-100 dark:bg-slate-800 dark:text-slate-300 dark:hover:text-slate-400 dark:hover:bg-slate-900 focus:outline-none focus:ring-2 focus:ring-offset-1 dark:focus:bg-slate-900 focus:bg-slate-700 focus:ring-neutral-500 transition ease-in-out delay-50 hover:scale-105"),
variant === "warn" &&
(disabled
? "text-gray-400 bg-transparent"
: "text-gray-700 bg-transparent hover:text-red-700 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:bg-red-50 focus:ring-red-500"),
// set not-allowed cursor if disabled
loading ? "cursor-wait" : disabled ? "cursor-not-allowed" : "",
props.className
),
// if we click a disabled button, we prevent going through the click handler
onClick: disabled
? (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
}
: props.onClick,
},
<>
{StartIcon && (
<StartIcon
className={clsx(
"inline",
size === "icon" ? "h-4 w-4 " : "-ml-1 h-4 w-4 ltr:mr-2 rtl:ml-2 rtl:-mr-1",
startIconClassName || ""
)}
/>
)}
{props.children}
{loading && (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform">
<svg
className={clsx(
"mx-4 h-5 w-5 animate-spin",
variant === "primary" ? "text-white dark:text-slate-900" : "text-slate-900"
)}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
)}
{EndIcon && (
<EndIcon className={clsx("-mr-1 inline h-5 w-5 ltr:ml-2 rtl:mr-2", endIconClassName || "")} />
)}
</>
);
return props.href ? (
<Link passHref href={props.href} shallow={shallow && shallow}>
{element}
</Link>
) : (
element
);
});
export default Button;

View File

@@ -0,0 +1,60 @@
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
import { Button } from "./Button";
<Meta title="Button" component={Button} />
## Last Update: 18. Nov 2022
## Definition
Default button with multiple sizes and variants for simple interaction within the Formbricks application.
## Structure
<ArgsTable of={Button} />
### Button Styles
<>
<ul>
<li>Primary: For the primary (most important action / success path) within the given context</li>
<li>Secondary: Important buttons that are not within a single success path</li>
<li>Minimal: Buttons for actions of little significance</li>
</ul>
<h2>Primary</h2>
<Button className="sb-fake-pseudo--focus">Button text</Button>
<h2 className="examples-title">Secondary</h2>
<Button variant="secondary">Button text</Button>
<h2 className="examples-title">Minimal</h2>
<Button variant="minimal">Button text</Button>
<h2 className="examples-title">State</h2>
<h3 className="examples-title">Default</h3>
<Button>Button text</Button>
<h3 className="examples-title">Hover</h3>
<Button className="sb-pseudo--hover">Button text</Button>
</>
## Anatomy
Labels should be used to clearly state what happens when the button is clicked and what the user should expect. In addition icons can be used to support the label.
## Usage
> Please use only one primary button within a given context
> Try to limit the number of words of the label to a maximum of 2
## Variants
<Story name="Primary">
<Button variant="primary">Button text</Button>
</Story>
<Story name="Secondary">
<Button variant="secondary">Button text</Button>
</Story>

View File

@@ -1 +1 @@
export * from "./Button";
export * from "./components/Button";

9221
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff