mirror of https://github.com/boxyhq/jackson.git
Bootstrap ui sdk development with Login component (#735)
* Push sdk folder * Ignore `sdk` from root tsconfig * Add postcss config * Tweaks to the vite setup * Cleanup and add `Login` component * Test * Undo test commit * Turn off next linter rule for sdk * Tweaks to component doc * Add dom libs to tsconfig * html id generation * Component WIP * Update tsconfig and lock file * Refactor * Tweak props and handle error display * Simplify `useId` hook * Update JSDOC * Remove ErrorDisplayComponent prop * Minor refactor * Refactor id generation, add jsdoc * Support styling via props and add default styles * Sync lock files * More default styles for input * Sync lock file * Tweak box-shadow * Minor tweak comment * WIP * Update package name and lock file * Update vite config fileName * Exclude type emit for internal components * Tweak package.json * Rename `react-ui` * Add repo and bugs fields * Fix import name and jsdoc * Support `unstyled` prop * Tweak README and docs * Minor tweaks * Tweak README and update favicon * Add workflow for npm publish * Update build script and use node 16 * Add trigger on push * Add dev dependencies to fix failing build * Add missing dev dependency * set access to public * Move react & others to dev dependencies * Fix main,module,exports in package.json * Include types in package.json * Fix types path * Fix return type for `forwardTenant` * Include react as a peer dependency * Add react plugin to vite * Handle `undefined` * Inject defaultStyles into build * Update tsconfig * Update homepage * Update type to void for no return * Update usage * [skip ci] update lock file * [skip ci] Merge branch 'main' into 702-react-sdk-login * WIP tweak component * Handle submission state, align focus styles * Sync lock file * Swap ::set-output with `$GITHUB_OUTPUT` * Use ci job output for getting npm version and publish tag * trigger sdk jobs on `workflow_call` * Use sdk workflow * Fix workflow path * Replace version in sdk package.json * Move condition to sdk workflow * Update vite to v4 and cleanup dependencies * Publish a beta version for testing * Revert removal of autoprefixer * Update node version and inherit secrets * Tweaks * Update SDK README * Revert changes related to testing * Align border-radius with boxyhq ui * Temporarily allow publish * Style alignment with boxyhq ui * - Update default styling to that of a b/w look - Support style attr for container - Tweak types - Support CSS vars on container element to style input and button outlines plus button hover * Accept any color format * Tweak README and demos * Support spreading props for input/button * Tweak style injection so that userland classes load later and override the default styles * Try non injection of css * Add style.css to exports and files * Formatting change * Add styling section * Bump up version * - Use defaultClasses only if custom classes are not set - Remove `unstyled` prop * Update demos * Add style for disabled state * Tweak README * Bring back css injection * Remove style from files and exports fields * Scope css properties to appropriate inner elements * Remove css properties * Bump version * Support HTMLAttributes for container and label also * Prep for release * Export types from entry point file * Tweak for local usage via npm linking * Replace package.json fields before publish * Tweak demos * updated dependabot for new package.json * fixed yaml Co-authored-by: Deepak Prabhakara <deepak@boxyhq.com>
This commit is contained in:
parent
f5582374d4
commit
a5ac299bbb
|
@ -23,5 +23,11 @@ module.exports = {
|
|||
'@typescript-eslint/no-var-requires': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['sdk/**/*'],
|
||||
rules: {
|
||||
'@next/next/no-img-element': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -13,3 +13,7 @@ updates:
|
|||
directory: '/npm'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
- package-ecosystem: 'react-sdk'
|
||||
directory: '/sdk/ui/react'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
|
|
|
@ -23,6 +23,9 @@ on:
|
|||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
NPM_VERSION: ${{ steps.version.outputs.NPM_VERSION }}
|
||||
PUBLISH_TAG: ${{ steps.version.outputs.PUBLISH_TAG }}
|
||||
env:
|
||||
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
||||
NEXTAUTH_URL: http://localhost:5225
|
||||
|
@ -118,7 +121,8 @@ jobs:
|
|||
run: npx playwright install chromium
|
||||
- name: e2e tests
|
||||
run: npx ts-node --log-error e2e/seedAuthDb.ts && npx playwright test
|
||||
- run: |
|
||||
- id: version
|
||||
run: |
|
||||
npm install --legacy-peer-deps
|
||||
npm run build
|
||||
npm install -g json
|
||||
|
@ -135,47 +139,25 @@ jobs:
|
|||
JACKSON_VERSION="${JACKSON_VERSION}-beta.${GITHUB_RUN_NUMBER}"
|
||||
fi
|
||||
|
||||
echo ${JACKSON_VERSION} > npmversion.txt
|
||||
echo ${publishTag} > publishTag.txt
|
||||
echo "NPM_VERSION=${JACKSON_VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "PUBLISH_TAG=${publishTag}" >> $GITHUB_OUTPUT
|
||||
|
||||
echo $(cat npmversion.txt)
|
||||
echo $(cat publishTag.txt)
|
||||
working-directory: ./npm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- name: Upload saml-jackson npm version
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: npmversion
|
||||
path: ./npm/npmversion.txt
|
||||
- name: Upload saml-jackson publish tag
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: publishTag
|
||||
path: ./npm/publishTag.txt
|
||||
|
||||
build:
|
||||
needs: ci
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check Out Repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download saml-jackson npm version
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: npmversion
|
||||
|
||||
- name: Get saml-jackson npm version
|
||||
id: npmversion
|
||||
run: echo "::set-output name=npmversion::$(cat npmversion.txt)"
|
||||
|
||||
- run: echo ${{ steps.npmversion.outputs.npmversion }}
|
||||
- run: echo ${{ needs.ci.outputs.NPM_VERSION }}
|
||||
|
||||
- name: Get short SHA
|
||||
id: slug
|
||||
run: echo "::set-output name=sha7::$(echo ${GITHUB_SHA} | cut -c1-7)"
|
||||
run: echo "{sha7}={$(echo ${GITHUB_SHA} | cut -c1-7)}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
|
@ -200,7 +182,7 @@ jobs:
|
|||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ github.repository }}:latest,${{ github.repository }}:${{ steps.slug.outputs.sha7 }},${{ github.repository }}:${{ steps.npmversion.outputs.npmversion }}
|
||||
tags: ${{ github.repository }}:latest,${{ github.repository }}:${{ steps.slug.outputs.sha7 }},${{ github.repository }}:${{ needs.ci.outputs.NPM_VERSION }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
|
@ -318,13 +300,6 @@ jobs:
|
|||
rm results.sarif || true
|
||||
rm ./_docker/sbom.json || true
|
||||
mv ./_docker/sbom.xml ./_docker/sbom.cyclonedx || true
|
||||
- name: Download saml-jackson npm version
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: npmversion
|
||||
- name: Get saml-jackson npm version
|
||||
id: _npmversion
|
||||
run: echo "::set-output name=npmversion::$(cat npmversion.txt)"
|
||||
- name: ORAS Setup & Push SBOM reports to GitHub Container Registry
|
||||
if: github.ref == 'refs/heads/release'
|
||||
run: |
|
||||
|
@ -333,24 +308,24 @@ jobs:
|
|||
curl -LO "https://cdn.bundle.bar/clients/oras/${ORAS_VERSION}/${ORAS_FILENAME}"
|
||||
mkdir oras_install
|
||||
tar -xvf "${ORAS_FILENAME}" -C oras_install
|
||||
./oras_install/oras push ghcr.io/${{github.repository_owner}}/jackson/sbom:service-${{ steps._npmversion.outputs.npmversion }} ./sbom.*
|
||||
./oras_install/oras push ghcr.io/${{github.repository_owner}}/jackson/sbom:service-${{ needs.ci.outputs.NPM_VERSION }} ./sbom.*
|
||||
cd _docker
|
||||
../oras_install/oras push ghcr.io/${{github.repository_owner}}/jackson/sbom:docker-${{ steps._npmversion.outputs.npmversion }} ./sbom.*
|
||||
../oras_install/oras push ghcr.io/${{github.repository_owner}}/jackson/sbom:docker-${{ needs.ci.outputs.NPM_VERSION }} ./sbom.*
|
||||
cd ..
|
||||
cd npm
|
||||
../oras_install/oras push ghcr.io/${{github.repository_owner}}/jackson/sbom:npm-${{ steps._npmversion.outputs.npmversion }} ./sbom.*
|
||||
../oras_install/oras push ghcr.io/${{github.repository_owner}}/jackson/sbom:npm-${{ needs.ci.outputs.NPM_VERSION }} ./sbom.*
|
||||
cd ..
|
||||
|
||||
- name: Sign the sbom images
|
||||
if: github.ref == 'refs/heads/release'
|
||||
run: |
|
||||
cosign sign --key /tmp/cosign.key ghcr.io/${{github.repository_owner}}/jackson/sbom:service-${{ steps._npmversion.outputs.npmversion }} || true
|
||||
cosign sign --key /tmp/cosign.key ghcr.io/${{github.repository_owner}}/jackson/sbom:docker-${{ steps._npmversion.outputs.npmversion }} || true
|
||||
cosign sign --key /tmp/cosign.key ghcr.io/${{github.repository_owner}}/jackson/sbom:npm-${{ steps._npmversion.outputs.npmversion }} || true
|
||||
cosign sign --key /tmp/cosign.key ghcr.io/${{github.repository_owner}}/jackson/sbom:service-${{ needs.ci.outputs.NPM_VERSION }} || true
|
||||
cosign sign --key /tmp/cosign.key ghcr.io/${{github.repository_owner}}/jackson/sbom:docker-${{ needs.ci.outputs.NPM_VERSION }} || true
|
||||
cosign sign --key /tmp/cosign.key ghcr.io/${{github.repository_owner}}/jackson/sbom:npm-${{ needs.ci.outputs.NPM_VERSION }} || true
|
||||
env:
|
||||
COSIGN_PASSWORD: ${{secrets.COSIGN_PASSWORD}}
|
||||
publish:
|
||||
needs: build
|
||||
needs: [ci, build]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
|
@ -360,23 +335,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download saml-jackson npm version
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: npmversion
|
||||
- name: Get saml-jackson npm version
|
||||
id: npmversion
|
||||
run: echo "::set-output name=npmversion::$(cat npmversion.txt)"
|
||||
- run: echo ${{ steps.npmversion.outputs.npmversion }}
|
||||
|
||||
- name: Download saml-jackson publish tag
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: publishTag
|
||||
- name: Get saml-jackson npm version
|
||||
id: publishTag
|
||||
run: echo "::set-output name=publishTag::$(cat publishTag.txt)"
|
||||
- run: echo ${{ steps.publishTag.outputs.publishTag }}
|
||||
- run: echo ${{ needs.ci.outputs.NPM_VERSION }}
|
||||
- run: echo ${{ needs.ci.outputs.PUBLISH_TAG }}
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
|
@ -393,12 +353,21 @@ jobs:
|
|||
if: github.ref == 'refs/heads/release' || contains(github.ref, 'refs/tags/beta-v')
|
||||
run: |
|
||||
npm install -g json
|
||||
JACKSON_VERSION=${{ steps.npmversion.outputs.npmversion }}
|
||||
JACKSON_VERSION=${{ needs.ci.outputs.NPM_VERSION }}
|
||||
json -I -f package.json -e "this.main=\"dist/index.js\""
|
||||
json -I -f package.json -e "this.types=\"dist/index.d.ts\""
|
||||
json -I -f package.json -e "this.version=\"${JACKSON_VERSION}\""
|
||||
|
||||
npm publish --tag ${{ steps.publishTag.outputs.publishTag }} --access public
|
||||
npm publish --tag ${{ needs.ci.outputs.PUBLISH_TAG }} --access public
|
||||
working-directory: ./npm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish_sdk:
|
||||
needs: [ci, build]
|
||||
name: Publish SDK for React
|
||||
uses: ./.github/workflows/sdk.yml
|
||||
with:
|
||||
npmversion: ${{ needs.ci.outputs.NPM_VERSION }}
|
||||
publishtag: ${{ needs.ci.outputs.PUBLISH_TAG }}
|
||||
secrets: inherit
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
name: Publish React component SDK to npmjs
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
npmversion:
|
||||
required: true
|
||||
type: string
|
||||
publishtag:
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./sdk/ui/react
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- name: Publish NPM
|
||||
if: github.ref == 'refs/heads/release' || contains(github.ref, 'refs/tags/beta-v')
|
||||
run: |
|
||||
npm install -g json
|
||||
PKG_VERSION=${{ inputs.npmversion }}
|
||||
json -I -f package.json -e "this.main=\"./dist/@boxyhq/react-ui.umd.cjs\""
|
||||
json -I -f package.json -e "this.module=\"./dist/@boxyhq/react-ui.js\""
|
||||
json -I -f package.json -e "this.types=\"./dist/dist/types/index.d.ts\""
|
||||
json -I -f package.json -e "this.exports['.']['import']=\"./dist/@boxyhq/react-ui.js\""
|
||||
json -I -f package.json -e "this.exports['.']['require']=\"./dist/@boxyhq/react-ui.umd.cjs\""
|
||||
json -I -f package.json -e "this.version=\"${PKG_VERSION}\""
|
||||
|
||||
npm publish --tag ${{ inputs.publishtag }} --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@ -95,4 +95,4 @@
|
|||
"engines": {
|
||||
"node": ">=14.18.1 <=18.x"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
docs-dist
|
||||
*.local
|
|
@ -0,0 +1,79 @@
|
|||
# @boxyhq/react-ui
|
||||
|
||||
UI components from [BoxyHQ](https://boxyhq.com/) for plug-and-play enterprise features.
|
||||
|
||||
## Installation
|
||||
|
||||
`npm install @boxyhq/react-ui --save`
|
||||
|
||||
## Usage
|
||||
|
||||
### SSO Login Component
|
||||
|
||||
There are mainly 2 ways of using the SSO Login Component as outlined below:
|
||||
|
||||
#### Preset value for `ssoIdentifier`
|
||||
|
||||
If a value is passed for `ssoIdentifier`, it would render a button that on click calls the passed-in handler (onSubmit) with the `ssoIdentifier` value. The handler can then initiate a redirect to the SSO service forwarding the value for ssoIdentifier.
|
||||
|
||||
```tsx
|
||||
import { Login as SSOLogin } from '@boxyhq/react-ui';
|
||||
|
||||
const onSSOSubmit = async (ssoIdentifier: string) => {
|
||||
// Below calls signIn from next-auth. Replace this with whatever auth lib that you are using.
|
||||
await signIn('boxyhq-saml', undefined, { client_id: ssoIdentifier });
|
||||
};
|
||||
|
||||
<SSOLogin
|
||||
buttonText={'Login with SSO'}
|
||||
ssoIdentifier={`tenant=${tenant}&product=${product}`}
|
||||
onSubmit={onSSOSubmit}
|
||||
classNames={{
|
||||
container: 'mt-2',
|
||||
button: 'btn-primary btn-block btn rounded-md active:-scale-95',
|
||||
}}
|
||||
/>;
|
||||
```
|
||||
|
||||
#### Accept input from the user for `ssoIdentifier`
|
||||
|
||||
If a value is not passed for `ssoIdentifier`, it would render an input field for the user to enter the `ssoIdentifier` value. And then on submit, the value gets passed to the handler. The handler can then initiate a redirect to the SSO service forwarding the value for ssoIdentifier.
|
||||
|
||||
```tsx
|
||||
import { Login as SSOLogin } from '@boxyhq/react-ui';
|
||||
|
||||
const onSSOSubmit = async (ssoIdentifier: string) => {
|
||||
// Below calls signIn from next-auth. Replace this with whatever auth lib that you are using.
|
||||
await signIn('boxyhq-saml', undefined, { client_id: ssoIdentifier });
|
||||
};
|
||||
|
||||
<SSOLogin
|
||||
buttonText={'Login with SSO'}
|
||||
onSubmit={onSSOSubmit}
|
||||
classNames={{
|
||||
container: 'mt-2',
|
||||
label: 'text-gray-400',
|
||||
button: 'btn-primary btn-block btn rounded-md active:-scale-95',
|
||||
input: 'input-bordered input mb-5 mt-2 w-full rounded-md',
|
||||
}}
|
||||
/>;
|
||||
```
|
||||
|
||||
#### Styling
|
||||
|
||||
If the classNames prop is passed in, we can override the default styling for each inner element. In case an inner element is omitted from the classNames prop, default styles will be set for the element. For example, In the below snippet, all the inner elements are styled by passing in the classNames for each inner one.
|
||||
|
||||
```tsx
|
||||
<SSOLogin
|
||||
buttonText={'Login with SSO'}
|
||||
onSubmit={onSSOSubmit}
|
||||
classNames={{
|
||||
container: 'mt-2',
|
||||
label: 'text-gray-400',
|
||||
button: 'btn-primary btn-block btn rounded-md active:-scale-95',
|
||||
input: 'input-bordered input mb-5 mt-2 w-full rounded-md',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Styling via style attribute is also supported for each inner element.
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@boxyhq/react-ui</title>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/@pages-infra/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: BoxyHQ React SDK
|
||||
---
|
||||
|
||||
import README from '../../README.md';
|
||||
|
||||
<README />
|
|
@ -0,0 +1,7 @@
|
|||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const Component404 = () => {
|
||||
return <Navigate to='/' />;
|
||||
};
|
||||
|
||||
export default Component404;
|
|
@ -0,0 +1,45 @@
|
|||
import { createTheme, defaultSideNavs } from 'vite-pages-theme-doc';
|
||||
|
||||
import Component404 from './404';
|
||||
import logo from './logo.png';
|
||||
|
||||
export default createTheme({
|
||||
logo: (
|
||||
<div style={{ fontSize: '20px', display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<img src={logo} alt='BoxyHQ logo' width='40' height='40' />
|
||||
BoxyHQ React SDK
|
||||
</div>
|
||||
),
|
||||
topNavs: [
|
||||
{
|
||||
label: 'SDK',
|
||||
path: '/',
|
||||
activeIfMatch: {
|
||||
// match all first-level paths
|
||||
path: '/:foo',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Components',
|
||||
path: '/components/demos/Login',
|
||||
activeIfMatch: '/components',
|
||||
},
|
||||
],
|
||||
sideNavs: (ctx) => {
|
||||
return defaultSideNavs(ctx, {
|
||||
groupConfig: {
|
||||
components: {
|
||||
demos: {
|
||||
label: 'Demos (dev only)',
|
||||
order: -1,
|
||||
},
|
||||
sso: {
|
||||
label: 'SSO',
|
||||
order: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
Component404,
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,66 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import * as path from 'path';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import pages, { DefaultPageStrategy } from 'vite-plugin-react-pages';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
pages({
|
||||
pagesDir: path.join(__dirname, 'pages'),
|
||||
pageStrategy: new DefaultPageStrategy({
|
||||
extraFindPages: async (pagesDir, helpers) => {
|
||||
const srcPath = path.join(__dirname, '../src');
|
||||
if (String(process.env.SHOW_ALL_COMPONENT_DEMOS) === 'true') {
|
||||
// show all component demos during dev
|
||||
// put them in page `/components/demos/${componentName}`
|
||||
helpers.watchFiles(
|
||||
srcPath,
|
||||
'*/demos/**/*.{[tj]sx,md?(x)}',
|
||||
async function fileHandler(file, api) {
|
||||
const { relative, path: absolute } = file;
|
||||
const match = relative.match(/(.*)\/demos\/(.*)\.([tj]sx|mdx?)$/);
|
||||
if (!match) throw new Error('unexpected file: ' + absolute);
|
||||
const [_, componentName, demoName] = match;
|
||||
const pageId = `/components/demos/${componentName}`;
|
||||
// register page data
|
||||
api.addPageData({
|
||||
pageId,
|
||||
key: demoName,
|
||||
// register demo runtime data path
|
||||
// the ?demo query will wrap the module with useful demoInfo
|
||||
// that will be consumed by theme-doc
|
||||
dataPath: `${absolute}?demo`,
|
||||
// register demo static data
|
||||
staticData: await helpers.extractStaticData(file),
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// find all component README
|
||||
helpers.watchFiles(srcPath, '*/README.md?(x)', async function fileHandler(file, api) {
|
||||
const { relative, path: absolute } = file;
|
||||
const match = relative.match(/(.*)\/README\.mdx?$/);
|
||||
if (!match) throw new Error('unexpected file: ' + absolute);
|
||||
const [_, componentName] = match;
|
||||
const pageId = `/components/${componentName}`;
|
||||
// register page data
|
||||
api.addPageData({
|
||||
pageId,
|
||||
// register demo runtime data path
|
||||
dataPath: absolute,
|
||||
// register demo static data
|
||||
staticData: await helpers.extractStaticData(file),
|
||||
});
|
||||
});
|
||||
},
|
||||
}),
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@boxyhq/react-ui': path.join(__dirname, '../src'),
|
||||
},
|
||||
},
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "@boxyhq/react-ui",
|
||||
"description": "React UI components from BoxyHQ",
|
||||
"version": "do-not-change",
|
||||
"type": "module",
|
||||
"keywords": [
|
||||
"react",
|
||||
"boxyhq",
|
||||
"sso",
|
||||
"enterprise-features"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"homepage": "https://github.com/boxyhq/jackson/tree/main/sdk/ui/react#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/boxyhq/jackson/issues?q=is%3Aopen+is%3Aissue+label%3Asdk"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/boxyhq/jackson",
|
||||
"directory": "sdk/ui/react"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "SHOW_ALL_COMPONENT_DEMOS=true vite serve docs",
|
||||
"build-docs": "rimraf docs/dist && vite build docs && serve -s docs/dist",
|
||||
"ssr-docs": "rimraf docs/dist && vite-pages ssr docs && serve docs/dist",
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdx-js/react": "2.2.1",
|
||||
"@rollup/plugin-typescript": "10.0.1",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-router-dom": "5.3.3",
|
||||
"@vitejs/plugin-react": "3.0.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-router-dom": "6.6.1",
|
||||
"rimraf": "3.0.2",
|
||||
"serve": "14.1.2",
|
||||
"vite": "4.0.4",
|
||||
"vite-pages-theme-doc": "4.0.1",
|
||||
"vite-plugin-css-injected-by-js": "2.2.0",
|
||||
"vite-plugin-react-pages": "4.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// Had to create this file and install autoprefixer dev dependency to fix vite error: "[plugin:vite:css] [postcss] Cannot read properties of undefined (reading 'config')"
|
||||
const postcss = {
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
export default postcss;
|
|
@ -0,0 +1,33 @@
|
|||
# Login
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @boxyhq/react-ui
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import { Login } from '@boxyhq/react-ui';
|
||||
|
||||
// Inside render
|
||||
<Login onSubmit={async function(ssoIdentifier) {
|
||||
// Use the ssoIdentifier to resolve the SSO connection for the SSO service
|
||||
}}
|
||||
inputLabel='Tenant'
|
||||
buttonText='Sign-in with SSO'
|
||||
placeholder='contoso@boxyhq.com'
|
||||
styles={{
|
||||
container: {'--btn-outline-color': '219 14% 22%'}
|
||||
button: {color: '#fff'},
|
||||
input: {'margin-top': '2px'},
|
||||
label: {'font-size': '1.5rem'}}},
|
||||
classNames={{
|
||||
container: 'mt-2',
|
||||
button: 'btn-primary btn-block btn rounded-md',
|
||||
input: 'input-bordered input mb-5 mt-2 w-full rounded-md',
|
||||
}}
|
||||
buttonText="Sign-in with SSO"
|
||||
/>;
|
||||
```
|
|
@ -0,0 +1,34 @@
|
|||
.btn {
|
||||
background: #0070f3;
|
||||
color: #fff;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #0070f3;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
transition: box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
box-shadow: 0 0 0 1px #fff, 0 0 0 3px #0070f3;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.inp {
|
||||
border-radius: 5px;
|
||||
appearance: none;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #f5f5f5;
|
||||
margin-bottom: 1rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.inp:focus {
|
||||
border-color: #666;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* @title Login Component with custom styling
|
||||
* @description Refer the code below to see the passed props. Also supported is the passing of style attribute for each inner element (Note that inline style will override other styles).
|
||||
* @order 1
|
||||
*/
|
||||
|
||||
import { Login } from '@boxyhq/react-ui';
|
||||
import './demo1.css';
|
||||
|
||||
const Demo1 = () => {
|
||||
return (
|
||||
<Login
|
||||
onSubmit={async (ssoIdentifier) => {
|
||||
// initiate the SSO flow here
|
||||
}}
|
||||
styles={{
|
||||
input: { borderColor: '#ebedf0' },
|
||||
button: { padding: '.85rem' },
|
||||
}}
|
||||
classNames={{ button: 'btn', input: 'inp' }}
|
||||
placeholder='contoso@boxyhq.com'
|
||||
inputLabel='Team Domain *'
|
||||
buttonText='Login with SSO'
|
||||
innerProps={{ input: { type: 'email' } }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo1;
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @title Login Component with default styles
|
||||
* @description If classNames prop is not passed in, then default styling will be applied. Also supported is the passing of style attribute for each inner element (Note that inline style will override other styles).
|
||||
* @order 2
|
||||
*/
|
||||
|
||||
import { Login } from '@boxyhq/react-ui';
|
||||
|
||||
const Demo2 = () => {
|
||||
return (
|
||||
<Login
|
||||
onSubmit={async (ssoIdentifier) => {
|
||||
// initiate the SSO flow here
|
||||
}}
|
||||
styles={{ input: { border: '1px solid darkcyan' } }}
|
||||
inputLabel='Team domain *'
|
||||
placeholder='contoso@boxyhq.com'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo2;
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* @title Login Component without input display
|
||||
* @description Here we pass the ssoIdentifier directly instead of taking a user input.
|
||||
* @order 3
|
||||
*/
|
||||
|
||||
import { Login } from '@boxyhq/react-ui';
|
||||
|
||||
const Demo3 = () => {
|
||||
return (
|
||||
<Login
|
||||
onSubmit={async (ssoIdentifier) => {
|
||||
// initiate the SSO flow here
|
||||
}}
|
||||
ssoIdentifier='some-identifier'
|
||||
buttonText='SIGN IN WITH SSO'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo3;
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* @title Login Component with failing onSubmit
|
||||
* @description If error object is returned with the error message, the message would be displayed inline.
|
||||
* @order 4
|
||||
*/
|
||||
|
||||
import { Login } from '@boxyhq/react-ui';
|
||||
|
||||
const Demo4 = () => {
|
||||
return (
|
||||
<Login
|
||||
onSubmit={async (ssoIdentifier) => ({
|
||||
error: {
|
||||
message: 'Invalid team domain',
|
||||
},
|
||||
})}
|
||||
inputLabel='Team domain *'
|
||||
placeholder='contoso@boxyhq.com'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo4;
|
|
@ -0,0 +1,62 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 28rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
.input {
|
||||
font-family: inherit;
|
||||
appearance: none;
|
||||
background-color: #fff;
|
||||
border-radius: 0.375rem;
|
||||
border-width: 1px;
|
||||
border-color: #ebedf0;
|
||||
border-style: solid;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
margin-top: 1px;
|
||||
outline: none;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: 2px solid hsl(0 0% 20%/ 0.2);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
background-image: none;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: transparent;
|
||||
border: 1px solid rgb(51, 51, 51);
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
background: hsl(219 14% 28% / 0.2);
|
||||
color: hsl(0 0% 20% / 0.2);
|
||||
border-color: currentColor;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.button:hover:not(:disabled) {
|
||||
background-color: hsl(0 0% 20%);
|
||||
color: hsl(0 0% 100%);
|
||||
}
|
||||
|
||||
.button:focus-visible {
|
||||
outline: 2px solid hsl(219 14% 22%);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.button:active:hover,
|
||||
.button:active:focus {
|
||||
transform: scale(0.95);
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
import { useState, type ChangeEventHandler, type FormEvent } from 'react';
|
||||
import type { LoginProps } from './types';
|
||||
import useId from '../hooks/useId';
|
||||
import cssClassAssembler from '../utils/cssClassAssembler';
|
||||
import defaultClasses from './index.module.css';
|
||||
|
||||
const COMPONENT = 'sso';
|
||||
|
||||
const Login = ({
|
||||
ssoIdentifier = '',
|
||||
onSubmit,
|
||||
inputLabel = 'Tenant',
|
||||
placeholder = '',
|
||||
buttonText = 'Sign-in with SSO',
|
||||
styles,
|
||||
classNames,
|
||||
innerProps,
|
||||
}: LoginProps) => {
|
||||
// Generate stable html id attributes for input/span elements
|
||||
const inputId = useId(COMPONENT, 'input');
|
||||
const errorSpanId = useId(COMPONENT, 'span');
|
||||
// States for ssoIdentifier input and error message
|
||||
const [_ssoIdentifier, _setSsoIdentifier] = useState('');
|
||||
const [errMsg, setErrMsg] = useState('');
|
||||
// input event listener
|
||||
const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setErrMsg(''); // clear error if any
|
||||
_setSsoIdentifier(e.currentTarget.value);
|
||||
};
|
||||
// state for button submission
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
// call onSubmit passing the _ssoIdentifier or the preset ssoIdentifier from props
|
||||
const onButtonClick = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsProcessing(true);
|
||||
const {
|
||||
error: { message },
|
||||
} = (await onSubmit(_ssoIdentifier || ssoIdentifier)) || { error: {} };
|
||||
setIsProcessing(false);
|
||||
if (typeof message === 'string' && message) {
|
||||
setErrMsg(message);
|
||||
}
|
||||
};
|
||||
|
||||
const isError = !!errMsg;
|
||||
// if preset ssoIdentifier not passed in, then render input UI
|
||||
const shouldRenderInput = !ssoIdentifier;
|
||||
|
||||
const inputUI = shouldRenderInput ? (
|
||||
<>
|
||||
<label
|
||||
htmlFor={inputId}
|
||||
style={styles?.label}
|
||||
className={cssClassAssembler(classNames?.label, defaultClasses.label)}
|
||||
{...innerProps?.label}>
|
||||
{inputLabel}
|
||||
</label>
|
||||
<input
|
||||
id={inputId}
|
||||
value={_ssoIdentifier}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
style={styles?.input}
|
||||
className={cssClassAssembler(classNames?.input, defaultClasses.input)}
|
||||
aria-invalid={isError}
|
||||
aria-describedby={errorSpanId}
|
||||
{...innerProps?.input}
|
||||
/>
|
||||
{isError && <span id={errorSpanId}>{errMsg}</span>}
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const disableButton = !(_ssoIdentifier || ssoIdentifier) || isProcessing;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cssClassAssembler(classNames?.container, defaultClasses.container)}
|
||||
style={styles?.container}
|
||||
{...innerProps?.container}>
|
||||
{inputUI}
|
||||
<button
|
||||
disabled={disableButton}
|
||||
type='button'
|
||||
onClick={onButtonClick}
|
||||
style={styles?.button}
|
||||
className={cssClassAssembler(classNames?.button, defaultClasses.button)}
|
||||
{...innerProps?.button}>
|
||||
{buttonText}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
|
@ -0,0 +1,54 @@
|
|||
import type {
|
||||
ButtonHTMLAttributes,
|
||||
CSSProperties,
|
||||
HTMLAttributes,
|
||||
InputHTMLAttributes,
|
||||
LabelHTMLAttributes,
|
||||
} from 'react';
|
||||
|
||||
export interface LoginProps {
|
||||
/**
|
||||
* Could be email, tenant or anything that can help to resolve the SSO connection. Use this if you want to set the value directly instead of taking a user input
|
||||
*/
|
||||
ssoIdentifier?: string;
|
||||
/**
|
||||
* Function to be passed into the component, takes in a value (ssoIdentifier) that can be used to resolve the SSO Connection in the Jackson SSO service.
|
||||
* @param {string} ssoIdentifier Could be email, tenant or anything that can help to resolve the SSO connection.
|
||||
* @returns {Promise} Any error raised while trying to resolve the ssoIdentifier. This could be displayed inline in the component. In case the error is handled upstream by means of a toast or a UI notification, nothing needs to be returned.
|
||||
*/
|
||||
onSubmit: (ssoIdentifier: string) => Promise<{ error: { message: string } } | void>;
|
||||
/**
|
||||
* Label for the input field that can accept the ssoIdentifier value
|
||||
* @defaultValue Tenant
|
||||
*/
|
||||
inputLabel?: string;
|
||||
/**
|
||||
* Placeholder for the input field that can accept the ssoIdentifier value
|
||||
* @defaultValue ''
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* Text/Name of the login button
|
||||
* @defaultValue Sign-in with SSO
|
||||
*/
|
||||
buttonText?: string;
|
||||
/**
|
||||
* Styles for each inner component that Login is made up of.
|
||||
*/
|
||||
styles?: {
|
||||
container?: CSSProperties;
|
||||
button?: CSSProperties;
|
||||
input?: CSSProperties;
|
||||
label?: CSSProperties;
|
||||
};
|
||||
/**
|
||||
* Classnames for each inner components that Login is made up of.
|
||||
*/
|
||||
classNames?: { container?: string; button?: string; input?: string; label?: string };
|
||||
innerProps?: {
|
||||
input?: InputHTMLAttributes<HTMLInputElement>;
|
||||
button?: ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
label?: LabelHTMLAttributes<HTMLLabelElement>;
|
||||
container?: HTMLAttributes<HTMLDivElement>;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import htmlIdGenerator from '../utils/htmlIdGenerator';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param component Pass the SDK component name here (e.g., sso)
|
||||
* @param elementType Pass the HTML element type for which the Id is to be generated (e.g., input)
|
||||
* @returns {string} Id that is gauranteed to be unique suitable for use as HTML id attributes
|
||||
*/
|
||||
const useId = (component: string, elementType?: string) => {
|
||||
const [id, setId] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
setId(htmlIdGenerator(component, elementType));
|
||||
}, [component, elementType]);
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
export default useId;
|
|
@ -0,0 +1,2 @@
|
|||
export { default as Login } from './Login';
|
||||
export * from './Login/types'
|
|
@ -0,0 +1,4 @@
|
|||
const cssClassAssembler = (customClasses = '', defaultClasses: string) =>
|
||||
customClasses ? customClasses : defaultClasses;
|
||||
|
||||
export default cssClassAssembler;
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Util function that helps to generate unique HTML ids that are namespaced by
|
||||
* the prefix and element type
|
||||
* @param prefix Pass anything that needs to be prefixed to the id
|
||||
* @param elementType Pass the HTML element type here (e.g., input)
|
||||
* @returns {string} Id that is gauranteed to be unique and suitable for html id attributes across various components
|
||||
*/
|
||||
const htmlIdGenerator = (prefix: string, elementType?: string) => {
|
||||
const uniqueId = Math.floor(1e6 + Math.random() * 1e6).toString();
|
||||
return elementType ? `boxyhq-${prefix}-${elementType}-${uniqueId}` : `boxyhq-${prefix}-${uniqueId}`;
|
||||
};
|
||||
|
||||
export default htmlIdGenerator;
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -0,0 +1,24 @@
|
|||
// This file was originally from https://github.com/vitejs/vite-plugin-react-pages/blob/main/packages/create-project/template-lib/tsconfig.json
|
||||
// Modified by referring https://github.com/vitejs/vite/blob/main/packages/create-vite/template-react-ts/tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"jsx": "react-jsx",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"paths": {
|
||||
"@boxyhq/react-ui": ["./src"]
|
||||
},
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// this is the build config for this demo library source, not the playground
|
||||
// the build config for the library playground (document) is located at docs/vite.config.ts
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
// use vite library mode to build the package
|
||||
// https://vitejs.dev/guide/build.html#library-mode
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
name: 'BoxyHQUI',
|
||||
// the proper extensions will be added
|
||||
fileName: '@boxyhq/react-ui',
|
||||
},
|
||||
rollupOptions: {
|
||||
// make sure to externalize deps that shouldn't be bundled
|
||||
// into your library
|
||||
external: ['react'],
|
||||
output: {
|
||||
// Provide global variables to use in the UMD build
|
||||
// for externalized deps
|
||||
globals: {
|
||||
react: 'React',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
cssInjectedByJsPlugin(),
|
||||
// use @rollup/plugin-typescript to generate .d.ts files
|
||||
// https://github.com/rollup/plugins/tree/master/packages/typescript#noforceemit
|
||||
typescript({
|
||||
declaration: true,
|
||||
emitDeclarationOnly: true,
|
||||
noForceEmit: true,
|
||||
declarationDir: resolve(__dirname, 'dist/types'),
|
||||
rootDir: resolve(__dirname, 'src'),
|
||||
exclude: ['**/demos/*', '**/utils/*', '**/hooks/*'],
|
||||
}),
|
||||
],
|
||||
});
|
|
@ -27,7 +27,7 @@
|
|||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "types/*.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", "npm/typeorm.ts"],
|
||||
"exclude": ["node_modules", "npm/typeorm.ts", "sdk"],
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS"
|
||||
|
|
Loading…
Reference in New Issue