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

fix: password input field #133

Merged
merged 4 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 26 additions & 0 deletions src/assets/passwordInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

const togglePassword = function (e) {
if (e.type === "password") {
e.type = "text"
} else {
e.type = "password"
}
}

document.getElementsByName("password").forEach((p) => {
const visibilityToggle = p.nextSibling
if (visibilityToggle) {
visibilityToggle.addEventListener("click", function () {
togglePassword(p)
visibilityToggle.dataset.checked =
visibilityToggle.dataset.checked === "true" ? "false" : "true"
})
visibilityToggle.addEventListener("keydown", function () {
togglePassword(p)
visibilityToggle.dataset.checked =
visibilityToggle.dataset.checked === "true" ? "false" : "true"
})
}
})
51 changes: 22 additions & 29 deletions src/react-components/input-field.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import cn from "classnames"
import { JSX } from "react"
import { JSX, useEffect, useRef, useState } from "react"

import {
gridStyle,
inputFieldSecurityStyle,
inputFieldStyle,
inputFieldTitleStyle,
passwordInputContainerStyle,
inputFieldVisibilityToggleLabelStyle,
inputFieldVisibilityToggleStyle,
inputFieldFallbackWrapperStyle,
typographyStyle,
} from "../theme"
import { Message, MessageStyleProps } from "./message"
Expand All @@ -30,14 +26,16 @@ export const InputField = ({
header: title,
helperMessage,
messageTestId,
fullWidth,
className,
dataTestid,
id,
...props
}: InputFieldProps): JSX.Element => {
const inputId = id ?? useIdWithFallback()

const [visibility, setVisibility] = useState(false)
const visibilityToggleRef = useRef<HTMLDivElement>(null)

return (
<div
data-testid={dataTestid}
Expand All @@ -52,31 +50,31 @@ export const InputField = ({
{props.required && <span className={inputFieldTitleStyle}>*</span>}
</label>
)}
{props.type === "password" && (
<div
className={passwordInputContainerStyle}
style={{ width: fullWidth ? "100%" : "auto" }}
>
<input
className={inputFieldVisibilityToggleStyle}
id={inputId + "-visibility-toggle"}
type="checkbox"
value={0}
/>

{props.type === "password" ? (
<div style={{ position: "relative" }}>
<input
className={cn(
inputFieldSecurityStyle,
inputFieldStyle,
typographyStyle({ size: "small", type: "regular" }),
)}
placeholder={" "} // we need this so the input css field border is not green by default
id={inputId}
{...props}
type="text"
type={visibility ? "text" : "password"}
/>
<label
<div
ref={visibilityToggleRef}
onClick={(e) => {
setVisibility(!visibility)
e.currentTarget.dataset.checked =
e.currentTarget.dataset.checked =
e.currentTarget.dataset.checked === "true" ? "false" : "true"
}}
data-checked="false"
className={inputFieldVisibilityToggleLabelStyle}
htmlFor={inputId + "-visibility-toggle"}
tabIndex={0}
aria-label="Toggle password visibility"
>
<svg
width="22"
Expand Down Expand Up @@ -106,14 +104,9 @@ export const InputField = ({
fill="#0F172A"
/>
</svg>
</label>
</div>
</div>
)}

<div
className={inputFieldFallbackWrapperStyle}
style={{ width: fullWidth ? "100%" : "auto" }}
>
) : (
<input
className={cn(
inputFieldStyle,
Expand All @@ -123,7 +116,7 @@ export const InputField = ({
id={inputId}
{...props}
/>
</div>
)}

{typeof helperMessage === "string" ? (
<Message data-testid={messageTestId} severity={props.severity}>
Expand Down
2 changes: 1 addition & 1 deletion src/test/models/AuthPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class AuthPage {
await expect(this.locator.locator(`*[name="${key}"]`)).toBeHidden()
} else {
await expect(
this.locator.locator(`*[name="${t[key].name || key}"]:visible`),
this.locator.locator(`*[name="${t[key].name || key}"]`),
).toBeVisible()
}
}
Expand Down
160 changes: 11 additions & 149 deletions src/theme/input-field.css.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import {
globalStyle,
GlobalStyleRule,
ComplexStyleRule,
style,
} from "@vanilla-extract/css"
import { globalStyle, style } from "@vanilla-extract/css"
import { pxToRem } from "../common"
import { oryTheme } from "./theme.css"

export const inputFieldTitleStyle = style({
color: oryTheme.accent.def,
})

// attached to the input field itself
// when using the icon: 'security' variant,
// we expect the container to show the border instead
export const inputFieldStyle = style({
all: "unset",
boxSizing: "border-box",
display: "inline-flex",
color: oryTheme.input.text,
border: `1px solid ${oryTheme.border.def}`,
borderRadius: pxToRem(4),
Expand Down Expand Up @@ -48,94 +41,6 @@ export const inputFieldStyle = style({
},
})

// input field with obfuscated text
// this can be attached to a input[type=text] and act
// as an input[type=password]
// **WARNING**: this is not supported by all browsers
// firefox only recently aded support for this!
// https://caniuse.com/mdn-css_properties_-webkit-text-security
export const inputFieldSecurityStyle = style({
all: "unset",
boxSizing: "border-box",
color: oryTheme.input.text,
borderRadius: pxToRem(4),
padding: pxToRem(12, 24),
background: oryTheme.input.background,
border: "none",
margin: 0,
flexGrow: 1,
selectors: {
"&:hover": {
border: `none`,
},
"&:focus": {
border: `none`,
},
"&:active": {
border: `none`,
},
"&:disabled": {
border: `none`,
},
},
["-webkit-text-security"]: "disc",
} as ComplexStyleRule & {
["-webkit-text-security"]: "disc"
})

// a div container that wraps the input field with the icon
// this is used when we want an icon on the input field
// we then take over the border styling from the input field on the container
export const passwordInputContainerStyle = style({
display: "none",
flexDirection: "row",
boxSizing: "border-box",
alignItems: "center",
gap: 0,
padding: 0,
width: "fit-content",
background: oryTheme.input.background,
border: `1px solid ${oryTheme.border.def}`,
borderRadius: pxToRem(4),
selectors: {
"&:hover": {
borderColor: oryTheme.accent.muted,
},
"&:focus-within": {
borderColor: oryTheme.accent.muted,
},
[`&:has(${inputFieldSecurityStyle}:active)`]: {
borderColor: oryTheme.accent.emphasis,
},
[`&:has(${inputFieldSecurityStyle}:not(:focus):not(:placeholder-shown):invalid)`]:
{
borderColor: oryTheme.error.emphasis,
},
[`&:has(${inputFieldSecurityStyle}:not(:focus):not(:placeholder-shown):valid)`]:
{
borderColor: oryTheme.success.emphasis,
},
[`&:has(${inputFieldSecurityStyle}:disabled)`]: {
borderColor: oryTheme.input.disabled,
},
},
})

// this is attached to a checkbox that toggles the visibility of the input field
// we don't want to show the checkbox, but we want to show the label
export const inputFieldVisibilityToggleStyle = style({
cursor: "pointer",
WebkitAppearance: "none",
MozAppearance: "none",
appearance: "none",
display: "none",
userSelect: "none",
MozUserSelect: "none",
WebkitUserSelect: "none",
})

// this is a label attached to the checkbox that toggles the visibility of the input field
// we don't want to show the checkbox, but we want to show the label
export const inputFieldVisibilityToggleLabelStyle = style({
cursor: "pointer",
WebkitAppearance: "none",
Expand All @@ -145,68 +50,25 @@ export const inputFieldVisibilityToggleLabelStyle = style({
MozUserSelect: "none",
WebkitUserSelect: "none",
margin: pxToRem(0, 18, 0, 0),
display: "inline-flex",
// display: "inline-flex",
right: 0,
top: 0,
bottom: 0,
display: "flex",
alignItems: "center",
position: "absolute",
})

export const inputFieldFallbackWrapperStyle = style({})

globalStyle(
`${inputFieldVisibilityToggleStyle}:checked ~ label svg:last-child`,
`${inputFieldVisibilityToggleLabelStyle}[data-checked="true"] svg:last-child`,
{
display: "none",
},
)

globalStyle(
`${inputFieldVisibilityToggleStyle}:not(:checked) ~ label svg:first-child`,
`${inputFieldVisibilityToggleLabelStyle}[data-checked="false"] svg:first-child`,
{
display: "none",
},
)

/**
* On selecting the visibility checkbox, we want to show the text in the input field
*
**/
globalStyle(
`${inputFieldVisibilityToggleStyle}:checked ~ ${inputFieldSecurityStyle}`,
{
["-webkit-text-security"]: "none",
} as GlobalStyleRule & {
["-webkit-text-security"]: "none"
},
)

/**
* Firefox does not support the :has selector yet, so we need to test for this
* https://caniuse.com/css-has
* https://caniuse.com/mdn-css_properties_-webkit-text-security
* If both the :has selector and the -webkit-text-security: disc are supported,
* we can show the password input field with a visibility toggle
* and hide the fallback password input field
**/
globalStyle(`${passwordInputContainerStyle}:has(*)`, {
"@supports": {
"(-webkit-text-security: disc)": {
display: "flex",
},
},
})

/**
* Firefox does not support the :has selector yet, so we need to test for this
* https://caniuse.com/css-has
* https://caniuse.com/mdn-css_properties_-webkit-text-security
* If both the :has selector and the -webkit-text-security: disc are supported,
* we hide this input field in favor of the custom password input field
**/
globalStyle(
`${inputFieldFallbackWrapperStyle}:has(${inputFieldStyle}[type="password"])`,
{
"@supports": {
"(-webkit-text-security: disc)": {
display: "none",
},
},
},
)
Loading