@keeper-security/keeper-js-ui 0.0.0

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.
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ extends: ['@commitlint/config-conventional'],
3
+ }
package/.czrc ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "path": "cz-conventional-changelog"
3
+ }
package/.eslintrc.cjs ADDED
@@ -0,0 +1,19 @@
1
+ module.exports = {
2
+ root: true,
3
+ env: { browser: true, es2020: true },
4
+ extends: [
5
+ 'eslint:recommended',
6
+ 'plugin:@typescript-eslint/recommended',
7
+ 'plugin:react-hooks/recommended',
8
+ 'plugin:storybook/recommended',
9
+ ],
10
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
11
+ parser: '@typescript-eslint/parser',
12
+ plugins: ['react-refresh'],
13
+ rules: {
14
+ 'react-refresh/only-export-components': [
15
+ 'warn',
16
+ { allowConstantExport: true },
17
+ ],
18
+ },
19
+ }
@@ -0,0 +1,19 @@
1
+ on:
2
+ push:
3
+ branches:
4
+ - main
5
+
6
+ permissions:
7
+ contents: write # creates a release commit
8
+ pull-requests: write # creates a release PR
9
+
10
+ name: release-please
11
+
12
+ jobs:
13
+ release-please:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: googleapis/release-please-action@v4
17
+ with:
18
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
19
+ release-type: node
@@ -0,0 +1 @@
1
+ npx --no -- commitlint --edit $1
@@ -0,0 +1 @@
1
+ npx lint-staged
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ '!(*.ts)': 'prettier --write --ignore-unknown',
3
+ '*.ts': ['eslint --fix', 'prettier --write'],
4
+ }
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ v18
package/.prettierrc ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "semi": false,
3
+ "singleQuote": true
4
+ }
@@ -0,0 +1,23 @@
1
+ import type { StorybookConfig } from '@storybook/react-vite'
2
+
3
+ const config: StorybookConfig = {
4
+ stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
5
+ addons: [
6
+ '@storybook/addon-onboarding',
7
+ '@storybook/addon-links',
8
+ '@storybook/addon-essentials',
9
+ '@chromatic-com/storybook',
10
+ '@storybook/addon-interactions',
11
+ '@storybook/addon-a11y',
12
+ '@storybook/addon-themes',
13
+ 'storybook-addon-rtl',
14
+ ],
15
+ framework: {
16
+ name: '@storybook/react-vite',
17
+ options: {},
18
+ },
19
+ typescript: {
20
+ reactDocgen: 'react-docgen-typescript',
21
+ },
22
+ }
23
+ export default config
@@ -0,0 +1,33 @@
1
+ import type { Preview } from '@storybook/react'
2
+ import { withThemeByClassName } from '@storybook/addon-themes'
3
+ import { withDirection } from './withDirection'
4
+ import '../src/index.css'
5
+
6
+ const preview: Preview = {
7
+ decorators: [
8
+ withDirection,
9
+ withThemeByClassName({
10
+ themes: {
11
+ light: '',
12
+ dark: 'dark',
13
+ 'high contrast light': 'contrast-light',
14
+ 'high contrast dark': 'contrast-dark',
15
+ },
16
+ // Set the theme based on user's browser pref.
17
+ defaultTheme: window.matchMedia?.('(prefers-color-scheme: dark)').matches
18
+ ? 'dark'
19
+ : 'light',
20
+ }),
21
+ ],
22
+ parameters: {
23
+ controls: {
24
+ matchers: {
25
+ color: /(background|color)$/i,
26
+ date: /Date$/i,
27
+ },
28
+ },
29
+ },
30
+ tags: ['autodocs'],
31
+ }
32
+
33
+ export default preview
@@ -0,0 +1,22 @@
1
+ import React from 'react'
2
+ import { RTL_UPDATE_EVENT, type RTLChangeEvent } from 'storybook-addon-rtl'
3
+ import { useChannel } from 'storybook/internal/preview-api'
4
+ import { DirectionProvider } from '@radix-ui/react-direction'
5
+
6
+ type Direction = 'ltr' | 'rtl'
7
+
8
+ export function withDirection(Story) {
9
+ const [direction, setDirection] = React.useState<Direction>('ltr')
10
+
11
+ useChannel({
12
+ [RTL_UPDATE_EVENT]: (rtlChangeEvent: RTLChangeEvent) => {
13
+ setDirection(rtlChangeEvent.direction)
14
+ },
15
+ })
16
+
17
+ return (
18
+ <DirectionProvider dir={direction}>
19
+ <Story />
20
+ </DirectionProvider>
21
+ )
22
+ }
package/README.md ADDED
@@ -0,0 +1,329 @@
1
+ # keeper-js-ui
2
+
3
+ _React components built for accessibility, consistency, and speed._
4
+
5
+ ## Requirements
6
+
7
+ - Terminal of your choice.
8
+ - [git](https://github.com/git-guides/install-git) - Type `git -v` into your terminal to verify.
9
+ - [Node.js v18](https://nodejs.org/en/download/package-manager) - Type `node -v` into your terminal to verify.
10
+
11
+ ## Recommendations
12
+
13
+ - [nvm](https://github.com/nvm-sh/nvm) - Manage multiple versions of node/npm. Type `nvm -v` into your terminal to verify.
14
+ - [VS Code](https://code.visualstudio.com/) - Popular code editor with a healthy ecosystem of [extensions](https://marketplace.visualstudio.com/VSCode)/plugins.
15
+
16
+ ## Getting Started
17
+
18
+ Git clone or [download](https://github.com/Keeper-Security/keeper-js-ui/archive/refs/heads/main.zip) the repo.
19
+
20
+ Open a terminal from the root of the project and run:
21
+
22
+ ```sh
23
+ npm install
24
+ ```
25
+
26
+ Views are rendered via [Storybook](https://storybook.js.org/). To see components in action and start making edits, simply run:
27
+
28
+ ```sh
29
+ npm run storybook
30
+
31
+ # ctrl + c to stop the server.
32
+ ```
33
+
34
+ [Storybook](https://storybook.js.org/) will open in a new tab. If it's your first time, you'll be greeted with a walk-through of Storybook's UI.
35
+
36
+ ## Editing Code
37
+
38
+ The [src/](./src/) folder holds all component-related code.
39
+
40
+ - [src/index.css](./src/index.css) - Any CSS required by components or [Tailwind CSS](https://tailwindcss.com/).
41
+ - [src/index.ts](./src/index.ts) - Only the exports from this file are directly available to external projects/implementations.
42
+ - [src/components/](./src/components) - React components and any relevant tests and stories.
43
+ - [src/components/ui](./src/components/ui/) - Default location of shadcn/ui components.
44
+ - [src/lib/](./src/lib/) - Utility/helper functions.
45
+
46
+ With [Storybook](https://storybook.js.org/) running (`npm run storybook`), most edits are shown immediately upon saving without the need for browser refreshes or server restarts. Exceptions would be changes to infrastructure code like edits to Storybook itself.
47
+
48
+ ## Adding shadcn/ui Components
49
+
50
+ If you're adding a [shadcn/ui component](https://ui.shadcn.com/docs/components/), you can pick from a list by running:
51
+
52
+ ```sh
53
+ npm run components:add
54
+ ```
55
+
56
+ Alternatively, you can append the name of a specific component to the command. Adding the [shadcn/ui badge](https://ui.shadcn.com/docs/components/badge) would look like:
57
+
58
+ ```sh
59
+ npm run components:add badge
60
+ ```
61
+
62
+ Any dependency installs or project updates will be handled automatically. The new component file will be created in [src/components/ui/](./src/components/ui/).
63
+
64
+ > 💡 TIP: Create a specific story for your component to explore its features and demonstrate its different states. [What's a story?](https://storybook.js.org/docs/get-started/whats-a-story)
65
+
66
+ ### Diff'ing a shadcn/ui Component
67
+
68
+ You can see what's different between the local copies of shadcn/ui components and the [latest versions](https://ui.shadcn.com/docs/components/). Simply run:
69
+
70
+ ```sh
71
+ npm run components:diff
72
+ ```
73
+
74
+ This will list all components that have differences. Spoiler: **they will all have formatting differences**. Perhaps more helpful is to see the changes of a specific component:
75
+
76
+ ```sh
77
+ npm run components:diff button
78
+ ```
79
+
80
+ Probably not super helpful, but it's there.
81
+
82
+ ### Creating a Story
83
+
84
+ > 💡 TIP: Highly recommend visiting the [Storybook Tutorial](https://storybook.js.org/tutorials/) section for a better understanding of [Component-Driven Development (CDD)](https://www.componentdriven.org/) and the [Component Story Format (CSF)](https://storybook.js.org/docs/api/csf).
85
+
86
+ Stories are chunks of code that render a component and give you the opportunity to configure its props. The naming convention of a story is `<component>.stories.ts`.
87
+
88
+ > 🚨 NOTE: If your story is more complex and requires [jsx](https://react.dev/learn/writing-markup-with-jsx), you'll have to name it with a `.tsx` extension: `<component>.stories.tsx`
89
+
90
+ #### Minimal Story Example
91
+
92
+ ```ts
93
+ // @/components/my-component.stories.ts
94
+
95
+ import type { Meta, StoryObj } from '@storybook/react'
96
+ import { MyComponent } from '@/components/my-component'
97
+
98
+ type Story = StoryObj<typeof meta>
99
+
100
+ const meta = {
101
+ component: MyComponent,
102
+ } satisfies Meta<typeof Button>
103
+
104
+ export default meta
105
+
106
+ export const Default: Story = {}
107
+ ```
108
+
109
+ #### Typical Story Example
110
+
111
+ ```ts
112
+ // @/components/my-component.stories.ts
113
+
114
+ import type { Meta, StoryObj } from '@storybook/react'
115
+ import { MyComponent } from '@/components/my-component'
116
+
117
+ type Story = StoryObj<typeof meta>
118
+
119
+ // Default config.
120
+ const meta = {
121
+ component: MyComponent,
122
+ args: {
123
+ // Shared React props configured here.
124
+ variant: 'primary',
125
+ children: 'Component Examples',
126
+ disabled: false,
127
+ },
128
+ } satisfies Meta<typeof Button>
129
+
130
+ export default meta
131
+
132
+ // Render with default config.
133
+ export const Default: Story = {}
134
+
135
+ // Override default config as needed.
136
+ export const Secondary: Story = {
137
+ args: {
138
+ variant: 'Secondary',
139
+ children: 'I am Secondary',
140
+ },
141
+ }
142
+
143
+ // Compose and override.
144
+ export const DisabledSecondary: Story = {
145
+ args: {
146
+ ...Secondary.args,
147
+ disabled: true,
148
+ },
149
+ }
150
+ ```
151
+
152
+ ### Building Storybook Screens/Workflows
153
+
154
+ [Storybook](https://storybook.js.org/) is not limited to simply running and testing components in isolation. Complex scenarios can be created with the [render](https://storybook.js.org/docs/api/csf#custom-render-functions) and [play](https://storybook.js.org/docs/api/csf#play-function) functions.
155
+
156
+ ### Changing Themes in Storybook
157
+
158
+ [Storybook's Themes Addon](https://storybook.js.org/addons/@storybook/addon-themes) has been configured to pick between the `light` or `dark` theme based on your system settings. You can use the Theme button in the Storybook toolbar to switch themes manually should you need to test a component in another theme.
159
+
160
+ > 🗒️ NOTE: Some pages, such as `Docs`, will always render the `light` theme regardless of the selected theme. The component itself will render in the correct theme but the surrounding UI will not. Switch to an actual story to see the full page rendered as the chosen theme.
161
+
162
+ There are four themes to choose from:
163
+
164
+ - light
165
+ - dark
166
+ - high contrast light
167
+ - high contrast dark
168
+
169
+ > 🗒️ NOTE: The `high contrast *` themes will make best efforts to render components using an example of simplified [system colors](https://developer.mozilla.org/en-US/docs/Web/CSS/system-color). This is to be used only as a quick reference, not as a source of truth. For accurate representations of component rendering in these modes, refer to [triggering forced-colors/high contrast modes](#triggering-forced-colorshigh-contrast-modes).
170
+
171
+ #### Triggering Forced-Colors/High Contrast Modes
172
+
173
+ - Safari and Firefox do not have this feature in their dev tools, but they will respect system-wide settings.
174
+ - Chromium Browsers (Chrome, Edge)
175
+ 1. Open DevTools:
176
+ - Mac: `Option + Command + I`
177
+ - Win: `Ctrl + Shift + I`
178
+ - Mouse: `Right-click` > `Inspect`
179
+ 2. Open Run command:
180
+ - Mac: `Command + Shift + P`
181
+ - Win: `Ctrl + Shift + P`
182
+ - Mouse: `Top-right ellipsis button` > `Run command`
183
+ 3. Type `forced`.
184
+ 4. Select `Emulate CSS forced-colors: active`.
185
+ 5. To deactivate, repeat the `Run` command but select `Do not emulate CSS forced-colors`.
186
+
187
+ ## Committing Changes
188
+
189
+ If you're familiar with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), feel free to commit using `git commit`.
190
+
191
+ The minimum requirements for a valid commit message are:
192
+
193
+ - `<type>: <subject>`
194
+ - Totally length must be under **100 characters**. `<body>` and `<footer>` do not have this length limit.
195
+
196
+ These standards are enforced with the help of [commitlint](https://commitlint.js.org/).
197
+
198
+ ### Commitizen
199
+
200
+ If you're not comfortable with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) and you're ready to commit your **staged changes**, you can run:
201
+
202
+ ```sh
203
+ npm run commit
204
+ ```
205
+
206
+ [Commitizen](https://commitizen-tools.github.io/commitizen/) will prompt a series of questions and format your answers per the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/#specification):
207
+
208
+ ```
209
+ Select the type of change that you're committing:
210
+ feat: A new feature
211
+
212
+ What is the scope of this change (e.g. component or file name): (press enter to skip)
213
+ lang
214
+
215
+ Write a short, imperative tense description of the change (max 88 chars):
216
+ (45) add Australia, Canada, and UK English options
217
+
218
+ Provide a longer description of the change: (press enter to skip)
219
+ Satisfies requirements for upcoming expansion of additional English-speaking countries.
220
+
221
+ Are there any breaking changes?
222
+ Yes
223
+
224
+ Describe the breaking changes:
225
+ Two-digit country codes are now deprecated. You must use a full country code (en-us) for the API to work.
226
+
227
+ Does this change affect any open issues?
228
+ Yes
229
+
230
+ Add issue references (e.g. "fix #123", "re #123".):
231
+ re #JIRA-1234
232
+ ```
233
+
234
+ Your commit message will then be formatted to look something like:
235
+
236
+ ```
237
+ feat(lang): add Australia, Canada, and UK English options
238
+
239
+ Satisfies requirements for upcoming expansion of additional English-speaking countries.
240
+
241
+ BREAKING CHANGE: Two-digit country codes are now deprecated. You must use a full country code (en-us) for the API to work.
242
+
243
+ re #JIRA-1234
244
+ ```
245
+
246
+ Enforcing commit message standards helps with automating the [CHANGELOG](./CHANGELOG) updates, [package.json](./package.json) version bumps, and release [tags](https://github.com/Keeper-Security/keeper-js-ui/tags).
247
+
248
+ ## Under the Hood Technologies
249
+
250
+ Inspect the [package.json](./package.json) `dependencies` and `devDependencies` sections for the full list of technologies.
251
+
252
+ ### shadcn/ui
253
+
254
+ [shadcn/ui](https://ui.shadcn.com/) is not a React library in the typical sense. It configures [Tailwind CSS](https://tailwindcss.com/) to style headless (i.e. unstyled) components from other accessibility-focused libraries, such as [Radix UI](https://www.radix-ui.com/).
255
+
256
+ As it states in its [introduction](https://ui.shadcn.com/docs):
257
+
258
+ > This is NOT a component library. It's a collection of re-usable components that you can copy and paste into your apps.
259
+
260
+ This provides 100% customizable opportunities and 100% ownership. It also relies on these other projects to make life easier:
261
+
262
+ - [Radix UI](https://www.radix-ui.com/) - Unstyled and accessible React primitives for apps and design systems.
263
+ - [Class Variance Authority (CVA)](https://cva.style/) - Easily generate type-safe React props that utilize CSS classes.
264
+ - [clsx](https://github.com/lukeed/clsx) - Conditionally construct your `className` string.
265
+ - [tailwind-merge](https://github.com/dcastil/tailwind-merge) - Efficiently merge [Tailwind CSS](https://tailwindcss.com/) classes without conflicts.
266
+
267
+ ### Tailwind CSS
268
+
269
+ [Tailwind CSS](https://tailwindcss.com/) is a configurable, themeable CSS utility framework that uses scales and tokens while producing minimal CSS.
270
+
271
+ ### Storybook
272
+
273
+ [Storybook](https://storybook.js.org/) was chosen to be the engine for rendering these React components. It's a great tool for exploring components, assembling more complex workflows/screens, and providing a source of truth for links and documentation.
274
+
275
+ - Easily render components in isolation and demonstrate specific scenarios.
276
+ - Automatically create documentation by parsing your React component code.
277
+ - Explore component state combinations with interactive controls and without writing code.
278
+ - Run tests to avoid regression bugs and verify things like [a11y](https://developer.mozilla.org/en-US/docs/Web/Accessibility) or [i18n](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n).
279
+
280
+ ### Vite
281
+
282
+ [Vite](https://vitejs.dev/) is a super fast dev server and build optimizer with out-of-the-box support for most things. Plugins can be installed and configured. [vite-plugin-dts](https://github.com/qmhc/vite-plugin-dts#readme) abstracts all TypeScript type information into a single `.d.ts` file for builds.
283
+
284
+ ### Husky
285
+
286
+ [Husky](https://github.com/typicode/husky#readme) runs scripts during git hooks such as commit. This is used to automate tasks such as linting and formatting without the engineer having to run them separately. Husky is helping incorporate:
287
+
288
+ - [ESLint](https://eslint.org/) - Finds common JavaScript problems.
289
+ - [Prettier](https://prettier.io/) - Formats code and moves the war of tabs vs. spaces out of PR discussions.
290
+ - [Lint Staged](https://github.com/lint-staged/lint-staged#readme) - Runs linters and formatters against your staged files.
291
+
292
+ ### Configuration files
293
+
294
+ - [.husky/\*](./.husky/) => [Husky](https://typicode.github.io/husky/how-to.html)
295
+ - [.storybook/\*](./.storybook/) => [Storybook](https://storybook.js.org/docs/get-started/setup)
296
+ - [.commitlintrc.cjs](./.commitlintrc.cjs) => [commitlint](https://commitlint.js.org/reference/configuration.html)
297
+ - [.czrc](./.czrc) => [cz-cli/commitizen](https://github.com/commitizen/cz-cli#readme)
298
+ - [.eslintrc.cjs](./.eslintrc.cjs) => [ESLint](https://eslint.org/docs/latest/use/configure/)
299
+ - [.lintstagedrc.cjs](./.lintstagedrc.cjs) => [Lint Staged](https://github.com/lint-staged/lint-staged?tab=readme-ov-file#configuration)
300
+ - [.nvmrc](./.nvmrc) => [nvm](https://github.com/nvm-sh/nvm#readme)
301
+ - [.prettierrc](./.prettierrc) => [Prettier](https://prettier.io/docs/en/configuration.html)
302
+ - [components.json](./components.json) => [shadcn/ui](https://ui.shadcn.com/docs/components-json)
303
+ - [postcss.config.js](./postcss.config.js) => [PostCSS](https://github.com/postcss/postcss#readme)
304
+ - [tailwind.config.js](./tailwind.config.js) => [Tailwind CSS](https://tailwindcss.com/docs/configuration)
305
+ - [tsconfig{.app,.node}.json](./tsconfig.json) => [TypeScript](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
306
+ - [vite.config.ts](./vite.config.ts) => [Vite](https://vitejs.dev/config/)
307
+
308
+ ## Production Build
309
+
310
+ To create a production-ready build, you can run:
311
+
312
+ ```sh
313
+ npm run build
314
+ ```
315
+
316
+ This process inspects the [src/index.ts](./src/index.ts) file and pulls in only the necessary code. Packages listed under [package.json's](./package.json) `peerDependencies` will be ignored and left to the external implementations to install separately. This avoids redundancy and keeps file sizes down.
317
+
318
+ A [dist](./dist/) folder will be created at the root of the project. This folder is ignored by `git`, so you shouldn't show any changes that need to be committied. The four files created are:
319
+
320
+ - style.css - All of the necessary CSS including [Tailwind CSS](https://tailwindcss.com/) utility classes.
321
+ - index.es.d.ts - All of the necessary type information.
322
+ - index.es.js - All of the exported components in ES Modules format.
323
+ - index.umd.js - All of the exported components in the Universal Module Definition (fallback) format.
324
+
325
+ > 💡 TIP: Learn more about [npm link](https://docs.npmjs.com/cli/v9/commands/npm-link) if you want to bypass the npm registry and install this library to another project locally.
326
+
327
+ ## Publishing (NPM Package)
328
+
329
+ ...Coming soon
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.js",
8
+ "css": "src/index.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils"
16
+ }
17
+ }
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@keeper-security/keeper-js-ui",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "storybook": "storybook dev -p 6006",
7
+ "build": "tsc -b && vite build",
8
+ "start": "npm run storybook",
9
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
+ "format": "prettier . --write --ignore-unknown",
11
+ "prepare": "husky",
12
+ "commit": "cz",
13
+ "components:add": "npx shadcn-ui@latest add",
14
+ "components:diff": "npx shadcn-ui@latest diff"
15
+ },
16
+ "dependencies": {
17
+ "@radix-ui/react-direction": "^1.1.0",
18
+ "@radix-ui/react-slot": "^1.1.0",
19
+ "class-variance-authority": "^0.7.0",
20
+ "clsx": "^2.1.1",
21
+ "lucide-react": "^0.418.0",
22
+ "react": "^18.3.1",
23
+ "tailwind-merge": "^2.4.0",
24
+ "tailwindcss-animate": "^1.0.7"
25
+ },
26
+ "devDependencies": {
27
+ "@chromatic-com/storybook": "^1.6.1",
28
+ "@commitlint/cli": "^19.3.0",
29
+ "@commitlint/config-conventional": "^19.2.2",
30
+ "@storybook/addon-a11y": "^8.2.6",
31
+ "@storybook/addon-essentials": "^8.2.6",
32
+ "@storybook/addon-interactions": "^8.2.6",
33
+ "@storybook/addon-links": "^8.2.6",
34
+ "@storybook/addon-onboarding": "^8.2.6",
35
+ "@storybook/addon-themes": "^8.2.6",
36
+ "@storybook/blocks": "^8.2.6",
37
+ "@storybook/react": "^8.2.6",
38
+ "@storybook/react-vite": "^8.2.6",
39
+ "@storybook/test": "^8.2.6",
40
+ "@types/node": "^22.0.2",
41
+ "@types/react": "^18.3.3",
42
+ "@typescript-eslint/eslint-plugin": "^7.15.0",
43
+ "@typescript-eslint/parser": "^7.15.0",
44
+ "@vitejs/plugin-react-swc": "^3.5.0",
45
+ "autoprefixer": "^10.4.19",
46
+ "commitizen": "^4.3.0",
47
+ "cz-conventional-changelog": "^3.3.0",
48
+ "eslint": "^8.57.0",
49
+ "eslint-plugin-react-hooks": "^4.6.2",
50
+ "eslint-plugin-react-refresh": "^0.4.7",
51
+ "eslint-plugin-storybook": "^0.8.0",
52
+ "husky": "^9.1.4",
53
+ "lint-staged": "^15.2.7",
54
+ "postcss": "^8.4.40",
55
+ "prettier": "3.3.3",
56
+ "storybook": "^8.2.6",
57
+ "storybook-addon-rtl": "^1.0.1",
58
+ "tailwindcss": "^3.4.7",
59
+ "typescript": "^5.2.2",
60
+ "vite": "^5.3.4",
61
+ "vite-plugin-dts": "^4.0.0-beta.2"
62
+ },
63
+ "peerDependencies": {
64
+ "react": "^18.3.1"
65
+ },
66
+ "description": "_React components built for accessibility, consistency, and speed._",
67
+ "main": "postcss.config.js",
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "git+https://github.com/Keeper-Security/keeper-js-ui.git"
71
+ },
72
+ "author": "",
73
+ "license": "ISC",
74
+ "bugs": {
75
+ "url": "https://github.com/Keeper-Security/keeper-js-ui/issues"
76
+ },
77
+ "homepage": "https://github.com/Keeper-Security/keeper-js-ui#readme"
78
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { fn } from '@storybook/test'
3
+ import { Mail } from 'lucide-react'
4
+ import { Button } from '@/components/ui/button'
5
+
6
+ type Story = StoryObj<typeof meta>
7
+
8
+ const meta = {
9
+ component: Button,
10
+ args: {
11
+ children: 'Button',
12
+ disabled: false,
13
+ onClick: fn(),
14
+ },
15
+ } satisfies Meta<typeof Button>
16
+
17
+ export default meta
18
+
19
+ export const Default: Story = {}
20
+
21
+ export const WithIcon: Story = {
22
+ args: {
23
+ children: [<Mail className="me-2 size-4" />, 'Button with Icon'],
24
+ },
25
+ }
@@ -0,0 +1,56 @@
1
+ import * as React from 'react'
2
+ import { Slot } from '@radix-ui/react-slot'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const buttonVariants = cva(
8
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13
+ destructive:
14
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
15
+ outline:
16
+ 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
17
+ secondary:
18
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
19
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
20
+ link: 'text-primary underline-offset-4 hover:underline',
21
+ },
22
+ size: {
23
+ default: 'h-10 px-4 py-2',
24
+ sm: 'h-9 rounded-md px-3',
25
+ lg: 'h-11 rounded-md px-8',
26
+ icon: 'h-10 w-10',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'default',
31
+ size: 'default',
32
+ },
33
+ },
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : 'button'
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ },
53
+ )
54
+ Button.displayName = 'Button'
55
+
56
+ export { Button, buttonVariants }
package/src/index.css ADDED
@@ -0,0 +1,81 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 0 0% 3.9%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 0 0% 3.9%;
11
+ --popover: 0 0% 100%;
12
+ --popover-foreground: 0 0% 3.9%;
13
+ --primary: 0 0% 9%;
14
+ --primary-foreground: 0 0% 98%;
15
+ --secondary: 0 0% 96.1%;
16
+ --secondary-foreground: 0 0% 9%;
17
+ --muted: 0 0% 96.1%;
18
+ --muted-foreground: 0 0% 45.1%;
19
+ --accent: 0 0% 96.1%;
20
+ --accent-foreground: 0 0% 9%;
21
+ --destructive: 0 84.2% 60.2%;
22
+ --destructive-foreground: 0 0% 98%;
23
+ --border: 0 0% 89.8%;
24
+ --input: 0 0% 89.8%;
25
+ --ring: 0 0% 3.9%;
26
+ --radius: 0.5rem;
27
+ --chart-1: 12 76% 61%;
28
+ --chart-2: 173 58% 39%;
29
+ --chart-3: 197 37% 24%;
30
+ --chart-4: 43 74% 66%;
31
+ --chart-5: 27 87% 67%;
32
+ }
33
+
34
+ .dark {
35
+ --background: 0 0% 3.9%;
36
+ --foreground: 0 0% 98%;
37
+ --card: 0 0% 3.9%;
38
+ --card-foreground: 0 0% 98%;
39
+ --popover: 0 0% 3.9%;
40
+ --popover-foreground: 0 0% 98%;
41
+ --primary: 0 0% 98%;
42
+ --primary-foreground: 0 0% 9%;
43
+ --secondary: 0 0% 14.9%;
44
+ --secondary-foreground: 0 0% 98%;
45
+ --muted: 0 0% 14.9%;
46
+ --muted-foreground: 0 0% 63.9%;
47
+ --accent: 0 0% 14.9%;
48
+ --accent-foreground: 0 0% 98%;
49
+ --destructive: 0 62.8% 30.6%;
50
+ --destructive-foreground: 0 0% 98%;
51
+ --border: 0 0% 14.9%;
52
+ --input: 0 0% 14.9%;
53
+ --ring: 0 0% 83.1%;
54
+ --chart-1: 220 70% 50%;
55
+ --chart-2: 160 60% 45%;
56
+ --chart-3: 30 80% 55%;
57
+ --chart-4: 280 65% 60%;
58
+ --chart-5: 340 75% 55%;
59
+ }
60
+
61
+ .contrast-dark {
62
+ /* TODO: Finish list of high-contrast values */
63
+ --background: 0 0% 0%;
64
+ --foreground: 0 0% 98%;
65
+ }
66
+
67
+ .contrast-light {
68
+ /* TODO: Finish list of high-contrast values */
69
+ --background: 0 0% 100%;
70
+ --foreground: 0 0% 3.9%;
71
+ }
72
+ }
73
+
74
+ @layer base {
75
+ * {
76
+ @apply border-border;
77
+ }
78
+ body {
79
+ @apply bg-background text-foreground;
80
+ }
81
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import '@/index.css'
2
+
3
+ export { Button } from '@/components/ui/button'
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,111 @@
1
+ const plugin = require('tailwindcss/plugin')
2
+
3
+ const isProductionBuild = process.env.NODE_ENV === 'production'
4
+
5
+ // Configure which files Tailwind CSS (TW) scans to produce CSS classes.
6
+ const content = [
7
+ // We want the ability to use TW anywhere in the project (like Storybook)...
8
+ './src/**/*.{ts,tsx}',
9
+ // But we only want to export the classes used by library components.
10
+ ...(isProductionBuild ? ['!./src/**/*.{spec,stories,test}.{ts,tsx}'] : []),
11
+ ]
12
+
13
+ const safelist = [
14
+ 'dark',
15
+ /**
16
+ * We only want these modifiers available to Storybook.
17
+ * To use them in Storybook, prepend them to any Tailwind CSS utility class.
18
+ * Example: add a white border while simulating a high contrast dark theme:
19
+ * `className="contrast-dark:border contrast-dark:border-white"`
20
+ */
21
+ ...(isProductionBuild ? [] : ['contrast-dark', 'contrast-light']),
22
+ ]
23
+
24
+ /** @type {import('tailwindcss').Config} */
25
+ module.exports = {
26
+ darkMode: ['class'],
27
+ content,
28
+ prefix: '',
29
+ safelist,
30
+ theme: {
31
+ container: {
32
+ center: true,
33
+ padding: '2rem',
34
+ screens: {
35
+ '2xl': '1400px',
36
+ },
37
+ },
38
+ extend: {
39
+ colors: {
40
+ border: 'hsl(var(--border))',
41
+ input: 'hsl(var(--input))',
42
+ ring: 'hsl(var(--ring))',
43
+ background: 'hsl(var(--background))',
44
+ foreground: 'hsl(var(--foreground))',
45
+ primary: {
46
+ DEFAULT: 'hsl(var(--primary))',
47
+ foreground: 'hsl(var(--primary-foreground))',
48
+ },
49
+ secondary: {
50
+ DEFAULT: 'hsl(var(--secondary))',
51
+ foreground: 'hsl(var(--secondary-foreground))',
52
+ },
53
+ destructive: {
54
+ DEFAULT: 'hsl(var(--destructive))',
55
+ foreground: 'hsl(var(--destructive-foreground))',
56
+ },
57
+ muted: {
58
+ DEFAULT: 'hsl(var(--muted))',
59
+ foreground: 'hsl(var(--muted-foreground))',
60
+ },
61
+ accent: {
62
+ DEFAULT: 'hsl(var(--accent))',
63
+ foreground: 'hsl(var(--accent-foreground))',
64
+ },
65
+ popover: {
66
+ DEFAULT: 'hsl(var(--popover))',
67
+ foreground: 'hsl(var(--popover-foreground))',
68
+ },
69
+ card: {
70
+ DEFAULT: 'hsl(var(--card))',
71
+ foreground: 'hsl(var(--card-foreground))',
72
+ },
73
+ },
74
+ borderRadius: {
75
+ lg: 'var(--radius)',
76
+ md: 'calc(var(--radius) - 2px)',
77
+ sm: 'calc(var(--radius) - 4px)',
78
+ },
79
+ keyframes: {
80
+ 'accordion-down': {
81
+ from: { height: '0' },
82
+ to: { height: 'var(--radix-accordion-content-height)' },
83
+ },
84
+ 'accordion-up': {
85
+ from: { height: 'var(--radix-accordion-content-height)' },
86
+ to: { height: '0' },
87
+ },
88
+ },
89
+ animation: {
90
+ 'accordion-down': 'accordion-down 0.2s ease-out',
91
+ 'accordion-up': 'accordion-up 0.2s ease-out',
92
+ },
93
+ },
94
+ },
95
+ plugins: [
96
+ require('tailwindcss-animate'),
97
+ ...(isProductionBuild
98
+ ? []
99
+ : [
100
+ plugin(({ addVariant }) => {
101
+ // Simulate high contrast dark themes.
102
+ addVariant('contrast-dark', ['.contrast-dark &', '&.contrast-dark'])
103
+ // Simulate high contrast light themes.
104
+ addVariant('contrast-light', [
105
+ '.contrast-light &',
106
+ '&.contrast-light',
107
+ ])
108
+ }),
109
+ ]),
110
+ ],
111
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
+ "target": "ES2020",
6
+ "useDefineForClassFields": true,
7
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
8
+ "module": "ESNext",
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "moduleDetection": "force",
17
+ "noEmit": true,
18
+ "jsx": "react-jsx",
19
+
20
+ /* Linting */
21
+ "strict": true,
22
+ "noUnusedLocals": true,
23
+ "noUnusedParameters": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+
26
+ /* UI components config */
27
+ "baseUrl": ".",
28
+ "paths": {
29
+ "@/*": ["./src/*"]
30
+ }
31
+ },
32
+ "include": ["src"]
33
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.app.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.node.json"
9
+ }
10
+ ],
11
+ "compilerOptions": {
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@/*": ["./src/*"]
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5
+ "skipLibCheck": true,
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "noEmit": true
11
+ },
12
+ "include": ["vite.config.ts"]
13
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react-swc'
3
+ import { resolve } from 'path'
4
+ import dts from 'vite-plugin-dts'
5
+ import { peerDependencies } from './package.json'
6
+
7
+ // https://vitejs.dev/config/
8
+ export default defineConfig({
9
+ plugins: [
10
+ react(),
11
+ dts({
12
+ tsconfigPath: resolve(__dirname, 'tsconfig.app.json'),
13
+ rollupTypes: true,
14
+ include: ['src/**/*'],
15
+ }),
16
+ ],
17
+ resolve: {
18
+ alias: {
19
+ '@': resolve(__dirname, './src'),
20
+ },
21
+ },
22
+ build: {
23
+ lib: {
24
+ entry: resolve(__dirname, 'src/index.ts'),
25
+ name: 'keeper-js-ui',
26
+ fileName: (format) => `index.${format}.js`,
27
+ },
28
+ rollupOptions: {
29
+ external: [
30
+ ...Object.keys(peerDependencies),
31
+ 'react/jsx-runtime',
32
+ 'react-dom/client',
33
+ ],
34
+ output: {
35
+ exports: 'named',
36
+ globals: {
37
+ react: 'React',
38
+ 'react-dom': 'ReactDOM',
39
+ 'react/jsx-runtime': 'react/jsx-runtime',
40
+ },
41
+ },
42
+ },
43
+ },
44
+ })