diff --git a/pages/page7/src/index.tsx b/pages/page7/src/index.tsx index 8caa3c8..3aff666 100644 --- a/pages/page7/src/index.tsx +++ b/pages/page7/src/index.tsx @@ -89,8 +89,8 @@ const LoginPage: FC>> = ( + {children} - {children} ); }; diff --git a/pages/page8/README.md b/pages/page8/README.md new file mode 100644 index 0000000..fd8df41 --- /dev/null +++ b/pages/page8/README.md @@ -0,0 +1,366 @@ +# @react-login-page/page8 + +[![npm version](https://img.shields.io/npm/v/@react-login-page/page8.svg)](https://www.npmjs.com/package/@react-login-page/page8) +[![Downloads](https://img.shields.io/npm/dm/@react-login-page/page8.svg?style=flat)](https://www.npmjs.com/package/@react-login-page/page8) + + + +login-page + + + + +## Install + +```bash +$ npm install @react-login-page/page8 --save +``` + +## Usage + +```jsx +import React from 'react'; +import Login from '@react-login-page/page8'; + +const Demo = () => ; + +export default Demo; +``` + +## Modify Controls + +```jsx mdx:preview +import React from 'react'; +import LoginPage, { Username, Password, Submit, Title, Logo } from '@react-login-page/page8'; +import LoginLogo from 'react-login-page/logo'; + +const styles = { height: 690 }; + +const Demo = () => ( +
+ + + <Logo> + <LoginLogo /> + </Logo> + <Username label="用户名" placeholder="请输入用户名" name="userUserName" /> + <Password label="密码" placeholder="请输入密码" name="userPassword" /> + <Submit keyname="submit">提交</Submit> + <Submit keyname="reset">重置</Submit> + + <Username panel="signup" label="邮箱" placeholder="E-mail" keyname="e-mail" /> + <Password panel="signup" label="密码" placeholder="请输入密码" keyname="password" /> + <Password panel="signup" label="确认密码" placeholder="请输入确认密码" keyname="confirm-password" /> + <Submit panel="signup" keyname="signup-submit"> + 注册 + </Submit> + <Submit panel="signup" keyname="signup-reset"> + 重置 + </Submit> + </LoginPage> + </div> +); + +export default Demo; +``` + +## Hide Controls + +Use `visible={false}` to hide controls. + +```jsx mdx:preview +import React from 'react'; +import LoginPage, { Reset, Logo, Password, Footer } from '@react-login-page/page8'; +import LoginLogo from 'react-login-page/logo-rect'; + +const Demo = () => ( + <LoginPage style={{ height: 690 }}> + <Logo> + <LoginLogo /> + </Logo> + <Password panel="signup" visible={false} keyname="confirm-password" /> + <Password visible={false} /> + <Footer> + Not a member? <a href="#">Sign up now</a> + </Footer> + </LoginPage> +); + +export default Demo; +``` + +## Add Controls + +```jsx mdx:preview +import React from 'react'; +import LoginPage, { Reset, Logo, Footer, Username, Password, Input } from '@react-login-page/page8'; +import LoginLogo from 'react-login-page/logo-rect'; + +const imgSrc = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYAAADDhn8LAAAMuElEQVR42u2d+XMUxxXH/Se4KpVK4h9SqUpSyU+plA/iOITYFRKnSEjZsQuDDARsHBMC2OG+rxCEQQYjc1tYRsgSiFMcEuKSkJDQgUCAECAJdiWZFatbQrd2t6M3zordUb+enp3Z2UPvW/UKSprpbpv3menX/V7PM4xEIqF6hv4XkEgECIlEgJBIBAiJRICQSAQIiUSAkEgECIlEgJBIBAiJRCJASCQChEQiQEgkAoREIkBIJAKERCJASCQChEQiQEgkXF9vuTzMCBASgSGAIdpAIUBIuuEw8zoChDTi4IgmSAgQUlCdPdIhIUBIQXfySIaEACFZ4uCRCgkBQrLEsQkQEgFCgJAIkOiE5MahxVwjQEiWOXQ4AuIFgd4gJAKEAwdNsUhh5dDhAokMHAQIyXJHJkBIBEiYAyILBwGiIWeDkx1NP8qWrV3OJk6bxH4zdjQb9eqvFIO/T5j6Dlu6eilLPZzKHPUOAiQCANEDhx8gn/+xls1/tpJrR+c5gz7wqtxutP/4sbWG2n5+9Auo8VRZXcnmLZnHXhzzkvBetc1ZMEe5N1gaPWsFauHc1+WPdrOMd2KHGfzcqG7vzeS27bXyhLPmAFKa1oE66JLvV7GBXk9QAdk/1YH2X5TUbgkgfX19bENcrC4oeLYrYTcB4uvEezJQB+5p7jA0zsv/3iMERA1hwIB43Iyt/ukD1EmvJrYFDY4njS628Dv8fpc+V20YThlAmpublWmUUTi8Nn/ZAgLk/3pcdA91YMeVOwGPsbf1iRAONYR64RgWg2Sub0IB2fb72qABcnFLM9rv8UUNhtvXAqSxqZGNe+vPpsHhtY1bPiFABjXQ3Ys6b1l8esBjdBRUSAHihdAwIK3fDKBPcrBHt3qDAsiGX9qC2qfIiXt6ekx9c6gtvzB/xAMCyl+ayHXeCx9sC3iMAJcMIF4IDQMC+jLmUVCf5lYG5zKArFq/ivvz8RP+qsQSufm5rKu7a6gtl8vFbt+5zZJSktgbE9/UBASuIUAYq9h/AXXgJ3WNgc08PoyXAsQLoV5AOh81DQdE5LAQD7j6zA3Wk2fUBz3u0fPEHzv+D+z02TNS7brdbgUirTYzz58d8YA4S6tQB67JKtXdXqejWQoOXwj1AgLj4u6DbHzJHrQVJdng3MyVM1k43n1/shKs61XasTRhux/OnRlVgKSOm67bPC43y5jId97rW47pHl/thRvctnLn7UUh1AsIjIsLSO7O1qBPe0DZ8S2WTOdk4Hgz5m+svSNw+GHVCmsb9lM6OztH9BsEIDn59kqu82ZN2ay7PXBeXlsYOHC9HkA8Hg/LmhrHB6S3080Wf68Kdd76ij5T/hFiX7BZsiCgBQfsjNtrawz1YbPbhH1czLk4ogGBHfSqw3noFKj1/je62js37dPhoA06NOjCjM+4EOoBpLXqkXIfmmoCu+eY855YYvzp/rCg27IlZS1Atu/ZYUo/7816H+0jflf8iAek+U4NCkjV0SvSbbVVO7ht3Nh24ttgfPBPDEJZSB6kXxUD4ijvRR14+Q+NB+sp/6i3bFNS6+3R0tpiSj8J+/cFdePQCkC8MUMwAIFpS2bMJ1znLVqfKv9wPV3EjzPOXVd+X5d9E4VQFpDiDQfFgIBE+VnFyYHP17uaXWzRd6ssS2sRATJ34Uem9XOl4Araz6TpMWEPCIARjL58ExRLNx/hOu+ZCRv8+hepJPYQtw1YllX8y9mKQigDCCwoeEEWAiLKz9r+p7qA/yFEiwDHFpi/1yICBFagzBLsyGP9jHn9d2ENiNo5gwUI9vQHa7herQkJ9ha69M/tftflzNk17Bq4D5xfC5KmW7ahe4SAaOVnOSsDC9Y3jbJbulsvAqSwpMi0fmATEevn5dd+HbaA8JzSrL7U6e3ttscoIHeTL6HjGXLecjv33ps7Tvldd3PnaT6EZQ++jVMEkNxPzZEDBCTKz0pfpv9pbyvEg/Otr9awYEgECGTwWtEXLPWONECw2g/eChRY/vKvNAGpTMvl51sVVPjH0Eie1r2vszUBubr6gDwgovysFT+qZu4BffHCwVmPLc8Y1lsPEq59RRIgosIobJUpc9JGJbFRBEnhmmTuvZDZ66v+zh4hhF5I1KC4evuVcUgDAhLlZ107KJ/P393qtjQ4J0ACg0OrL6PfB8E288AgNR4blxI8+zjv0O75/C+4/eQt2ieE0BcUrx4X3/e7XgoQUX7WjnHywbrVwTkBEhgcsn0F+oUpUR4VVAhi41M7r9fu7Mvi9lPx1XkhhGpIwHKWrdEPCEiUn9VQJTeP3/yy3fJUegIkeIAY0cWZn2tWAKrHhzr8IDg8Oa9VSpXh+r11FiYEBojo6X9qlXa6sr24x/LgnAAJX0DKtp/UrABUjw+bMrn7B/D9DM6UDKuF72vvGnatNCCi/KxVP36gLAmLlDbXGZJyXgIkPAERVQM+yisfNj4s6L66MknYDxbU82rheWPSdeyPKD/r+pEOIVyhCM4JkPAFpLetU7MC0Hd8GFD3D4njHnRZmFMLz9s70QWIKD9r51/wYP3KXnx6dvjj4B8pRICYu4pllrDaDagAVI/r1u4z3Gth11skLEGSVwufPXunMUBAovysxgf93Hs+HV0TkuCcAAlvQGD1SbYMF00d8YhnH1hqiroWvqu+hTsO3YCI8rMy1jUNu76mBA/Ot4ypYVaIANEHSU9fvyWAiI4Dsp+99nQ8Te3cayBpUcpnkQRJb3IjCMsA1g2IKD8Lfq4O1kXBecGXbQSIDr02dw3qtG6PxzRAblTaLAHE1dPHXWVSl+FizvvwVKFUP1rp8cp/M7K7H9DZvKL8rLLjT7f8+7s96MqXFcF5tAHy+vz1qNM6W9pMe4sczy22rDgLUj+wMlzv9AlzXiickhGWIOkL4fn3tpoHiCg/a/cbT0sn4Q0RyuA82gD5YNNu1Gnzb98z3L63WGrxrmTLAIHkQa0yXIgXsPJaWfEA8NbCd9Q40TEEfLq7KD+rpXZAMzivu9FLgOjUsj0pqNMmZmSbMv7WJ53stwI4zAYEakBEZbgdtQ3C8lpZicpw7Zkl5gMiys+CKRgAEOrgPNoA2ZN+HnXamXF7TRn/FycvCOEIRnkvWoa7LkUJ1rHTS/So7lIZt53q4wWsNO4oP7t4cFyGvg+C5Wet/flDZQoV6uA82gDJLasQOq79sbGET7hftBBgZvWib8wDIGAOKrMCJSNsGRf6Pjt5E/o7Q4CI8rNEB8LBzjoBol+w/Dpm9irUcSF2CFQdXd0sZu1nmnAY2bVXgzE0GxmcSuk5JTH7X4GdQsPbCBQZjMsQIFrnZ4U6OI82QEALdiQJnfdITqHuNps7nrDpsTuk4Aj0ZEWRWu7W6nJcdXmtrLAyXMxgXIY/wSbKz+JZbWkPAWJAeTfvajpwwumL0vsiZwpK2fjFsdJwBOMMLuUUwymbpR1XXV4rK9nPJfguMxsGRJSfpTZY1QqFogkQ0JT/xGs68cQ1W1lyVi67Y6tjLrfbbypVUH5fCcbfXhnHvTf2wDFLAQFhgTLP+ju6A+oD7pPtA8YDMuUjnqL8rFAG52Y4bV7JTxQLJ0DKquy6nvh6bNp/t7O+/gHLAREttcqU10q/gVUFUZjZzhSbB4goP8trEKtYHZxHKyCgXSeyTIfjrRWblXgEZDUgos06v/LaxHOG+sGqEtUG4zENEK3zs0IVnEczIKD1+4+YBgcE6U1tT2t6rAYExDt0Wra8VlZYXbuvwa67V6Z9J12UnxWq4NwsQHz/DCdAQPszc4RLvzIWl3pSmVb5KhSAYLvdfuW1Ay5jD3OkDBfbpTcNkPKMzrALzo06rS8U4QoIyOZwsuV7U3WDsnDnAVZZx0/4CwUgWNbuUHnt6gOm9ON7MBzPYNfddEAOzcYPhMtPaGORqEAACaUgfkjPK2FrE9OUYHvsx+uUnXEw+Dv8DH4H2boNre2MpC1TANGqOYffx4x61s8IENKIAUSU1g4FUzwgIgESAoRkCiCQnYsB8vdfvILeF+6QECAkw4DA6hQGR9wrNUIIIgkQgoQACUiJkx0oIHDcjxYE4QoJDwYChADRJVEe1tLnqtm7L/5Asw0ChBS1gMDXaDFATq5olHb+cISEACEZAkSU5g5LvlOe/5l0WwQIKeIBgXyrNscAK0lpFx7G4P00mx6nJ0BIEQeIniIo9WfZ4DPPBAiJAOFYUVK7bqcnQEgjApCkaY6AHT7cICFASKYCAgfJeb94S4CQCBCfA6vV2boECCnqAYESWZ5Bdi6kkCTPqFdKbV19HlOcnQAhRRQgRkSAkAgQAoREgBAgBAgBQoAQICQChAAhESAECIkAIUBIBAgBQgoD/Q8QZpUThrhrWAAAAABJRU5ErkJggg=='; + +const Demo = () => { + const [data, setData] = React.useState({}); + const handle = (even) => { + even.preventDefault(); + const formData = new FormData(even.target); + const data = Object.fromEntries(formData); + setData({ ...data }); + }; + return ( + <form onSubmit={handle}> + <LoginPage style={{ height: 990 }}> + <Logo> + <LoginLogo /> + </Logo> + <Username keyname="subtitle" visible={false} index={0}> + 欢迎登录页面 + </Username> + {/* hide default username field */} + <Username visible={false} /> + <Username keyname="fields" index={3} label="字段" placeholder="修改了 name 字段" /> + <Username keyname="code" index={2} label="验证码"> + <img src={imgSrc} height={38} /> + </Username> + <Username keyname="username_rule" visible={false} index={4}> + 用户名规则 + </Username> + <Password index={1} /> + <Input name="phone" index={2} placeholder="Phone number"> + <img src={imgSrc} height={38} /> + </Input> + <Footer> + Not a member? <a href="#">Sign up now</a> + </Footer> + </LoginPage> + <pre>{JSON.stringify(data, null, 2)}</pre> + </form> + ); +}; + +export default Demo; +``` + +## Modify default control `name` + +Modify the string that specifies the **`name`** of the input control by default + +1. remove default `name=username` controls + +```jsx +<Username visible={false} /> +``` + +2. add `name=user` controls + +```jsx +<Username keyname="user" index={3} placeholder="修改了 name 字段" /> +``` + +## Modify Color Style + +```jsx mdx:preview +import React from 'react'; +import Login from '@react-login-page/page8'; + +const css = { + '--login-bg': '#5941a2', + '--login-color': '#fff', + '--login-label': '#a1b4b4', + '--login-tab': '#999', + '--login-input': '#3b4465', + '--login-input-bg': '#eef9fe', + '--login-input-placeholder': '#cecece', + '--login-input-placeholder-active': '#53e3a6', + '--login-input-border': '#cddbef', + '--login-input-bg-hover': 'rgba(255, 255, 255, 0.4)', + '--login-btn': '#fff', + '--login-btn-bg': '#5941a2', + '--login-btn-focus': 'white', + '--login-btn-hover': '#9579e7', + '--login-btn-active': '#6544ca', + '--login-footer': '#ffffff99', + '--login-animation-start': '#e2d7f1', + '--login-animation-end': '#fff', +}; + +const Demo = () => <Login style={{ height: 690, ...css }} />; + +export default Demo; +``` + +Use css variables to override default color values + +```css +.login-page7 { + --login-bg: #3b4465; + --login-color: #fff; + --login-label: #a1b4b4; + --login-tab: #999; + --login-input: #3b4465; + --login-input-bg: #eef9fe; + --login-input-placeholder: #cecece; + --login-input-placeholder-active: #53e3a6; + --login-input-border: #cddbef; + --login-input-bg-hover: rgba(255, 255, 255, 0.4); + --login-btn: #fff; + --login-btn-bg: #a7e245; + --login-btn-focus: white; + --login-btn-hover: #53e3a6; + --login-btn-active: #1aa97d; + --login-footer: #ffffff99; + --login-animation-start: #d7e7f1; + --login-animation-end: #fff; +} +``` + +Custom CSS style overrides + +```css +.login-page7 section button:focus { + box-shadow: 0 0 0 2px rgba(0, 142, 240, 0.26); +} +.login-page7 section button:hover { + background-color: #0070bd; +} +.login-page7 section button:active { + background-color: #00528a; +} +``` + +## Light & Dark Theme + +```css +[data-color-mode*='dark'] .login-page8, +.login-page8 { + --login-bg: #3b4465; + --login-color: #fff; + --login-label: #a1b4b4; + --login-tab: #999; + --login-input: #3b4465; + --login-input-bg: #eef9fe; + --login-input-placeholder: #cecece; + --login-input-placeholder-active: #53e3a6; + --login-input-border: #cddbef; + --login-input-bg-hover: rgba(255, 255, 255, 0.4); + --login-btn: #fff; + --login-btn-bg: #a7e245; + --login-btn-focus: white; + --login-btn-hover: #53e3a6; + --login-btn-active: #1aa97d; + --login-footer: #ffffff99; + --login-animation-start: #d7e7f1; + --login-animation-end: #fff; +} +[data-color-mode*='light'] .login-page8 { + --login-bg: #3b4465; + --login-color: #fff; + --login-label: #a1b4b4; + --login-tab: #999; + --login-input: #3b4465; + --login-input-bg: #eef9fe; + --login-input-placeholder: #cecece; + --login-input-placeholder-active: #53e3a6; + --login-input-border: #cddbef; + --login-input-bg-hover: rgba(255, 255, 255, 0.4); + --login-btn: #fff; + --login-btn-bg: #a7e245; + --login-btn-focus: white; + --login-btn-hover: #53e3a6; + --login-btn-active: #1aa97d; + --login-footer: #ffffff99; + --login-animation-start: #d7e7f1; + --login-animation-end: #fff; +} +``` + +## API + +Components be provided to modify control properties and perform other related functions. + +```jsx +import LoginPage from '@react-login-page/page8'; +// buttons +import { Reset, Submit } from '@react-login-page/page8'; +// blocks +import { Logo, Title, TitleLogin, TitleSignup, Footer } from '@react-login-page/page8'; +// fields +import { Username, Password } from '@react-login-page/page8'; +// Basic Components +import { Button, Input } from '@react-login-page/page8'; +// or +import { Button, Input } from 'react-login-page'; + +<LoginPage> + <Password index={2} /> +</LoginPage> + +<Input name="phone" index={1} placeholder="Phone number"> + <img src="..." height={38} /> +</Input> + +// Define the order of `Password` controls +<Password index={2} /> + +// Hiding the `Password` control +<Password visible={false} /> + +// Add footer content +<Footer> + Not a member? <a href="#">Sign up now</a> +</Footer> + +// Modify logo image +<Logo>⚛️</Logo> + +// Signup Fields +<Username panel="signup" label="E-mail" type="email" placeholder="E-mail" keyname="e-mail" /> +<Password panel="signup" label="Password" placeholder="Password" keyname="password" /> +<Password panel="signup" label="Confirm Password" placeholder="Confirm Password" keyname="confirm-password" /> +<Submit keyname="signup-submit" panel="signup">Signup</Submit> + +``` + +Use [dot notation](https://legacy.reactjs.org/docs/jsx-in-depth.html#using-dot-notation-for-jsx-type) components. + +```jsx +import Login from '@react-login-page/page8'; + +<Login> + <Login.Password index={2} /> +</Login> + +// Define the order of `Password` controls +<Login.Password index={2} /> + +// Hiding the `Password` control +<Login.Password visible={false} /> + +// Add footer content +<Login.Footer> + Not a member? <a href="#">Sign up now</a> +</Login.Footer> + +// Modify logo image +<Login.Logo>⚛️</Login.Logo> + +// Signup Fields +<Login.Username panel="signup" label="E-mail" type="email" placeholder="E-mail" keyname="e-mail" /> +<Login.Password panel="signup" label="Password" placeholder="Password" keyname="password" /> +<Login.Password panel="signup" label="Confirm Password" placeholder="Confirm Password" keyname="confirm-password" /> +<Login.Submit keyname="signup-submit" panel="signup">Signup</Login.Submit> +``` + +## Contributors + +As always, thanks to our amazing contributors! + +<a href="https://github.com/uiwjs/react-login-page/graphs/contributors"> + <img src="https://uiwjs.github.io/react-login-page/CONTRIBUTORS.svg" /> +</a> + +Made with [contributors](https://github.com/jaywcjlove/github-action-contributors). + +## License + +Licensed under the MIT License. diff --git a/pages/page8/package.json b/pages/page8/package.json new file mode 100644 index 0000000..20e8ea0 --- /dev/null +++ b/pages/page8/package.json @@ -0,0 +1,52 @@ +{ + "name": "@react-login-page/page8", + "version": "0.4.5", + "description": "Some `react` login pages, which can be used quickly after installation.", + "homepage": "https://uiwjs.github.io/react-login-page", + "author": "kenny wong <wowohoo@qq.com>", + "license": "MIT", + "main": "./cjs/index.js", + "module": "./esm/index.js", + "scripts": { + "watch": "tsbb watch src/*.tsx --use-babel", + "build": "tsbb build src/*.tsx --use-babel", + "test": "tsbb test --env=jsdom", + "coverage": "tsbb test --env=jsdom --coverage --bail" + }, + "repository": { + "type": "git", + "url": "https://github.com/uiwjs/react-login-page.git" + }, + "files": [ + "README.md", + "src", + "esm", + "cjs" + ], + "peerDependencies": { + "@babel/runtime": ">=7.11.0", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + }, + "dependencies": { + "react-login-page": "0.4.5" + }, + "keywords": [ + "react", + "login", + "login-page", + "login-pages", + "react-login", + "react-login-page", + "editor", + "syntax", + "ide", + "code" + ], + "jest": { + "coverageReporters": [ + "lcov", + "json-summary" + ] + } +} diff --git a/pages/page8/src/control/Footer.tsx b/pages/page8/src/control/Footer.tsx new file mode 100644 index 0000000..5770d54 --- /dev/null +++ b/pages/page8/src/control/Footer.tsx @@ -0,0 +1,12 @@ +import { Block, BlockProps, BlockTagType } from 'react-login-page'; + +export interface FooterProps<Tag extends BlockTagType> extends BlockProps<Tag> { + panel?: 'login' | 'signup'; +} + +export const Footer = <Tag extends BlockTagType = 'footer'>(props: FooterProps<Tag | 'footer'>) => { + const { keyname = 'footer', name, panel = 'login', ...elmProps } = props; + return <Block {...elmProps} keyname={name || keyname} tagName="footer" />; +}; + +Footer.displayName = 'Login.Footer'; diff --git a/pages/page8/src/control/Logo.tsx b/pages/page8/src/control/Logo.tsx new file mode 100644 index 0000000..29acfc0 --- /dev/null +++ b/pages/page8/src/control/Logo.tsx @@ -0,0 +1,13 @@ +import { PropsWithChildren } from 'react'; +import { Block, BlockProps, BlockTagType } from 'react-login-page'; + +export const Logo = <T extends BlockTagType = 'div'>(props: PropsWithChildren<Partial<BlockProps<T | 'div'>>>) => { + const { keyname = 'logo', name, ...elmProps } = props; + if (!elmProps.children) { + elmProps.children = '⚛️'; + } + const nameBase = name || keyname; + return <Block {...elmProps} keyname={nameBase} className={`login-page8-logo ${elmProps.className || ''}`} />; +}; + +Logo.displayName = 'Login.Logo'; diff --git a/pages/page8/src/control/Title.tsx b/pages/page8/src/control/Title.tsx new file mode 100644 index 0000000..9bf44db --- /dev/null +++ b/pages/page8/src/control/Title.tsx @@ -0,0 +1,13 @@ +import { PropsWithChildren } from 'react'; +import { Block, BlockProps, BlockTagType } from 'react-login-page'; + +export const Title = <T extends BlockTagType>(props: PropsWithChildren<Partial<BlockProps<T>>>) => { + const { keyname = 'title', name, ...elmProps } = props; + if (!elmProps.children) { + elmProps.children = 'Login'; + } + const nameBase = name || keyname; + return <Block {...elmProps} keyname={nameBase} />; +}; + +Title.displayName = 'Login.Title'; diff --git a/pages/page8/src/control/TitleLogin.tsx b/pages/page8/src/control/TitleLogin.tsx new file mode 100644 index 0000000..ad4affc --- /dev/null +++ b/pages/page8/src/control/TitleLogin.tsx @@ -0,0 +1,13 @@ +import { PropsWithChildren } from 'react'; +import { Block, BlockProps, BlockTagType } from 'react-login-page'; + +export const TitleLogin = <T extends BlockTagType>(props: PropsWithChildren<Partial<BlockProps<T>>>) => { + const { keyname = 'title-login', name, ...elmProps } = props; + if (!elmProps.children) { + elmProps.children = 'Login'; + } + const nameBase = name || keyname; + return <Block {...elmProps} keyname={nameBase} />; +}; + +TitleLogin.displayName = 'Login.TitleLogin'; diff --git a/pages/page8/src/control/TitleSignup.tsx b/pages/page8/src/control/TitleSignup.tsx new file mode 100644 index 0000000..0ec051e --- /dev/null +++ b/pages/page8/src/control/TitleSignup.tsx @@ -0,0 +1,13 @@ +import { PropsWithChildren } from 'react'; +import { Block, BlockProps, BlockTagType } from 'react-login-page'; + +export const TitleSignup = <T extends BlockTagType>(props: PropsWithChildren<Partial<BlockProps<T>>>) => { + const { keyname = 'title-signup', name, ...elmProps } = props; + if (!elmProps.children) { + elmProps.children = 'Signup'; + } + const nameBase = name || keyname; + return <Block {...elmProps} keyname={nameBase} />; +}; + +TitleSignup.displayName = 'Login.TitleSignup'; diff --git a/pages/page8/src/control/login/Password.tsx b/pages/page8/src/control/login/Password.tsx new file mode 100644 index 0000000..0ee5532 --- /dev/null +++ b/pages/page8/src/control/login/Password.tsx @@ -0,0 +1,23 @@ +import { FC, useEffect, memo } from 'react'; +import { Input, InputProps, useStore } from 'react-login-page'; + +export interface PasswordProps extends InputProps { + label?: React.ReactNode; + panel?: 'login' | 'signup'; +} +export const Password: FC<PasswordProps> = memo((props) => { + const { keyname = 'password', name, rename, panel = 'login', label = 'Password', ...elmProps } = props; + const { dispatch } = useStore(); + const panelName = panel; + + const key = (keyname || name) as string; + + useEffect(() => dispatch({ [`$$${panelName}${key}`]: label }), [label]); + const nameBase = name || rename || keyname; + + return ( + <Input type="password" placeholder="Password" {...elmProps} name={nameBase} keyname={`$$${panelName}${key}`} /> + ); +}); + +Password.displayName = 'Login.Password'; diff --git a/pages/page8/src/control/login/Submit.tsx b/pages/page8/src/control/login/Submit.tsx new file mode 100644 index 0000000..06fd3ee --- /dev/null +++ b/pages/page8/src/control/login/Submit.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { Button, ButtonProps } from 'react-login-page'; + +export interface Submit extends ButtonProps { + panel?: 'login' | 'signup'; +} + +export const Submit: FC<Submit> = (props) => { + const { keyname = 'submit', panel = 'login', ...elmProps } = props; + const panelName = panel; + if (!elmProps.children) { + elmProps.children = 'Submit'; + } + return <Button type="submit" {...elmProps} keyname={`$$${panelName}${keyname}`} />; +}; + +Submit.displayName = 'Login.Submit'; diff --git a/pages/page8/src/control/login/Username.tsx b/pages/page8/src/control/login/Username.tsx new file mode 100644 index 0000000..b9fc90a --- /dev/null +++ b/pages/page8/src/control/login/Username.tsx @@ -0,0 +1,24 @@ +import { FC, useEffect, memo } from 'react'; +import { Input, InputProps, useStore } from 'react-login-page'; + +export interface UsernameProps extends InputProps { + label?: React.ReactNode; + panel?: 'login' | 'signup'; +} +export const Username: FC<UsernameProps> = memo((props) => { + const { keyname = 'username', name, rename, panel = 'login', label = 'Username', ...elmProps } = props; + const { dispatch } = useStore(); + const panelName = panel; + + const key = (keyname || name) as string; + + useEffect(() => dispatch({ [`$$${panelName}${key}`]: label }), [label]); + + const nameBase = name || rename || keyname; + + return ( + <Input placeholder="Username" spellCheck={false} {...elmProps} name={nameBase} keyname={`$$${panelName}${key}`} /> + ); +}); + +Username.displayName = 'Login.Username'; diff --git a/pages/page8/src/control/signup/Password.tsx b/pages/page8/src/control/signup/Password.tsx new file mode 100644 index 0000000..350b055 --- /dev/null +++ b/pages/page8/src/control/signup/Password.tsx @@ -0,0 +1,21 @@ +import { FC, useEffect, memo } from 'react'; +import { Input, InputProps, useStore } from 'react-login-page'; + +export interface SignupPasswordProps extends InputProps { + label?: React.ReactNode; +} +export const SignupPassword: FC<SignupPasswordProps> = memo((props) => { + const { keyname = 'password', name, rename, label = '', ...elmProps } = props; + const { dispatch } = useStore(); + const panelName = 'signup'; + const key = (keyname || name) as string; + + useEffect(() => dispatch({ [`$$${panelName}${key}`]: label }), [label]); + const nameBase = name || rename || keyname; + + return ( + <Input type="password" placeholder="Password" {...elmProps} name={nameBase} keyname={`$$${panelName}${key}`} /> + ); +}); + +SignupPassword.displayName = 'Login.SignupPassword'; diff --git a/pages/page8/src/control/signup/Submit.tsx b/pages/page8/src/control/signup/Submit.tsx new file mode 100644 index 0000000..2af84f4 --- /dev/null +++ b/pages/page8/src/control/signup/Submit.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react'; +import { Button, ButtonProps } from 'react-login-page'; + +export const SignupSubmit: FC<ButtonProps> = (props) => { + const { keyname = 'submit', ...elmProps } = props; + const panelName = 'signup'; + if (!elmProps.children) { + elmProps.children = 'Signup'; + } + return <Button type="submit" {...elmProps} keyname={`$$${panelName}${keyname}`} />; +}; + +SignupSubmit.displayName = 'Login.SignupSubmit'; diff --git a/pages/page8/src/index.css b/pages/page8/src/index.css new file mode 100644 index 0000000..962bd4a --- /dev/null +++ b/pages/page8/src/index.css @@ -0,0 +1,380 @@ +[data-color-mode*='dark'] .login-page8, .login-page8 { + --login-bg: #3b4465; + --login-color: #fff; + --login-label: #a1b4b4; + --login-tab: #999; + --login-input: #3b4465; + --login-input-bg: #eef9fe; + --login-input-placeholder: #cecece; + --login-input-placeholder-active: #53e3a6; + --login-input-border: #cddbef; + --login-input-bg-hover: rgba(255, 255, 255, 0.4); + --login-btn: #fff; + --login-btn-bg: #a7e245; + --login-btn-focus: white; + --login-btn-hover: #53e3a6; + --login-btn-active: #1aa97d; + --login-footer: #ffffff99; + --login-animation-start: #d7e7f1; + --login-animation-end: #fff; +} +[data-color-mode*='light'] .login-page8 { + --login-bg: #3b4465; + --login-color: #fff; + --login-label: #a1b4b4; + --login-tab: #999; + --login-input: #3b4465; + --login-input-bg: #eef9fe; + --login-input-placeholder: #cecece; + --login-input-placeholder-active: #53e3a6; + --login-input-border: #cddbef; + --login-input-bg-hover: rgba(255, 255, 255, 0.4); + --login-btn: #fff; + --login-btn-bg: #a7e245; + --login-btn-focus: white; + --login-btn-hover: #53e3a6; + --login-btn-active: #1aa97d; + --login-footer: #ffffff99; + --login-animation-start: #d7e7f1; + --login-animation-end: #fff; +} + +.login-page8 { + --gap: 15px; + --login-line-height: 1.2; + line-height: var(--login-line-height); + color: var(--login-color); + background-color: var(--login-bg); + height: auto; + min-height: 100%; + display: flex; + font-size: 16px; + justify-content: center; + align-items: center; + flex-direction: column; + position: relative; + box-sizing: border-box; +} +.login-page8 header { + display: flex; + gap: var(--gap); + padding-bottom: 1rem; + + font-size: 32px; + font-weight: 500; + line-height: 42px; + text-align: center; + justify-content: center; +} +.login-page8 header > div { + display: flex; + align-items: center; +} +.login-page8 header > div > * { + display: block; +} + +.login-page8-inner > main { + position: relative; + display: flex; + align-items: flex-start; + margin-top: 30px; +} + +.login-page8-inner main > article.active > button { + color: var(--login-btn); + transform: translateX(-90px); +} +.login-page8-inner main > article.active:first-child > button { + transform: translateX(90px); +} +.login-page8-inner main > article > button { + position: relative; + cursor: pointer; + display: block; + margin-right: auto; + margin-left: auto; + padding: 0; + text-transform: uppercase; + font-family: inherit; + font-size: 16px; + letter-spacing: .5px; + color: var(--login-tab); + background-color: transparent; + border: none; + outline: none; + transform: translateX(0); + transition: all .3s ease-out; +} + +.login-page8-inner main > article { + color: var(--login-input); + animation: hideLayer .3s ease-out forwards; +} + +.login-page8-inner main > article.active { + animation: showLayer .3s ease-in forwards; +} + +@keyframes showLayer { + 50% { + z-index: 1; + } + 100% { + z-index: 1; + } +} + +@keyframes hideLayer { + 0% { + z-index: 1; + } + 49.999% { + z-index: 1; + } +} + +.login-page8-inner main > article .login-page8-fields { + overflow: hidden; + min-width: 260px; + margin-top: 50px; + padding: 30px 25px; + border-radius: 5px; + transform-origin: top; +} + +.login-page8-login { + animation: hideLogin .3s ease-out forwards; +} + +.login-page8-inner main > article.active .login-page8-login { + animation: showLogin .3s ease-in forwards; +} + +.login-page8-signup { + animation: hideSignup .3s ease-out forwards; +} + +.login-page8-inner main > article.active .login-page8-signup { + animation: showSignup .3s ease-in forwards; +} + + +@keyframes showLogin { + 0% { + background: var(--login-animation-start); + transform: translate(40%, 10px); + } + 50% { + transform: translate(0, 0); + } + 100% { + background-color: var(--login-animation-end); + transform: translate(35%, -20px); + } +} + +@keyframes hideLogin { + 0% { + background-color: var(--login-animation-end); + transform: translate(35%, -20px); + } + 50% { + transform: translate(0, 0); + } + 100% { + background: var(--login-animation-start); + transform: translate(40%, 10px); + } +} + +@keyframes showSignup { + 0% { + background: var(--login-animation-start); + transform: translate(-40%, 10px) scaleY(.8); + } + 50% { + transform: translate(0, 0) scaleY(.8); + } + 100% { + background-color: var(--login-animation-end); + transform: translate(-35%, -20px) scaleY(1); + } +} + +@keyframes hideSignup { + 0% { + background-color: var(--login-animation-end); + transform: translate(-35%, -20px) scaleY(1); + } + 50% { + transform: translate(0, 0) scaleY(.8); + } + 100% { + background: var(--login-animation-start); + transform: translate(-40%, 10px) scaleY(.8); + } +} + +@keyframes showLogin { + 0% { + background: var(--login-animation-start); + transform: translate(40%, 10px); + } + 50% { + transform: translate(0, 0); + } + 100% { + background-color: var(--login-animation-end); + transform: translate(35%, -20px); + } +} + +@keyframes hideLogin { + 0% { + background-color: var(--login-animation-end); + transform: translate(35%, -20px); + } + 50% { + transform: translate(0, 0); + } + 100% { + background: var(--login-animation-start); + transform: translate(40%, 10px); + } +} + +.login-page8-inner > main > article:not(.active) .login-page8-fields > * { + opacity: 0; +} + +.login-page8-label { + margin-bottom: 6px; + position: absolute; + left: 0; + top: 2px; + color: var(--login-label); +} + +.login-page8-label + input, .login-page8-label + input + * { + margin-top: 24px !important; +} + +@keyframes showLayer { + 50% { + z-index: 1; + } + 100% { + z-index: 1; + } +} + +@keyframes hideLayer { + 0% { + z-index: 1; + } + 49.999% { + z-index: 1; + } +} + +.login-page8-fields { + border-radius: 10px; + margin: 2rem 0; + gap: var(--gap); + display: flex; + flex-direction: column; +} +.login-page8-fields > label { + gap: var(--gap); + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + position: relative; +} +.login-page8-fields > label > input { + display: block; + width: 100%; + margin-top: 8px; + padding-right: 15px; + padding-left: 15px; + font-size: 16px; + line-height: 40px; + color: var(--login-input) !important; + background: var(--login-input-bg); + border: 1px solid var(--login-input-border); + border-radius: 2px; +} +.login-page8-fields > label > input:focus { + background-color: var(--login-btn-focus); + color: var(--login-btn); + width: 100%; +} +.login-page8-fields > label > input:not(:focus):hover { + background-color: var(--login-input-bg-hover); +} +.login-page8-fields > label > input::placeholder { + color: var(--login-input-placeholder); +} +.login-page8-fields > label > input::placeholder:active { + color: var(--login-input-placeholder-active); +} + +.login-page8-fields > label > div + input { + margin-top: 5px; +} +.login-page8-fields > label > div > * { + display: block; +} + +.login-page8-fields > label > input:-webkit-autofill +.login-page8-fields > label > input:-webkit-autofill:hover, +.login-page8-fields > label > input:-webkit-autofill:focus +{ + -webkit-box-shadow: 0 0 0px 1000px transparent inset !important; + box-shadow: 0 0 0 1000px transparent inset; + -webkit-text-fill-color: transparent !important; +} + +.login-page8-button { + gap: var(--gap); + display: flex; +} + +.login-page8-button > button { + cursor: pointer; + min-width: 80px; + text-transform: uppercase; + border: 0; + transition: background-color 300ms; + position: relative; + z-index: 1; + width: 100%; + background-color: var(--login-btn-bg); + color: var(--login-btn); + padding: 7px 0; + font-size: 18px; + border-radius: 3px; +} + +.login-page8-button > button:hover:not(:disabled) { + background-color: var(--login-btn-hover); +} +.login-page8-button > button:active:not(:disabled) { + background-color: var(--login-btn-active); +} +.login-page8-button > button:focus:not(:disabled) { + box-shadow: 0 0 3px 0 var(--login-btn-focus); +} +.login-page8-inner > footer a { + color: var(--login-footer); +} +.login-page8-inner > footer { + color: var(--login-footer); + padding-bottom: 1rem; + position: relative; + text-align: center; + z-index: 2; +} + diff --git a/pages/page8/src/index.tsx b/pages/page8/src/index.tsx new file mode 100644 index 0000000..002de79 --- /dev/null +++ b/pages/page8/src/index.tsx @@ -0,0 +1,173 @@ +import { FC, PropsWithChildren, cloneElement, isValidElement, useState } from 'react'; +import { Render, Provider, Container, useStore } from 'react-login-page'; +import { Username } from './control/login/Username'; +import { Password } from './control/login/Password'; +import { Submit } from './control/login/Submit'; +import { Footer } from './control/Footer'; +import { Logo } from './control/Logo'; +import { Title } from './control/Title'; +import { TitleLogin } from './control/TitleLogin'; +import { TitleSignup } from './control/TitleSignup'; + +import './index.css'; + +export * from 'react-login-page'; +export * from './control/login/Username'; +export * from './control/login/Password'; +export * from './control/login/Submit'; +export * from './control/signup/Submit'; +export * from './control/signup/Password'; +export * from './control/Title'; +export * from './control/TitleLogin'; +export * from './control/TitleSignup'; +export * from './control/Logo'; +export * from './control/Footer'; + +const RenderLogin = () => { + const { blocks = {}, extra = {}, data, ...label } = useStore(); + const { fields, buttons } = data || { fields: [] }; + const [panel, setPanel] = useState<'login' | 'signup'>('login'); + const loginButtons = buttons.filter((m) => m.name.indexOf(`$$login`) > -1).sort((a, b) => a.index - b.index); + const loginFields = fields.filter((m) => m.name.indexOf(`$$login`) > -1).sort((a, b) => a.index - b.index); + const signupButtons = buttons.filter((m) => m.name.indexOf(`$$signup`) > -1).sort((a, b) => a.index - b.index); + const signupFields = fields.filter((m) => m.name.indexOf(`$$signup`) > -1).sort((a, b) => a.index - b.index); + console.log('loginFields', loginFields, label); + return ( + <Render> + <div className="login-page8-inner"> + <header> + {blocks.logo} + {blocks.title} + </header> + <main> + <article className={panel === 'login' ? 'active' : ''}> + <button onClick={() => setPanel('login')}>{blocks['title-login']}</button> + <section className="login-page8-fields login-page8-login"> + {loginFields.map((item, idx) => { + const extraLabel = extra[item.name as keyof typeof extra]; + if (!item.children && !extraLabel) return null; + if (!item.children && extraLabel) return <div key={idx}>{extraLabel}</div>; + if (!item.children) return null; + const labelElement = label[item.name]; + const htmlFor = item.name.replace('$$login', ''); + const { name, ...props } = item.children.props; + return ( + <label htmlFor={htmlFor} key={item.name + idx}> + {labelElement && <span className={`login-page8-label`}>{labelElement}</span>} + {cloneElement(item.children, { + ...props, + name: panel === 'login' ? name : '', + key: item.name + idx, + })} + {extraLabel && <div>{extraLabel}</div>} + </label> + ); + })} + <section className="login-page8-button"> + {loginButtons.map((item, idx) => { + const child = item.children; + if (!isValidElement(child)) return null; + return cloneElement(child, { + ...child.props, + key: item.name + idx, + }); + })} + </section> + </section> + </article> + <article className={panel === 'signup' ? 'active' : ''}> + <button onClick={() => setPanel('signup')}>{blocks['title-signup']}</button> + <section className="login-page8-fields login-page8-signup"> + {signupFields.map((item, idx) => { + const extraLabel = extra[item.name as keyof typeof extra]; + if (!item.children && !extraLabel) return null; + if (!item.children && extraLabel) return <div key={idx}>{extraLabel}</div>; + if (!item.children) return null; + const labelElement = label[item.name]; + const htmlFor = item.name.replace('$$signup', ''); + const { name, ...props } = item.children.props; + return ( + <label htmlFor={htmlFor} key={item.name + idx}> + {labelElement && <span className={`login-page8-label`}>{labelElement}</span>} + {cloneElement(item.children, { + ...props, + name: panel === 'signup' ? name : '', + key: item.name + idx, + })} + {extraLabel && <div>{extraLabel}</div>} + </label> + ); + })} + <section className="login-page8-button"> + {signupButtons.map((item, idx) => { + const child = item.children; + if (!isValidElement(child)) return null; + return cloneElement(child, { + ...child.props, + key: item.name + idx, + }); + })} + </section> + </section> + </article> + </main> + {blocks.footer} + </div> + </Render> + ); +}; + +const LoginPage: FC<PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>> = ({ + children, + className, + ...divProps +}) => { + return ( + <Provider> + <Title /> + <TitleSignup /> + <TitleLogin /> + <Logo /> + <Username /> + <Password /> + <Submit /> + + <Username panel="signup" label="E-mail" type="email" placeholder="E-mail" keyname="e-mail" /> + <Password panel="signup" label="Password" placeholder="Password" keyname="password" /> + <Password panel="signup" label="Confirm Password" placeholder="Confirm Password" keyname="confirm-password" /> + <Submit keyname="signup-submit" panel="signup"> + Signup + </Submit> + + <Container {...divProps} className={`login-page8 ${className || ''}`}> + <RenderLogin /> + {children} + </Container> + </Provider> + ); +}; + +type LoginComponent = FC<PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>> & { + Username: typeof Username; + Password: typeof Password; + Submit: typeof Submit; + Footer: typeof Footer; + Logo: typeof Logo; + Title: typeof Title; + TitleLogin: typeof TitleLogin; + TitleSignup: typeof TitleSignup; +}; + +const Login = LoginPage as LoginComponent; + +Login.Username = Username; +Login.Password = Password; +Login.Submit = Submit; +Login.Footer = Footer; +Login.Logo = Logo; +Login.Title = Title; +Login.TitleLogin = TitleLogin; +Login.TitleSignup = TitleSignup; +Login.displayName = 'BaseLoginPage'; + +export default Login; diff --git a/pages/page8/tsconfig.json b/pages/page8/tsconfig.json new file mode 100644 index 0000000..a23d645 --- /dev/null +++ b/pages/page8/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "include": ["src"], + "compilerOptions": { + "outDir": "cjs", + "baseUrl": "." + } +} diff --git a/website/package.json b/website/package.json index 3f52599..a98ef4e 100644 --- a/website/package.json +++ b/website/package.json @@ -29,6 +29,8 @@ "@react-login-page/page4": "0.4.5", "@react-login-page/page5": "0.4.5", "@react-login-page/page6": "0.4.5", + "@react-login-page/page7": "0.4.5", + "@react-login-page/page8": "0.4.5", "@uiw/copy-to-clipboard": "^1.0.14", "@uiw/react-back-to-top": "^1.1.2", "@uiw/react-markdown-preview": "^4.1.11", diff --git a/website/src/pages/Examples.tsx b/website/src/pages/Examples.tsx index 57020cd..5a5d799 100644 --- a/website/src/pages/Examples.tsx +++ b/website/src/pages/Examples.tsx @@ -9,6 +9,7 @@ import Login4 from '@react-login-page/page4'; import Login5 from '@react-login-page/page5'; import Login6 from '@react-login-page/page6'; import Login7 from '@react-login-page/page7'; +import Login8 from '@react-login-page/page8'; interface Example { magnify?: number; @@ -104,3 +105,14 @@ export const page7: Example = { </Login7> ), }; + +export const page8: Example = { + magnify: 2.2, + children: ( + <Login8> + <Login8.Logo> + <LoginLogo /> + </Login8.Logo> + </Login8> + ), +}; diff --git a/website/src/router.tsx b/website/src/router.tsx index bc80ac4..983f213 100644 --- a/website/src/router.tsx +++ b/website/src/router.tsx @@ -120,6 +120,14 @@ export const routes: MenuRouteObject = { </Preview> ), }, + { + path: 'page8', + element: ( + <Preview disableNav path={() => import('@react-login-page/page8/README.md')}> + {datas.page8.children} + </Preview> + ), + }, ], }, ],