Skip to content

Commit fb935d8

Browse files
committed
add stories
1 parent a8638fd commit fb935d8

File tree

2 files changed

+285
-0
lines changed

2 files changed

+285
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
.viewport {
2+
--easing: cubic-bezier(0.165, 0.84, 0.44, 1);
3+
display: flex;
4+
flex-direction: column;
5+
height: 100vh;
6+
width: 100vw;
7+
justify-content: center;
8+
align-items: center;
9+
10+
margin: 0;
11+
padding: 24px;
12+
font-family:
13+
system-ui,
14+
-apple-system,
15+
BlinkMacSystemFont,
16+
'Segoe UI',
17+
Roboto,
18+
Oxygen,
19+
Ubuntu,
20+
Cantarell,
21+
'Open Sans',
22+
'Helvetica Neue',
23+
sans-serif;
24+
font-size: 14px;
25+
}
26+
27+
.bars {
28+
width: 240px;
29+
height: 4px;
30+
margin-top: 12px;
31+
gap: 2px;
32+
display: grid;
33+
grid-template-columns: repeat(var(--radix-password-strength-rule-count), 1fr);
34+
}
35+
36+
.bars[data-step='1'],
37+
.circle[data-step='1'] {
38+
--color: #e54d2e;
39+
}
40+
41+
:is(.bars, .circle)[data-step='2'] {
42+
--color: #ec9455;
43+
}
44+
45+
:is(.bars, .circle)[data-step='3'] {
46+
--color: #e9c162;
47+
}
48+
49+
:is(.bars, .circle)[data-step='4'] {
50+
--color: #65ba74;
51+
}
52+
53+
.bar {
54+
background-color: #ccc;
55+
transition: background 0.2s var(--easing);
56+
}
57+
58+
.bar[data-active='true'] {
59+
transition: background 0.4s var(--easing);
60+
background-color: var(--color);
61+
animation: glow 0.75s ease-out;
62+
}
63+
64+
.bar {
65+
border-radius: 999px;
66+
}
67+
68+
.circle {
69+
width: 140px;
70+
height: 140px;
71+
margin-top: 2rem;
72+
}
73+
74+
.circleSegment {
75+
fill: #ccc;
76+
transition: fill 0.2s var(--easing);
77+
}
78+
79+
.circleSegment[data-active='true'] {
80+
transition: fill 0.4s var(--easing);
81+
fill: var(--color);
82+
}
83+
84+
.rules {
85+
list-style: none;
86+
margin-block: 8px;
87+
padding: 0;
88+
display: flex;
89+
flex-direction: column;
90+
gap: 6px;
91+
color: #999;
92+
}
93+
94+
.rule {
95+
display: flex;
96+
align-items: center;
97+
gap: 4px;
98+
transition: all 0.2s ease-out;
99+
}
100+
101+
.input {
102+
all: unset;
103+
box-sizing: border-box;
104+
display: inline-flex;
105+
align-items: center;
106+
justify-content: center;
107+
padding-inline: 12px;
108+
width: 240px;
109+
height: 32px;
110+
border-radius: 4px;
111+
font-size: 14px;
112+
line-height: 1;
113+
font-weight: 400;
114+
font-family: inherit;
115+
box-shadow: inset 0 0 0 1px #ccc;
116+
color: #111;
117+
118+
&::placeholder {
119+
color: #999;
120+
}
121+
122+
&:focus {
123+
box-shadow:
124+
inset 0 0 0 1px #5b5bd6,
125+
0 0 0 2px #000eff25;
126+
}
127+
}
128+
129+
@keyframes glow {
130+
0% {
131+
box-shadow: 0 0px 10px transparent;
132+
}
133+
134+
20%,
135+
60% {
136+
box-shadow: 0 0px 10px var(--color);
137+
}
138+
139+
100% {
140+
box-shadow: 0 0px 10px transparent;
141+
}
142+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import * as React from 'react';
2+
import { PasswordStrength } from 'radix-ui';
3+
import styles from './password-strength.stories.module.css';
4+
5+
export default {
6+
title: 'Components/PasswordStrength',
7+
};
8+
9+
export const Styled = () => {
10+
const [password, setPassword] = React.useState('');
11+
const rules: PasswordStrength.PasswordStrengthRule[] = [
12+
{
13+
label: 'At least 8 characters long.',
14+
validate: (password) => password.length >= 8,
15+
},
16+
{
17+
label: 'At least one uppercase letter.',
18+
validate: (password) => /[A-Z]/.test(password),
19+
},
20+
{
21+
label: 'At least one number.',
22+
validate: (password) => /[0-9]/.test(password),
23+
},
24+
{
25+
label: 'At least one special character.',
26+
validate: (password) => /[^A-Za-z0-9]/.test(password),
27+
},
28+
];
29+
return (
30+
<div className={styles.viewport}>
31+
<PasswordStrength.Root value={password} onValueChange={setPassword} rules={rules}>
32+
<PasswordStrength.Input placeholder="Password" className={styles.input} data-1p-ignore />
33+
<PasswordStrength.Progress className={styles.bars}>
34+
{({ rules }) =>
35+
rules.map((rule, index) => (
36+
<PasswordStrength.Indicator key={rule.label} index={index} className={styles.bar} />
37+
))
38+
}
39+
</PasswordStrength.Progress>
40+
41+
<PasswordStrength.Rules>
42+
{({ rules }) => (
43+
<ul className={styles.rules}>
44+
{rules.map(({ label, isValid }) => (
45+
<li
46+
className={styles.rule}
47+
key={label}
48+
style={{ color: isValid ? '#222' : undefined }}
49+
>
50+
<CheckIcon style={{ color: isValid ? '#30a46c' : '#999' }} />
51+
{label}
52+
</li>
53+
))}
54+
</ul>
55+
)}
56+
</PasswordStrength.Rules>
57+
</PasswordStrength.Root>
58+
</div>
59+
);
60+
};
61+
62+
export const Circle = () => {
63+
const [password, setPassword] = React.useState('');
64+
const rules: PasswordStrength.PasswordStrengthRule[] = [
65+
{
66+
label: 'At least 8 characters long.',
67+
validate: (password) => password.length >= 8,
68+
},
69+
{
70+
label: 'At least one uppercase letter.',
71+
validate: (password) => /[A-Z]/.test(password),
72+
},
73+
{
74+
label: 'At least one number.',
75+
validate: (password) => /[0-9]/.test(password),
76+
},
77+
{
78+
label: 'At least one special character.',
79+
validate: (password) => /[^A-Za-z0-9]/.test(password),
80+
},
81+
];
82+
return (
83+
<div className={styles.viewport}>
84+
<PasswordStrength.Root value={password} onValueChange={setPassword} rules={rules}>
85+
<PasswordStrength.Input placeholder="Password" className={styles.input} data-1p-ignore />
86+
87+
<PasswordStrength.Progress className={styles.circle} asChild>
88+
<svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
89+
<PasswordStrength.Indicator index={0} className={styles.circleSegment} asChild>
90+
<path d="M63.9847 31C63.4666 14.1217 49.8783 0.533422 33 0.0153198V10.0223C44.3547 10.5303 53.4697 19.6453 53.9777 31H63.9847Z" />
91+
</PasswordStrength.Indicator>
92+
<PasswordStrength.Indicator index={1} className={styles.circleSegment} asChild>
93+
<path d="M53.9777 33C53.4697 44.3547 44.3547 53.4697 33 53.9777V63.9847C49.8783 63.4666 63.4666 49.8783 63.9847 33H53.9777Z" />
94+
</PasswordStrength.Indicator>
95+
<PasswordStrength.Indicator index={2} className={styles.circleSegment} asChild>
96+
<path d="M0.0153198 33C0.533424 49.8783 14.1217 63.4666 31 63.9847V53.9777C19.6453 53.4697 10.5303 44.3547 10.0223 33H0.0153198Z" />
97+
</PasswordStrength.Indicator>
98+
<PasswordStrength.Indicator index={3} className={styles.circleSegment} asChild>
99+
<path d="M0.0153198 31C0.53342 14.1217 14.1217 0.533422 31 0.0153198V10.0223C19.6453 10.5303 10.5303 19.6453 10.0223 31H0.0153198Z" />
100+
</PasswordStrength.Indicator>
101+
</svg>
102+
</PasswordStrength.Progress>
103+
104+
<PasswordStrength.Rules>
105+
{({ rules }) => (
106+
<ul className={styles.rules}>
107+
{rules.map(({ label, isValid }) => (
108+
<li
109+
className={styles.rule}
110+
key={label}
111+
style={{ color: isValid ? '#222' : undefined }}
112+
>
113+
<CheckIcon style={{ color: isValid ? '#30a46c' : '#999' }} />
114+
{label}
115+
</li>
116+
))}
117+
</ul>
118+
)}
119+
</PasswordStrength.Rules>
120+
</PasswordStrength.Root>
121+
</div>
122+
);
123+
};
124+
125+
function CheckIcon({ className, style }: { className?: string; style?: React.CSSProperties }) {
126+
return (
127+
<svg
128+
xmlns="http://www.w3.org/2000/svg"
129+
viewBox="0 0 24 24"
130+
height="12"
131+
width="12"
132+
fill="none"
133+
stroke="currentColor"
134+
strokeWidth="2"
135+
strokeLinecap="round"
136+
strokeLinejoin="round"
137+
style={style}
138+
className={className}
139+
>
140+
<polyline points="20 6 9 17 4 12" />
141+
</svg>
142+
);
143+
}

0 commit comments

Comments
 (0)