caplink-saas-ui-shared-component-library 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -4
- package/component-library/examples/button/Button.spec.tsx +20 -0
- package/component-library/examples/button/Button.tsx +62 -0
- package/component-library/examples/button/button.css +30 -0
- package/component-library/examples/header/Header.tsx +82 -0
- package/component-library/examples/header/header.css +32 -0
- package/component-library/examples/index.d.ts +5 -0
- package/component-library/examples/page/Page.tsx +86 -0
- package/component-library/examples/page/page.css +69 -0
- package/component-library/index.d.ts +9 -0
- package/component-library/index.ts +5 -0
- package/dist/index.js +6 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -24,7 +24,28 @@ import { Button } from '@caplink/saas-ui-shared-component-library';
|
|
|
24
24
|
|
|
25
25
|
## Development
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
### Components
|
|
28
|
+
|
|
29
|
+
All the components are located in the 'component-library' folder. Each component should be in its own folder, with the component file and any other files it needs (like styles, tests, etc).
|
|
30
|
+
|
|
31
|
+
An expected component development workflow would be:
|
|
32
|
+
|
|
33
|
+
1. Create a new folder for the component
|
|
34
|
+
2. Create the component file (e.g. MyComponent.js)
|
|
35
|
+
3. Create a story file (e.g. MyComponent.stories.js)
|
|
36
|
+
4. Create a test file (e.g. MyComponent.spec.js)
|
|
37
|
+
5. Create a style file (e.g. MyComponent.css) (if needed, we use tailwindcss for styles)
|
|
38
|
+
6. If it's a complex component, create a Cypress test file (e.g. MyComponent.spec.js in the 'cypress' folder)
|
|
39
|
+
|
|
40
|
+
### Folder structure
|
|
41
|
+
|
|
42
|
+
- All the components are located in the '/component-library' folder.
|
|
43
|
+
- Each component should be in its own folder, with the component file and any other files it needs (like styles, tests, etc).
|
|
44
|
+
- Cypress tests are located in the 'cypress' folder and should follow the same folder structure as the components.
|
|
45
|
+
|
|
46
|
+
### Storybook
|
|
47
|
+
|
|
48
|
+
We use Storybook to develop and test the components. To start the Storybook server, run:
|
|
28
49
|
|
|
29
50
|
```bash
|
|
30
51
|
npm run storybook
|
|
@@ -32,9 +53,7 @@ npm run storybook
|
|
|
32
53
|
|
|
33
54
|
This will start the Storybook server and open a browser window with the Storybook UI. You can use this to develop and test the components.
|
|
34
55
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
All the components are located in the 'component-library' folder.
|
|
56
|
+
This is the gallery of components that we use to develop and test the components. It's also used to generate the documentation for the components, so it's important to keep it up to date and encompassing relevant states and use cases within the stories.
|
|
38
57
|
|
|
39
58
|
## Testing
|
|
40
59
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
|
|
5
|
+
// Test example using tsx files and components
|
|
6
|
+
|
|
7
|
+
describe("Button", () => {
|
|
8
|
+
it("renders the button in the primary state", () => {
|
|
9
|
+
render(
|
|
10
|
+
<Button
|
|
11
|
+
primary
|
|
12
|
+
label="Button"
|
|
13
|
+
backgroundColor={"#cccccc"}
|
|
14
|
+
size={"large"}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
const button = screen.getByRole("button", { name: /Button/i });
|
|
18
|
+
expect(button.className).toContain("storybook-button--primary");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import "./button.css";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Primary UI component for user interaction
|
|
7
|
+
*/
|
|
8
|
+
export const Button = ({ primary, backgroundColor, size, label, ...props }) => {
|
|
9
|
+
const mode = primary
|
|
10
|
+
? "storybook-button--primary"
|
|
11
|
+
: "storybook-button--secondary";
|
|
12
|
+
return (
|
|
13
|
+
<button
|
|
14
|
+
type="button"
|
|
15
|
+
className={[
|
|
16
|
+
"w-64", // Working Tailwind CSS example
|
|
17
|
+
"text-3xl",
|
|
18
|
+
"storybook-button",
|
|
19
|
+
`storybook-button--${size}`,
|
|
20
|
+
mode,
|
|
21
|
+
].join(" ")}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
{label}
|
|
25
|
+
<style>{`
|
|
26
|
+
button {
|
|
27
|
+
background-color: ${backgroundColor};
|
|
28
|
+
}
|
|
29
|
+
`}</style>
|
|
30
|
+
</button>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
Button.propTypes = {
|
|
35
|
+
/**
|
|
36
|
+
* Is this the principal call to action on the page?
|
|
37
|
+
*/
|
|
38
|
+
primary: PropTypes.bool,
|
|
39
|
+
/**
|
|
40
|
+
* What background color to use
|
|
41
|
+
*/
|
|
42
|
+
backgroundColor: PropTypes.string,
|
|
43
|
+
/**
|
|
44
|
+
* How large should the button be?
|
|
45
|
+
*/
|
|
46
|
+
size: PropTypes.oneOf(["small", "medium", "large"]),
|
|
47
|
+
/**
|
|
48
|
+
* Button contents
|
|
49
|
+
*/
|
|
50
|
+
label: PropTypes.string.isRequired,
|
|
51
|
+
/**
|
|
52
|
+
* Optional click handler
|
|
53
|
+
*/
|
|
54
|
+
onClick: PropTypes.func,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
Button.defaultProps = {
|
|
58
|
+
backgroundColor: null,
|
|
59
|
+
primary: false,
|
|
60
|
+
size: "medium",
|
|
61
|
+
onClick: undefined,
|
|
62
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.storybook-button {
|
|
2
|
+
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
3
|
+
font-weight: 700;
|
|
4
|
+
border: 0;
|
|
5
|
+
border-radius: 3em;
|
|
6
|
+
cursor: pointer;
|
|
7
|
+
display: inline-block;
|
|
8
|
+
line-height: 1;
|
|
9
|
+
}
|
|
10
|
+
.storybook-button--primary {
|
|
11
|
+
color: white;
|
|
12
|
+
background-color: #1ea7fd;
|
|
13
|
+
}
|
|
14
|
+
.storybook-button--secondary {
|
|
15
|
+
color: #333;
|
|
16
|
+
background-color: transparent;
|
|
17
|
+
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
|
|
18
|
+
}
|
|
19
|
+
.storybook-button--small {
|
|
20
|
+
font-size: 12px;
|
|
21
|
+
padding: 10px 16px;
|
|
22
|
+
}
|
|
23
|
+
.storybook-button--medium {
|
|
24
|
+
font-size: 14px;
|
|
25
|
+
padding: 11px 20px;
|
|
26
|
+
}
|
|
27
|
+
.storybook-button--large {
|
|
28
|
+
font-size: 16px;
|
|
29
|
+
padding: 12px 24px;
|
|
30
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import PropTypes from "prop-types";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "../button/Button";
|
|
5
|
+
import "./header.css";
|
|
6
|
+
|
|
7
|
+
export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (
|
|
8
|
+
<header>
|
|
9
|
+
<div className="storybook-header">
|
|
10
|
+
<div>
|
|
11
|
+
<svg
|
|
12
|
+
width="32"
|
|
13
|
+
height="32"
|
|
14
|
+
viewBox="0 0 32 32"
|
|
15
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
16
|
+
>
|
|
17
|
+
<g fill="none" fillRule="evenodd">
|
|
18
|
+
<path
|
|
19
|
+
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
|
|
20
|
+
fill="#FFF"
|
|
21
|
+
/>
|
|
22
|
+
<path
|
|
23
|
+
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
|
|
24
|
+
fill="#555AB9"
|
|
25
|
+
/>
|
|
26
|
+
<path
|
|
27
|
+
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
|
|
28
|
+
fill="#91BAF8"
|
|
29
|
+
/>
|
|
30
|
+
</g>
|
|
31
|
+
</svg>
|
|
32
|
+
<h1>Acme</h1>
|
|
33
|
+
</div>
|
|
34
|
+
<div>
|
|
35
|
+
{user ? (
|
|
36
|
+
<>
|
|
37
|
+
<span className="welcome">
|
|
38
|
+
Welcome, <b>{user.name}</b>!
|
|
39
|
+
</span>
|
|
40
|
+
<Button
|
|
41
|
+
size="small"
|
|
42
|
+
onClick={onLogout}
|
|
43
|
+
label="Log out"
|
|
44
|
+
primary
|
|
45
|
+
backgroundColor
|
|
46
|
+
/>
|
|
47
|
+
</>
|
|
48
|
+
) : (
|
|
49
|
+
<>
|
|
50
|
+
<Button
|
|
51
|
+
size="small"
|
|
52
|
+
onClick={onLogin}
|
|
53
|
+
label="Log in"
|
|
54
|
+
primary={false}
|
|
55
|
+
backgroundColor
|
|
56
|
+
/>
|
|
57
|
+
<Button
|
|
58
|
+
primary
|
|
59
|
+
size="small"
|
|
60
|
+
onClick={onCreateAccount}
|
|
61
|
+
label="Sign up"
|
|
62
|
+
backgroundColor
|
|
63
|
+
/>
|
|
64
|
+
</>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</header>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
Header.propTypes = {
|
|
72
|
+
user: PropTypes.shape({
|
|
73
|
+
name: PropTypes.string.isRequired,
|
|
74
|
+
}),
|
|
75
|
+
onLogin: PropTypes.func.isRequired,
|
|
76
|
+
onLogout: PropTypes.func.isRequired,
|
|
77
|
+
onCreateAccount: PropTypes.func.isRequired,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
Header.defaultProps = {
|
|
81
|
+
user: null,
|
|
82
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.storybook-header {
|
|
2
|
+
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
3
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
4
|
+
padding: 15px 20px;
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.storybook-header svg {
|
|
11
|
+
display: inline-block;
|
|
12
|
+
vertical-align: top;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.storybook-header h1 {
|
|
16
|
+
font-weight: 700;
|
|
17
|
+
font-size: 20px;
|
|
18
|
+
line-height: 1;
|
|
19
|
+
margin: 6px 0 6px 10px;
|
|
20
|
+
display: inline-block;
|
|
21
|
+
vertical-align: top;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.storybook-header button + button {
|
|
25
|
+
margin-left: 10px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.storybook-header .welcome {
|
|
29
|
+
color: #333;
|
|
30
|
+
font-size: 14px;
|
|
31
|
+
margin-right: 10px;
|
|
32
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { Header } from "../header/Header";
|
|
4
|
+
import "./page.css";
|
|
5
|
+
|
|
6
|
+
export const Page = () => {
|
|
7
|
+
const [user, setUser] = React.useState<any>();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<article>
|
|
11
|
+
<Header
|
|
12
|
+
user={user}
|
|
13
|
+
onLogin={() => setUser({ name: "Jane Doe" })}
|
|
14
|
+
onLogout={() => setUser(undefined)}
|
|
15
|
+
onCreateAccount={() => setUser({ name: "Jane Doe" })}
|
|
16
|
+
/>
|
|
17
|
+
<section className="storybook-page">
|
|
18
|
+
<h2>Pages in Storybook</h2>
|
|
19
|
+
<p>
|
|
20
|
+
We recommend building UIs with a{" "}
|
|
21
|
+
<a
|
|
22
|
+
href="https://componentdriven.org"
|
|
23
|
+
target="_blank"
|
|
24
|
+
rel="noopener noreferrer"
|
|
25
|
+
>
|
|
26
|
+
<strong>component-driven</strong>
|
|
27
|
+
</a>{" "}
|
|
28
|
+
process starting with atomic components and ending with pages.
|
|
29
|
+
</p>
|
|
30
|
+
<p>
|
|
31
|
+
Render pages with mock data. This makes it easy to build and review
|
|
32
|
+
page states without needing to navigate to them in your app. Here are
|
|
33
|
+
some handy patterns for managing page data in Storybook:
|
|
34
|
+
</p>
|
|
35
|
+
<ul>
|
|
36
|
+
<li>
|
|
37
|
+
Use a higher-level connected component. Storybook helps you compose
|
|
38
|
+
such data from the "args" of child component stories
|
|
39
|
+
</li>
|
|
40
|
+
<li>
|
|
41
|
+
Assemble data in the page component from your services. You can mock
|
|
42
|
+
these services out using Storybook.
|
|
43
|
+
</li>
|
|
44
|
+
</ul>
|
|
45
|
+
<p>
|
|
46
|
+
Get a guided tutorial on component-driven development at{" "}
|
|
47
|
+
<a
|
|
48
|
+
href="https://storybook.js.org/tutorials/"
|
|
49
|
+
target="_blank"
|
|
50
|
+
rel="noopener noreferrer"
|
|
51
|
+
>
|
|
52
|
+
Storybook tutorials
|
|
53
|
+
</a>
|
|
54
|
+
. Read more in the{" "}
|
|
55
|
+
<a
|
|
56
|
+
href="https://storybook.js.org/docs"
|
|
57
|
+
target="_blank"
|
|
58
|
+
rel="noopener noreferrer"
|
|
59
|
+
>
|
|
60
|
+
docs
|
|
61
|
+
</a>
|
|
62
|
+
.
|
|
63
|
+
</p>
|
|
64
|
+
<div className="tip-wrapper">
|
|
65
|
+
<span className="tip">Tip</span> Adjust the width of the canvas with
|
|
66
|
+
the{" "}
|
|
67
|
+
<svg
|
|
68
|
+
width="10"
|
|
69
|
+
height="10"
|
|
70
|
+
viewBox="0 0 12 12"
|
|
71
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
72
|
+
>
|
|
73
|
+
<g fill="none" fillRule="evenodd">
|
|
74
|
+
<path
|
|
75
|
+
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
|
|
76
|
+
id="a"
|
|
77
|
+
fill="#999"
|
|
78
|
+
/>
|
|
79
|
+
</g>
|
|
80
|
+
</svg>
|
|
81
|
+
Viewports addon in the toolbar
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
84
|
+
</article>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
.storybook-page {
|
|
2
|
+
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
3
|
+
font-size: 14px;
|
|
4
|
+
line-height: 24px;
|
|
5
|
+
padding: 48px 20px;
|
|
6
|
+
margin: 0 auto;
|
|
7
|
+
max-width: 600px;
|
|
8
|
+
color: #333;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.storybook-page h2 {
|
|
12
|
+
font-weight: 700;
|
|
13
|
+
font-size: 32px;
|
|
14
|
+
line-height: 1;
|
|
15
|
+
margin: 0 0 4px;
|
|
16
|
+
display: inline-block;
|
|
17
|
+
vertical-align: top;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.storybook-page p {
|
|
21
|
+
margin: 1em 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.storybook-page a {
|
|
25
|
+
text-decoration: none;
|
|
26
|
+
color: #1ea7fd;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.storybook-page ul {
|
|
30
|
+
padding-left: 30px;
|
|
31
|
+
margin: 1em 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.storybook-page li {
|
|
35
|
+
margin-bottom: 8px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.storybook-page .tip {
|
|
39
|
+
display: inline-block;
|
|
40
|
+
border-radius: 1em;
|
|
41
|
+
font-size: 11px;
|
|
42
|
+
line-height: 12px;
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
background: #e7fdd8;
|
|
45
|
+
color: #66bf3c;
|
|
46
|
+
padding: 4px 12px;
|
|
47
|
+
margin-right: 10px;
|
|
48
|
+
vertical-align: top;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.storybook-page .tip-wrapper {
|
|
52
|
+
font-size: 13px;
|
|
53
|
+
line-height: 20px;
|
|
54
|
+
margin-top: 40px;
|
|
55
|
+
margin-bottom: 40px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.storybook-page .tip-wrapper svg {
|
|
59
|
+
display: inline-block;
|
|
60
|
+
height: 12px;
|
|
61
|
+
width: 12px;
|
|
62
|
+
margin-right: 4px;
|
|
63
|
+
vertical-align: top;
|
|
64
|
+
margin-top: 3px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.storybook-page .tip-wrapper svg path {
|
|
68
|
+
fill: #1ea7fd;
|
|
69
|
+
}
|
package/dist/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "caplink-saas-ui-shared-component-library",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "A shared UI component library for React projects from Caplink",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
8
|
"storybook": "storybook dev -p 6006",
|
|
9
|
-
"build-storybook": "storybook build"
|
|
9
|
+
"build-storybook": "storybook build",
|
|
10
|
+
"build": "tsc"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
|
-
"dist"
|
|
13
|
+
"dist",
|
|
14
|
+
"component-library"
|
|
13
15
|
],
|
|
14
16
|
"keywords": [],
|
|
15
17
|
"author": "",
|