Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add demo form, add theme customization #100

Merged
merged 26 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5fec11e
Add demo form with fixed no-event mock CoreJS
RJFelix Jan 10, 2024
05f119a
Add light and dark theming
RJFelix Jan 13, 2024
b4eae09
Expand and refactor theming
RJFelix Jan 16, 2024
c797281
Fix storybook
RJFelix Jan 16, 2024
43fbcfa
Refactor and expand on theming a bit
RJFelix Jan 26, 2024
7b74da8
Add theme prop to forms, pass through to underlying UniversalForm
RJFelix Jan 26, 2024
3439ba8
Add more options, add rudimentary controls to demo form, rework toggl…
RJFelix Jan 26, 2024
cd956c8
Add spacing between demo form theme controls, allow setting/clearing …
RJFelix Jan 26, 2024
7aec94c
Have the 'mini' size be narrower
RJFelix Jan 26, 2024
1a5868b
Embiggen font for large size
RJFelix Jan 26, 2024
6565298
Get rid of @layer in CSS because it doesn't work as intended. Fix a t…
RJFelix Jan 31, 2024
7ace8ee
Have mockUserfront accept an authFlow to use
RJFelix Jan 31, 2024
0711dce
Remove unnecessary console logs
RJFelix Jan 31, 2024
ce4a384
Accept mode for DemoForm
RJFelix Feb 2, 2024
14f811e
Set test mode text to always be dark
RJFelix Feb 2, 2024
693687e
Address PR comments
RJFelix Feb 2, 2024
d6488e9
Logout button supports theming
RJFelix Feb 2, 2024
e3232bd
Pass through demo state to underlying form; set max width for logout …
RJFelix Feb 2, 2024
947d7ac
Add more null checks
RJFelix Feb 2, 2024
8b8cd2c
Allow passing an external style to LogoutButton
RJFelix Feb 2, 2024
e4dc454
Increment to version 1.1, run npm audit to clear vulnerabilities
RJFelix Feb 2, 2024
e678143
Fix remaining references to default.css
RJFelix Feb 2, 2024
171d1f6
Remove default.css
RJFelix Feb 2, 2024
603eaef
Merge branch 'main' into demo-form-and-theme
RJFelix Feb 2, 2024
1b9df64
Fix failing test
RJFelix Feb 2, 2024
dc1699b
Update to v1.1.0-alpha.1
github-actions[bot] Feb 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 137 additions & 137 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package/.storybook/decorators/css-variables.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import styles from "../../src/themes/default.css?inline";
import styles from "../../src/themes/dynamic.css?inline";

const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(styles);
Expand Down
2 changes: 1 addition & 1 deletion package/.storybook/preview.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import "../src/themes/default.css";
import "../src/themes/dynamic.css";

import { withCssVariables } from "./decorators/css-variables"

Expand Down
2 changes: 1 addition & 1 deletion package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ The `src` directory contains the package's source:

**CSS**

The forms' CSS is in `themes/default.css`. This uses CSS variables to allow full customization of forms' appearance. Variables are all prefixed `--userfront` to separate them from variables in client code.
The forms' CSS is in `themes/dynamic.css`. This uses CSS variables to allow full customization of forms' appearance. Variables are all prefixed `--userfront` to separate them from variables in client code. The `dynamic` theme uses CSS's color modification capabilities (`color-mix` mostly) to derive a full color scheme (with active/hover states etc) from 1-3 main colors: "dark" (primary), "light" (secondary), and "accent".

**Storybook**

Expand Down
4 changes: 2 additions & 2 deletions package/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@userfront/toolkit",
"version": "1.0.6",
"version": "1.1.0-alpha.0",
"description": "Bindings and components for authentication with Userfront with React, Vue, other frameworks, and plain JS + HTML",
"type": "module",
"directories": {
Expand Down Expand Up @@ -47,7 +47,7 @@
"import": "./dist/web-component.es.js",
"require": "./dist/web-component.umd.js"
},
"./themes/default.css": "./dist/style.css"
"./themes/dynamic.css": "./dist/style.css"
},
"files": [
"dist"
Expand Down
33 changes: 25 additions & 8 deletions package/src/components/Input/BaseInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,41 @@
* @property {string} errorMessage The error message to display
* @returns
*/
import { FaEye, FaEyeSlash } from "react-icons/fa";
import { useState } from "react";

export default function BaseInput({
label,
showError = false,
errorMessage = "Required field",
isPassword = false,
...props
} = {}) {
const [hideContents, setHideContents] = useState(isPassword);

return (
<>
<div className="userfront-password-input-container">
{label && <label htmlFor={props.name}>{label}</label>}
<input
className={`userfront-input ${
showError ? "userfront-input-error" : ""
}`}
{...props}
/>
<div className="userfront-input-container">
<input
type={hideContents ? "password" : "text"}
className={`userfront-input ${
showError ? "userfront-input-error" : ""
}`}
{...props}
/>
{isPassword && (
<div
className="userfront-password-toggle"
onClick={() => setHideContents(!hideContents)}
>
{hideContents ? <FaEye size="15px" /> : <FaEyeSlash size="15px" />}
</div>
)}
</div>
{showError && (
<div className="userfront-input-error-message">{errorMessage}</div>
)}
</>
</div>
);
}
38 changes: 10 additions & 28 deletions package/src/components/Input/PasswordInput.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { useState } from "react";
import { FaEye, FaEyeSlash } from "react-icons/fa";
import BaseInput from "./BaseInput";

/**
Expand All @@ -9,36 +7,20 @@ import BaseInput from "./BaseInput";
* @returns
*/
export default function PasswordInput({
label = "Password",
label = "Choose a password",
showError,
errorMessage = "Please enter your password",
...props
}) {
const [showPassword, setShowPassword] = useState(false);

function togglePasswordVisibility() {
setShowPassword(!showPassword);
}

return (
<>
<label htmlFor="password">{label}</label>
<span className="userfront-password-input-container">
<BaseInput
type={showPassword ? "text" : "password"}
name="password"
aria-describedby="userfront-password-rules"
showError={showError}
errorMessage={errorMessage}
{...props}
/>
<div
className="userfront-password-toggle"
onClick={togglePasswordVisibility}
>
{showPassword ? <FaEyeSlash size="15px" /> : <FaEye size="15px" />}
</div>
</span>
</>
<BaseInput
isPassword
name="password"
aria-describedby="userfront-password-rules"
showError={showError}
errorMessage={errorMessage}
label={label}
{...props}
/>
);
}
104 changes: 102 additions & 2 deletions package/src/components/LogoutButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,40 @@ import { callUserfront } from "../services/userfront";
*
* @param {object} props
* @param {boolean=} props.disabled - is the button disabled?
* @param {object=} props.theme - theme information: color scheme, font, sizing, options
* @param {string=} props.theme.colors.light - light color to use when deriving color scheme
* @param {string=} props.theme.colors.dark - dark color to use when deriving color scheme
* @param {object=} props.theme.colors - theme colors
* @param {string=} props.theme.colors.accent - accent color to use when deriving color scheme (optional)
* @param {string=} props.theme.colors.lightBackground - background color for light mode (optional)
* @param {string=} props.theme.colors.darkBackground - background color for dark mode (optional)
* @param {string=} props.theme.fontFamily - CSS font family to use for the form
* @param {object=} props.theme.extras - additional options to modify the form's appearance
* @param {boolean=} props.theme.extras.rounded - make form elements appear more rounded generally
* @param {boolean=} props.theme.extras.squared - make form elements appear more squared-off generally
* @param {boolean=} props.theme.extras.gradientButtons - add an interactive gradient to buttons
* @param {boolean=} props.theme.extras.hideSecuredMessage - hide the "secured by Userfront" message
* @param {boolean=} props.theme.extras.dottedOutline - use a dotted outline with some padding around active elements,
* rather than a solid outline that is flush with the outside of the element
* @param {boolean=} props.theme.extras.raisedButtons - use old-school 3D-looking buttons
* @param {(string|boolean)=} props.redirect - URL to redirect to. If false, disables redirect.
* If absent, redirect based on the tenant's after-logout path.
* @param {array=} props.children - children to display in the button. Shows "Log out" if children are absent.
* @returns
*/
const LogoutButton = ({ redirect, disabled = false, children }) => {
const LogoutButton = ({
redirect,
disabled = false,
demo = false,
theme = {},
children,
style = {},
}) => {
const _children = children || "Log out";
const handleClick = async () => {
if (demo) {
return;
}
try {
const arg = {};
if (redirect != null) {
Expand All @@ -29,10 +55,84 @@ const LogoutButton = ({ redirect, disabled = false, children }) => {
);
}
};

// Build the theme

// Define CSS variables
const themeColors = theme?.colors || {};
const themeStyle = {
"--userfront-light-color": themeColors.light || "#ffffff",
"--userfront-dark-color": themeColors.dark || "#5e72e4",
"--userfront-accent-color": themeColors.accent || "#13a0ff",
};
if (themeColors.lightBackground) {
themeStyle["--userfront-light-background-color"] =
themeColors.lightBackground;
}
if (themeColors.darkBackground) {
themeStyle["--userfront-dark-background-color"] =
themeColors.darkBackground;
}
if (theme?.fontFamily) {
themeStyle["--userfront-font-family"] = theme.fontFamily;
}

// Classes for color schemes
// For now, "light scheme only" is default, to match existing behavior.
// In a future iteration "auto scheme" should be made default
let colorSchemeClass = "userfront-light-scheme";
if (theme?.colorScheme === "dark") {
colorSchemeClass = "userfront-dark-scheme";
}
if (theme?.colorScheme === "auto") {
colorSchemeClass = "userfront-auto-scheme";
}

// CSS variables for sizing
if (theme?.size === "compact") {
themeStyle["--userfront-em-size"] = "14px";
themeStyle["--userfront-spacing"] = "0.5em";
}
if (theme?.size === "mini") {
themeStyle["--userfront-em-size"] = "12px";
themeStyle["--userfront-spacing"] = "0.5em";
themeStyle["--userfront-container-width"] = "250px";
}
if (theme?.size === "spaced") {
themeStyle["--userfront-em-size"] = "14px";
themeStyle["--userfront-spacing"] = "20px";
}
if (theme?.size === "large") {
themeStyle["--userfront-em-size"] = "20px";
themeStyle["--userfront-spacing"] = "18px";
}

// Attach classes for theme customizations
// TODO: syntax and terminology is flexible here
const extrasClassMap = {
gradientButtons: "userfront-gradient-buttons",
hideSecuredMessage: "userfront-hide-branding",
raisedButtons: "userfront-raised-buttons",
dottedOutlines: "userfront-dotted-outlines",
};
const extras = theme?.extras || {};
let customizationClasses = "";
Object.entries(extras)
.filter(([key, val]) => Boolean(val))
.forEach(([key]) => {
if (key in extrasClassMap) {
customizationClasses += ` ${extrasClassMap[key]}`;
} else {
customizationClasses += ` userfront-${key}`;
}
});
customizationClasses = customizationClasses.trim();

return (
<button
onClick={handleClick}
className="userfront-toolkit userfront-element userfront-button userfront-button-logout"
style={{ ...themeStyle, ...style }}
className={`userfront-toolkit userfront-element userfront-button userfront-button-logout ${customizationClasses}`}
aria-disabled={disabled}
disabled={disabled}
>
Expand Down
85 changes: 82 additions & 3 deletions package/src/forms/UniversalForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,13 @@ const componentForStep = (state) => {
}
};

const UniversalForm = ({ state, onEvent }) => {
const UniversalForm = ({
theme = {},
state,
isDemo = false,
demoState = "live",
onEvent,
}) => {
// Apply CSS classes based on the size of the form's container
const [containerRef, setContainerRef] = useState();
const sizeClass = useSizeClass(containerRef);
Expand All @@ -581,15 +587,88 @@ const UniversalForm = ({ state, onEvent }) => {
type: state.context.config.type,
};

// Build the theme

// Define CSS variables
const themeColors = theme.colors || {};
const style = {
"--userfront-light-color": themeColors.light || "#ffffff",
"--userfront-dark-color": themeColors.dark || "#5e72e4",
"--userfront-accent-color": themeColors.accent || "#13a0ff",
};
if (themeColors.lightBackground) {
style["--userfront-light-background-color"] = themeColors.lightBackground;
}
if (themeColors.darkBackground) {
style["--userfront-dark-background-color"] = themeColors.darkBackground;
}
if (theme.fontFamily) {
style["--userfront-font-family"] = theme.fontFamily;
}

// Classes for color schemes
// For now, "light scheme only" is default, to match existing behavior.
// In a future iteration "auto scheme" should be made default
let colorSchemeClass = "userfront-light-scheme";
if (theme.colorScheme === "dark") {
colorSchemeClass = "userfront-dark-scheme";
}
if (theme.colorScheme === "auto") {
colorSchemeClass = "userfront-auto-scheme";
}

// CSS variables for sizing
if (theme.size === "compact") {
style["--userfront-em-size"] = "14px";
style["--userfront-spacing"] = "0.5em";
}
if (theme.size === "mini") {
style["--userfront-em-size"] = "12px";
style["--userfront-spacing"] = "0.5em";
style["--userfront-container-width"] = "250px";
}
if (theme.size === "spaced") {
style["--userfront-em-size"] = "14px";
style["--userfront-spacing"] = "20px";
}
if (theme.size === "large") {
style["--userfront-em-size"] = "20px";
style["--userfront-spacing"] = "18px";
}

// Attach classes for theme customizations
// TODO: syntax and terminology is flexible here
const extrasClassMap = {
gradientButtons: "userfront-gradient-buttons",
hideSecuredMessage: "userfront-hide-branding",
raisedButtons: "userfront-raised-buttons",
dottedOutlines: "userfront-dotted-outlines",
};
const extras = theme.extras || {};
let customizationClasses = "";
Object.entries(extras)
.filter(([key, val]) => Boolean(val))
.forEach(([key]) => {
if (key in extrasClassMap) {
customizationClasses += ` ${extrasClassMap[key]}`;
} else {
customizationClasses += ` userfront-${key}`;
}
});
customizationClasses = customizationClasses.trim();

return (
<div
ref={setContainerRef}
className={`userfront-toolkit userfront-container ${sizeClass}`}
className={`userfront-toolkit userfront-container ${sizeClass} ${colorSchemeClass} ${customizationClasses}`}
style={style}
>
<h2>{title}</h2>
<Component onEvent={onEvent} {...defaultProps} {...props} />
<div>
<SecuredByUserfront mode={state.context.config?.mode} />
<SecuredByUserfront
mode={isDemo ? demoState : state.context.config?.mode}
/>
</div>
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion package/src/index-cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ import SignupForm from "./packaged-forms/SignupForm";
import LoginForm from "./packaged-forms/LoginForm";
import PasswordResetForm from "./packaged-forms/PasswordResetForm";
import LogoutButton from "./components/LogoutButton";
import DemoForm from "./packaged-forms/DemoForm";

UserfrontCore.SignupForm = SignupForm;
UserfrontCore.LoginForm = LoginForm;
UserfrontCore.PasswordResetForm = PasswordResetForm;
UserfrontCore.LogoutButton = LogoutButton;
UserfrontCore.DemoForm = DemoForm;

/*
* CSS styles for the forms
*/
import "./themes/default.css";
import "./themes/dynamic.css";

// TODO #9: add link to upgrade guide
UserfrontCore.build = (toolId) => {
Expand Down
Loading
Loading