Просмотр исходного кода

Merge pull request #8607 from weseek/support/142719-update-login-styles

support: New login design
Yuki Takei 2 лет назад
Родитель
Сommit
6d915e704f

+ 2 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -44,9 +44,11 @@
   "Error": "Error",
   "Warning": "Warning",
   "Sign in": "Sign in",
+  "Sign in with External auth": "Sign in with {{signin}}",
   "Sign up is here": "Sign up",
   "Sign in is here": "Sign in",
   "Sign up": "Sign up",
+  "or": "or",
   "Sign up with Google Account": "Sign up with Google Account",
   "Sign in with Google Account": "Sign in with Google Account",
   "Sign up with this Google Account": "Sign up with this Google Account",

+ 2 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -41,9 +41,11 @@
   "Error": "エラー",
   "Warning": "注意",
   "Sign in": "ログイン",
+  "Sign in with External auth": "{{signin}} でログイン",
   "Sign up is here": "新規登録はこちら",
   "Sign in is here": "ログインはこちら",
   "Sign up": "新規登録",
+  "or": "または",
   "Sign up with Google Account": "Google で登録",
   "Sign in with Google Account": "Google でログイン",
   "Sign up with this Google Account": "この Google アカウントで登録します",

+ 2 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -41,9 +41,11 @@
   "Error": "误差",
   "Warning": "警告",
   "Sign in": "登录",
+  "Sign in with External auth": "Sign in with {{signin}}",
   "Sign up is here": "注册",
   "Sign in is here": "登录",
   "Sign up": "注册",
+  "or": "或者",
   "Sign up with Google Account": "Sign up with Google Account",
   "Sign in with Google Account": "Sign in with Google Account",
   "Sign up with this Google Account": "Sign up with this Google Account",

+ 10 - 0
apps/app/src/components/CompleteUserRegistrationForm.module.scss

@@ -0,0 +1,10 @@
+@use '~/styles/atoms/placeholders/buttons';
+
+:root {
+  .complete-user-registration-form :global {
+    .btn-register {
+      @extend %btn-nologin;
+      @extend %btn-register;
+    }
+  }
+}

+ 42 - 18
apps/app/src/components/CompleteUserRegistrationForm.tsx

@@ -11,6 +11,12 @@ import { toastError } from '../client/util/toastr';
 
 import { CompleteUserRegistration } from './CompleteUserRegistration';
 
+
+import styles from './CompleteUserRegistrationForm.module.scss';
+
+const moduleClass = styles['complete-user-registration-form'] ?? '';
+
+
 interface Props {
   email: string,
   token: string,
@@ -85,9 +91,9 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
 
   return (
     <>
-      <div className="nologin-dialog mx-auto" id="nologin-dialog">
+      <div className={`${moduleClass} nologin-dialog mx-auto rounded-4 rounded-top-0`} id="nologin-dialog">
         <div className="row mx-0">
-          <div className="col-12">
+          <div className="col-12 px-4">
 
             { (errorCode != null && errorCode === UserActivationErrorCode.TOKEN_NOT_FOUND) && (
               <p className="alert alert-danger">
@@ -111,15 +117,19 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               <input type="hidden" name="token" value={token} />
 
               <div className="input-group">
-                <span className="input-group-text"></span><span className="material-symbols-outlined">mail</span>
-                <input type="text" className="form-control" placeholder={t('Email')} disabled value={email} />
+                <span className="p-2 text-white opacity-75">
+                  <span className="material-symbols-outlined">mail</span>
+                </span>
+                <input type="text" className="form-control rounded" placeholder={t('Email')} disabled value={email} />
               </div>
 
               <div className="input-group" id="input-group-username">
-                <span className="input-group-text"></span><span className="material-symbols-outlined">person</span>
+                <span className="p-2 text-white opacity-75">
+                  <span className="material-symbols-outlined">person</span>
+                </span>
                 <input
                   type="text"
-                  className="form-control"
+                  className="form-control rounded"
                   placeholder={t('User ID')}
                   name="username"
                   onChange={e => setUsername(e.target.value)}
@@ -129,15 +139,22 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               </div>
               {!usernameAvailable && (
                 <p className="form-text text-red">
-                  <span id="help-block-username"><span className="material-symbols-outlined">block</span>{t('installer.unavaliable_user_id')}</span>
+                  <span id="help-block-username">
+                    <span className="p-2 text-white opacity-75">
+                      <span className="material-symbols-outlined">block</span>
+                    </span>
+                    {t('installer.unavaliable_user_id')}
+                  </span>
                 </p>
               )}
 
               <div className="input-group">
-                <span className="input-group-text"></span><span className="material-symbols-outlined">sell</span>
+                <span className="p-2 text-white opacity-75">
+                  <span className="material-symbols-outlined">sell</span>
+                </span>
                 <input
                   type="text"
-                  className="form-control"
+                  className="form-control rounded"
                   placeholder={t('Name')}
                   name="name"
                   value={name}
@@ -148,10 +165,12 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               </div>
 
               <div className="input-group">
-                <span className="input-group-text"></span><span className="material-symbols-outlined">lock</span>
+                <span className="p-2 text-white opacity-75">
+                  <span className="material-symbols-outlined">lock</span>
+                </span>
                 <input
                   type="password"
-                  className="form-control"
+                  className="form-control rounded"
                   placeholder={t('Password')}
                   name="password"
                   value={password}
@@ -161,17 +180,22 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
                 />
               </div>
 
-              <div className="input-group justify-content-center d-flex mt-5">
-                <button type="button" disabled={forceDisableForm || disableForm} className="btn btn-fill" id="register">
-                  <div className="eff"></div>
-                  <span className="btn-label"></span><span className="material-symbols-outlined">person_add</span>
-                  <span className="btn-label-text">{t('Create')}</span>
+              <div className="input-group justify-content-center mt-4">
+                <button
+                  type="button"
+                  disabled={forceDisableForm || disableForm}
+                  className="btn btn-secondary btn-register col-6 mx-auto d-flex"
+                >
+                  <span>
+                    <span className="material-symbols-outlined">person_add</span>
+                  </span>
+                  <span className="flex-grow-1">{t('Create')}</span>
                 </button>
               </div>
 
-              <div className="input-group mt-5 d-flex justify-content-center">
+              <div className="input-group mt-5 d-flex">
                 <a href="https://growi.org" className="link-growi-org">
-                  <span className="growi">GROWI</span>.<span className="org">ORG</span>
+                  <span className="growi">GROWI</span><span className="org">.org</span>
                 </a>
               </div>
             </form>

+ 4 - 4
apps/app/src/components/DataTransferForm.tsx

@@ -11,8 +11,8 @@ const DataTransferForm = (): JSX.Element => {
   const { transferKey, generateTransferKey } = useGenerateTransferKey();
 
   return (
-    <div data-testid="installerForm" className="p-3">
-      <p className="alert alert-success">
+    <div data-testid="installerForm" className="py-3 px-4">
+      <p className="text-white fs-5 mt-2">
         <strong>{ t('g2g_data_transfer.transfer_data_to_this_growi')}</strong>
       </p>
 
@@ -22,8 +22,8 @@ const DataTransferForm = (): JSX.Element => {
             {t('g2g_data_transfer.publish_transfer_key')}
           </button>
         </div>
-        <div className="col-md-12 mt-1">
-          <div>
+        <div className="col-md-12 mt-2">
+          <div className="d-flex">
             <input className="form-control" type="text" value={transferKey} readOnly />
             <CustomCopyToClipBoard textToBeCopied={transferKey} message="copied_to_clipboard" />
           </div>

+ 10 - 0
apps/app/src/components/InstallerForm.module.scss

@@ -0,0 +1,10 @@
+@use '~/styles/atoms/placeholders/buttons';
+
+:root {
+  .installer-form :global {
+    .btn-register {
+      @extend %btn-nologin;
+      @extend %btn-register;
+    }
+  }
+}

+ 34 - 21
apps/app/src/components/InstallerForm.tsx

@@ -12,6 +12,11 @@ import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
 
 
+import styles from './InstallerForm.module.scss';
+
+const moduleClass = styles['installer-form'] ?? '';
+
+
 const InstallerForm = memo((): JSX.Element => {
   const { t, i18n } = useTranslation();
 
@@ -88,8 +93,8 @@ const InstallerForm = memo((): JSX.Element => {
     : <span><span className="material-symbols-outlined">block</span>{ t('installer.unavaliable_user_id') }</span>;
 
   return (
-    <div data-testid="installerForm" className={`nologin-dialog p-3 mx-auto${hasErrorClass}`}>
-      <div className="row">
+    <div data-testid="installerForm" className={`${moduleClass} nologin-dialog py-3 px-4 rounded-4 rounded-top-0 mx-auto${hasErrorClass}`}>
+      <div className="row mt-3">
         <div className="col-md-12">
           <p className="alert alert-success">
             <strong>{ t('installer.create_initial_account') }</strong><br />
@@ -97,14 +102,16 @@ const InstallerForm = memo((): JSX.Element => {
           </p>
         </div>
       </div>
-      <div className="row">
-        <form role="form" id="register-form" className="col-md-12" onSubmit={submitHandler}>
+      <div className="row mt-2">
+        <form role="form" id="register-form" className="ps-1" onSubmit={submitHandler}>
           <div className="dropdown mb-3">
             <div className="input-group dropdown-with-icon">
-              <span className="input-group-text"></span><span className="material-symbols-outlined">language</span>
+              <span className="p-2 text-white opacity-75">
+                <span className="material-symbols-outlined">language</span>
+              </span>
               <button
                 type="button"
-                className="btn btn-secondary dropdown-toggle form-control text-end rounded-end"
+                className="btn btn-secondary dropdown-toggle form-control text-end rounded"
                 id="dropdownLanguage"
                 data-testid="dropdownLanguage"
                 data-bs-toggle="dropdown"
@@ -146,11 +153,13 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className={`input-group mb-3${hasErrorClass}`}>
-            <span className="input-group-text"></span><span className="material-symbols-outlined">person</span>
+            <span className="p-2 text-white opacity-75">
+              <span className="material-symbols-outlined">person</span>
+            </span>
             <input
               data-testid="tiUsername"
               type="text"
-              className="form-control"
+              className="form-control rounded"
               placeholder={t('User ID')}
               name="registerForm[username]"
               // onBlur={checkUserName} // need not to check username before installation -- 2020.07.24 Yuki Takei
@@ -160,11 +169,13 @@ const InstallerForm = memo((): JSX.Element => {
           <p className="form-text">{ unavailableUserId }</p>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"></span><span className="material-symbols-outlined">sell</span>
+            <span className="p-2 text-white opacity-75">
+              <span className="material-symbols-outlined">sell</span>
+            </span>
             <input
               data-testid="tiName"
               type="text"
-              className="form-control"
+              className="form-control rounded"
               placeholder={t('Name')}
               name="registerForm[name]"
               required
@@ -172,11 +183,13 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"></span><span className="material-symbols-outlined">mail</span>
+            <span className="p-2 text-white opacity-75">
+              <span className="material-symbols-outlined">mail</span>
+            </span>
             <input
               data-testid="tiEmail"
               type="email"
-              className="form-control"
+              className="form-control rounded"
               placeholder={t('Email')}
               name="registerForm[email]"
               required
@@ -184,40 +197,40 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"></span> <span className="material-symbols-outlined">lock</span>
+            <span className="p-2 text-white opacity-75">
+              <span className="material-symbols-outlined">lock</span>
+            </span>
             <input
               data-testid="tiPassword"
               type="password"
-              className="form-control"
+              className="form-control rounded"
               placeholder={t('Password')}
               name="registerForm[password]"
               required
             />
           </div>
 
-          <div className="input-group mt-4 d-flex justify-content-center">
+          <div className="input-group mt-4 justify-content-center">
             <button
               data-testid="btnSubmit"
               type="submit"
-              className="btn-fill btn btn-register"
-              id="register"
+              className="btn btn-secondary btn-register col-6 d-flex"
               disabled={isLoading}
             >
-              <div className="eff"></div>
-              <span className="btn-label">
+              <span>
                 {isLoading ? (
                   <LoadingSpinner />
                 ) : (
                   <span className="material-symbols-outlined">person_add</span>
                 )}
               </span>
-              <span className="btn-label-text">{ t('Create') }</span>
+              <span className="flex-grow-1">{ t('Create') }</span>
             </button>
           </div>
 
           <div>
             <a href="https://growi.org" className="link-growi-org">
-              <span className="growi">GROWI</span>.<span className="org">org</span>
+              <span className="growi">GROWI</span><span className="org">.org</span>
             </a>
           </div>
 

+ 1 - 2
apps/app/src/components/InvitedForm.tsx

@@ -141,7 +141,6 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         {/* Create Button */}
         <div className="input-group justify-content-center d-flex mt-4">
           <button type="submit" className="btn btn-fill" id="register" disabled={isLoading}>
-            <div className="eff"></div>
             <span className="btn-label">
               {isLoading ? (
                 <LoadingSpinner />
@@ -155,7 +154,7 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
       </form>
       <div className="input-group mt-4 d-flex justify-content-center">
         <a href="https://growi.org" className="link-growi-org">
-          <span className="growi">GROWI</span>.<span className="org">ORG</span>
+          <span className="growi">GROWI</span><span className="org">.ORG</span>
         </a>
       </div>
     </div>

+ 28 - 118
apps/app/src/components/Layout/NoLoginLayout.module.scss

@@ -6,45 +6,33 @@
   height: 100vh;
 
   // layout
-  .page-wrapper {
-    display: flex;
-    align-items: center;
-    height: 100vh;
-    margin-top: 0px;
-
-    .main {
-      width: 100vw;
-
-      .nologin-header {
-        display: flex;
-        align-items: center;
-        padding-top: 30px;
-        padding-bottom: 10px;
+  .main {
+    width: 100vw;
+
+    .nologin-header {
+      padding-top: 30px;
+      padding-bottom: 10px;
+      svg {
+        fill: white;
       }
+    }
 
+    .growi-logo-type {
+      letter-spacing: .5rem;
     }
 
   }
 
   // styles
-  .nologin-header {
-    h1 {
-      font-size: 22px;
-      line-height: 1em;
-    }
-  }
-
   .alert {
     padding: 0.5em 1em 0.5em 2em;
   }
 
   .input-group {
-    margin-bottom: 10px;
+    margin-bottom: 16px;
 
     .input-group-text {
       text-align: center;
-      border: none;
-      border-radius: 0;
     }
   }
 
@@ -54,48 +42,6 @@
     }
   }
 
-  $btn-fill-colors: (
-    'login': (
-      rgba(bs.$danger, 0.4),
-      rgba(#7e4153, 0.7),
-    ),
-    'register': (
-      rgba(bs.$success, 0.4),
-      rgba(#3f7263, 0.7),
-    ),
-    'google': (
-      rgba(#24292e, 0.4),
-      bs.$gray-700,
-    ),
-    'github': (
-      rgba(lighten(black, 20%), 0.4),
-      bs.$gray-700,
-    ),
-    'facebook': (
-      rgba(#29487d, 0.4),
-      bs.$gray-700,
-    ),
-    'oidc': (
-      rgba(#24292e, 0.4),
-      bs.$gray-700,
-    ),
-    'saml': (
-      rgba(#55a79a, 0.4),
-      bs.$gray-700,
-    ),
-  );
-
-  @each $label, $colors in $btn-fill-colors {
-    .btn-fill##{$label} {
-      .btn-label {
-        background-color: nth($colors, 1);
-      }
-      .eff {
-        background-color: nth($colors, 2);
-      }
-    }
-  }
-
   // footer link text
   .link-growi-org {
     font-size: smaller;
@@ -107,6 +53,7 @@
       transition: color 0.8s;
     }
   }
+
   .nologin-header,
   .nologin-dialog {
     max-width: 480px;
@@ -138,24 +85,11 @@
       linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
     .nologin-header {
-      background-color: rgba(white, 0.5);
-
-      svg {
-        color: var(--bs-body-color);
-      }
-
-      .logo {
-        color: rgba(black, 0.5);
-        background-color: rgba(black, 0);
-      }
-
-      h1 {
-        color: rgba(black, 0.5);
-      }
+      background-color: rgba(white, 0.3);
     }
 
     .nologin-dialog {
-      background-color: rgba(white, 0.5);
+      background-color: rgba(white, 0.3);
       .link-switch {
         color: #1939b8;
         &:hover {
@@ -165,35 +99,30 @@
     }
 
     .input-group {
-      .input-group-text {
-        color: darken(white, 30%);
-        background-color: rgba(bs.$gray-700, 0.7);
-      }
-
       .form-control {
-        color: white;
-        background-color: rgba(bs.$gray-600, 0.7);
+        color: bs.$gray-800;
+        background-color: white;
         box-shadow: unset;
 
         &::placeholder {
-          color: darken(white, 30%);
+          color: bs.$gray-500;
         }
       }
     }
 
     .link-growi-org {
-      color: rgba(black, 0.4);
+      color: rgba(white, 0.4);
 
       &:hover,
       &.focus {
-        color: black;
+        color: white;
 
         .growi {
-          color: darken(var.$growi-green, 20%);
+          color: darken(var.$growi-blue, 10%);
         }
 
         .org {
-          color: darken(var.$growi-blue, 15%);
+          color: darken(var.$growi-green, 10%);
         }
       }
     }
@@ -206,29 +135,15 @@
     // background color
     $color-gradient: #3c465c;
     background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
-      linear-gradient(135deg, darken(var.$growi-green, 30%) 10%, hsla(225, 95%, 50%, 0) 70%),
-      linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
+      linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
       linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
     .nologin-header {
-      background-color: rgba(black, 0.5);
-
-      svg {
-        color: var(--bs-body-color);
-      }
-
-      .logo {
-        color: rgba(white, 0.5);
-        background-color: rgba(white, 0);
-      }
-
-      h1 {
-        color: rgba(white, 0.5);
-      }
+      background-color: rgba(black, 0.3);
     }
 
     .nologin-dialog {
-      background-color: rgba(black, 0.5);
+      background-color: rgba(black, 0.3);
       .link-switch {
         color: #7b9bd5;
         &:hover {
@@ -238,18 +153,13 @@
     }
 
     .input-group {
-      .input-group-text {
-        color: darken(white, 30%);
-        background-color: rgba(bs.$gray-700, 0.7);
-      }
-
       .form-control {
         color: white;
         background-color: rgba(#505050, 0.7);
         box-shadow: unset;
 
         &::placeholder {
-          color: darken(white, 30%);
+          color: bs.$gray-500;
         }
       }
     }
@@ -262,11 +172,11 @@
         color: rgba(white, 0.7);
 
         .growi {
-          color: darken(var.$growi-green, 5%);
+          color: darken(var.$growi-blue, 5%);
         }
 
         .org {
-          color: darken(var.$growi-blue, 5%);
+          color: darken(var.$growi-green, 5%);
         }
       }
     }

+ 11 - 5
apps/app/src/components/Layout/NoLoginLayout.tsx

@@ -1,4 +1,5 @@
-import React, { ReactNode } from 'react';
+import type { ReactNode } from 'react';
+import React from 'react';
 
 import { useAppTitle } from '~/stores/context';
 
@@ -26,15 +27,20 @@ export const NoLoginLayout = ({
 
   return (
     <RawLayout className={`nologin ${commonStyles.nologin} ${classNames}`}>
-      <div className="page-wrapper flex-row">
+      <div className="d-flex align-items-center vh-100 mt-0 flex-row">
         <div className="main container-fluid">
 
           <div className="row">
 
             <div className="col-md-12 position-relative">
-              <div className="nologin-header mx-auto flex-column">
-                <GrowiLogo />
-                <h1 className="my-3">{ appTitle ?? 'GROWI' }</h1>
+              <div className="nologin-header mx-auto rounded-4 rounded-bottom-0">
+                <div className="d-flex justify-content-center align-items-center">
+                  <GrowiLogo />
+                  <h1 className="growi-logo-type text-white fs-3 my-3 ms-3">GROWI</h1>
+                </div>
+                {appTitle !== 'GROWI' ? (
+                  <h2 className="fs-4 text-center text-white">{ appTitle }</h2>
+                ) : null}
                 <div className="noLogin-form-errors px-3"></div>
               </div>
               {children}

+ 83 - 0
apps/app/src/components/LoginForm.module.scss

@@ -1,3 +1,7 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '~/styles/atoms/placeholders/buttons';
+
 .login-form :global {
   // To adjust the behavior, this problem is not solved.
   // See https://github.com/AaronCCWong/react-card-flip/issues/56
@@ -15,4 +19,83 @@
     bottom: 9px;
     z-index: 3;
   }
+
+  .text-line {
+    &:before,
+    &:after {
+      flex-grow: 1;
+      height: 1px;
+      margin:0 1em;
+      content: '';
+      background: rgba(white,0.5);
+    }
+  }
+
+
+  .ldap-space {
+    padding-right: 76px;
+  }
+
+  .input-ldap {
+    position: absolute;
+    top: 4px;
+    right: 5px;
+  }
+}
+
+
+// Button colors
+:root {
+  .login-form :global {
+
+    .btn {
+      @extend %btn-nologin;
+    }
+
+    .btn-register {
+      @extend %btn-register;
+    }
+
+    .btn-login {
+      --bs-btn-bg: #{rgba(#204986, 0.6)};
+      --bs-btn-hover-bg: #{rgba(#204986, 0.8)};
+      --bs-btn-active-bg: #{rgba(#204986, 0.8)};
+    }
+
+    .btn-function {
+      --bs-btn-bg: #{rgba(bs.$gray-800, 0.8)};
+      --bs-btn-hover-bg: #{rgba(bs.$gray-800, 0.5)};
+      --bs-btn-active-bg: #{rgba(bs.$gray-800, 0.5)};
+    }
+
+    .btn-auth-google {
+      --bs-btn-bg: #{rgba(#4285F4, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#4285F4, 0.8)};
+      --bs-btn-active-bg: #{rgba(#4285F4, 0.8)};
+    }
+
+    .btn-auth-github {
+      --bs-btn-bg: #{rgba(#403D3E, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#403D3E, 0.7)};
+      --bs-btn-active-bg: #{rgba(#403D3E, 0.7)};
+    }
+
+    .btn-auth-facebook {
+      --bs-btn-bg: #{rgba(#29487d, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#29487d, 0.9)};
+      --bs-btn-active-bg: #{rgba(#29487d, 0.9)};
+    }
+
+    .btn-auth-oidc {
+      --bs-btn-bg: #{rgba(#835B1A, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#835B1A, 0.8)};
+      --bs-btn-active-bg: #{rgba(#835B1A, 0.8)};
+    }
+
+    .btn-auth-saml {
+      --bs-btn-bg: #{rgba(#138957, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#138957, 0.7)};
+      --bs-btn-active-bg: #{rgba(#138957, 0.7)};
+    }
+  }
 }

+ 80 - 85
apps/app/src/components/LoginForm.tsx

@@ -18,6 +18,8 @@ import { CompleteUserRegistration } from './CompleteUserRegistration';
 
 import styles from './LoginForm.module.scss';
 
+const moduleClass = styles['login-form'];
+
 type LoginFormProps = {
   username?: string,
   name?: string,
@@ -196,31 +198,33 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
         <form role="form" onSubmit={handleLoginWithLocalSubmit} id="login-form">
           <div className="input-group">
-            <span className="input-group-text">
+            <span className="text-white opacity-75 d-flex align-items-center">
               <span className="material-symbols-outlined">person</span>
             </span>
             <input
               type="text"
-              className="form-control rounded-0"
+              className={`form-control rounded ms-2 ${isLdapStrategySetup ? 'ldap-space' : ''}`}
               data-testid="tiUsernameForLogin"
               placeholder="Username or E-mail"
               onChange={(e) => { setUsernameForLogin(e.target.value) }}
               name="usernameForLogin"
             />
             {isLdapStrategySetup && (
-              <small className="input-group-text text-success">
-                <span className="material-symbols-outlined">select_check_box</span>LDAP
+              <small className="badge text-bg-success input-ldap d-flex align-items-center">
+                <span className="material-symbols-outlined">network_node</span>
+                <span className="">LDAP</span>
               </small>
             )}
+
           </div>
 
           <div className="input-group">
-            <span className="input-group-text">
+            <span className="text-white opacity-75 d-flex align-items-center">
               <span className="material-symbols-outlined">lock</span>
             </span>
             <input
               type="password"
-              className="form-control rounded-0"
+              className="form-control rounded ms-2"
               data-testid="tiPasswordForLogin"
               placeholder="Password"
               onChange={(e) => { setPasswordForLogin(e.target.value) }}
@@ -231,20 +235,18 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           <div className="input-group my-4">
             <button
               type="submit"
-              id="login"
-              className="btn btn-fill rounded-0 login mx-auto"
+              className="btn btn-secondary btn-login col-7 mx-auto d-flex"
               data-testid="btnSubmitForLogin"
               disabled={isLoading}
             >
-              <div className="eff"></div>
-              <span className="btn-label">
+              <span>
                 {isLoading ? (
                   <LoadingSpinner />
                 ) : (
                   <span className="material-symbols-outlined">login</span>
                 )}
               </span>
-              <span className="btn-label-text">{t('Sign in')}</span>
+              <span className="flex-grow-1">{t('Sign in')}</span>
             </button>
           </div>
         </form>
@@ -264,62 +266,54 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
 
   const renderExternalAuthInput = useCallback((auth) => {
-    const authIconNames = {
-      google: 'google',
-      github: 'github',
-      facebook: 'facebook',
-      oidc: 'openid',
-      saml: 'key',
+    const authIcon = {
+      google: <span className="growi-custom-icons align-bottom">google</span>,
+      github: <span className="growi-custom-icons align-bottom">github</span>,
+      facebook: <span className="growi-custom-icons align-bottom">facebook</span>,
+      oidc: <span className="growi-custom-icons align-bottom">openid</span>,
+      saml: <span className="material-symbols-outlined align-bottom">key</span>,
+    };
+    const authBtn = `btn-auth-${auth}`;
+    const signin = {
+      google: 'Google',
+      github: 'GitHub',
+      facebook: 'Facebook',
+      oidc: 'OIDC',
+      saml: 'SAML',
     };
 
     return (
-      <div key={auth} className="col-6 my-2">
-        <button type="button" className="btn btn-fill rounded-0" id={auth} onClick={handleLoginWithExternalAuth}>
-          <div className="eff"></div>
-          <span className="btn-label">
-            <span className="growi-custom-icons align-bottom">{authIconNames[auth]}</span>
-          </span>
-          <span className="btn-label-text">{t('Sign in')}</span>
-        </button>
-        <div className="small text-end">by {auth} Account</div>
-      </div>
+      <button
+        key={`btn-auth-${auth}`}
+        type="button"
+        className={`btn btn-secondary ${authBtn} my-2 col-10 col-sm-7 mx-auto d-flex`}
+        onClick={handleLoginWithExternalAuth}
+      >
+        <span>{authIcon[auth]}</span>
+        <span className="flex-grow-1">{t('Sign in with External auth', { signin: signin[auth] })}</span>
+      </button>
     );
   }, [handleLoginWithExternalAuth, t]);
 
   const renderExternalAuthLoginForm = useCallback(() => {
-    const { isLocalStrategySetup, isLdapStrategySetup, objOfIsExternalAuthEnableds } = props;
-    const isExternalAuthCollapsible = isLocalStrategySetup || isLdapStrategySetup;
-    const collapsibleClass = isExternalAuthCollapsible ? 'collapse collapse-external-auth' : '';
+    const { objOfIsExternalAuthEnableds } = props;
 
     return (
       <>
-        <div className="grw-external-auth-form border-top border-bottom">
-          <div id="external-auth" className={`external-auth ${collapsibleClass}`}>
-            <div className="row mt-2">
-              {Object.keys(objOfIsExternalAuthEnableds).map((auth) => {
-                if (!objOfIsExternalAuthEnableds[auth]) {
-                  return;
-                }
-                return renderExternalAuthInput(auth);
-              })}
-            </div>
-          </div>
+        <div className="text-center text-line d-flex align-items-center mb-3">
+          <p className="text-white mb-0">{t('or')}</p>
         </div>
-        <div className="text-center">
-          <button
-            type="button"
-            className="btn btn-secondary btn-external-auth-tab btn-sm rounded-0 mb-3"
-            data-bs-toggle={isExternalAuthCollapsible ? 'collapse' : ''}
-            data-bs-target="#external-auth"
-            aria-expanded="false"
-            aria-controls="external-auth"
-          >
-            External Auth
-          </button>
+        <div className="mt-2">
+          {Object.keys(objOfIsExternalAuthEnableds).map((auth) => {
+            if (!objOfIsExternalAuthEnableds[auth]) {
+              return;
+            }
+            return renderExternalAuthInput(auth);
+          })}
         </div>
       </>
     );
-  }, [props, renderExternalAuthInput]);
+  }, [props, t, renderExternalAuthInput]);
 
   const resetRegisterErrors = useCallback(() => {
     if (registerErrors.length === 0) return;
@@ -420,13 +414,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           {!isEmailAuthenticationEnabled && (
             <div>
               <div className="input-group" id="input-group-username">
-                <span className="input-group-text">
+                <span className="text-white opacity-75 d-flex align-items-center">
                   <span className="material-symbols-outlined">person</span>
                 </span>
                 {/* username */}
                 <input
                   type="text"
-                  className="form-control rounded-0"
+                  className="form-control rounded ms-2"
                   onChange={(e) => { setUsernameForRegister(e.target.value) }}
                   placeholder={t('User ID')}
                   name="username"
@@ -438,13 +432,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 <span id="help-block-username"></span>
               </p>
               <div className="input-group">
-                <span className="input-group-text">
+                <span className="text-white opacity-75 d-flex align-items-center">
                   <span className="material-symbols-outlined">sell</span>
                 </span>
                 {/* name */}
                 <input
                   type="text"
-                  className="form-control rounded-0"
+                  className="form-control rounded ms-2"
                   onChange={(e) => { setNameForRegister(e.target.value) }}
                   placeholder={t('Name')}
                   name="name"
@@ -456,14 +450,14 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           )}
 
           <div className="input-group">
-            <span className="input-group-text">
+            <span className="text-white opacity-75 d-flex align-items-center">
               <span className="material-symbols-outlined">mail</span>
             </span>
             {/* email */}
             <input
               type="email"
               disabled={!isMailerSetup && isEmailAuthenticationEnabled}
-              className="form-control rounded-0"
+              className="form-control rounded ms-2"
               onChange={(e) => { setEmailForRegister(e.target.value) }}
               placeholder={t('Email')}
               name="email"
@@ -490,13 +484,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           {!isEmailAuthenticationEnabled && (
             <div>
               <div className="input-group">
-                <span className="input-group-text">
+                <span className="text-white opacity-75 d-flex align-items-center">
                   <span className="material-symbols-outlined">lock</span>
                 </span>
                 {/* Password */}
                 <input
                   type="password"
-                  className="form-control rounded-0"
+                  className="form-control rounded ms-2"
                   onChange={(e) => { setPasswordForRegister(e.target.value) }}
                   placeholder={t('Password')}
                   name="password"
@@ -510,35 +504,31 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           <div className="input-group justify-content-center my-4">
             <button
               type="submit"
-              className="btn btn-fill rounded-0"
-              id="register"
+              className="btn btn-secondary btn-register d-flex col-7"
               disabled={(!isMailerSetup && isEmailAuthenticationEnabled) || isLoading}
             >
-              <div className="eff"></div>
-              <span className="btn-label">
+              <span>
                 {isLoading ? (
                   <LoadingSpinner />
                 ) : (
-                  <span className="material-symbols-outlined">login</span>
+                  <span className="material-symbols-outlined">person_add</span>
                 )}
               </span>
-              <span className="btn-label-text">{submitText}</span>
+              <span className="flex-grow-1">{submitText}</span>
             </button>
           </div>
         </form>
 
-        <div className="border-bottom"></div>
-
         <div className="row">
-          <div className="text-end col-12 mt-2 py-2">
+          <div className="text-end col-12 mb-5">
             <a
               href="#login"
-              id="login"
-              className="link-switch"
-              style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
+              className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
+              style={{ pointerEvents: isLoading ? 'none' : undefined }}
               onClick={switchForm}
             >
-              <span className="material-symbols-outlined">login</span>{t('Sign in is here')}
+              <span className="material-symbols-outlined fs-5">login</span>
+              <span className="flex-grow-1">{t('Sign in is here')}</span>
             </a>
           </div>
         </div>
@@ -554,32 +544,37 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
   }
 
   return (
-    <div className={`login-form ${styles['login-form']}`}>
-      <div className="nologin-dialog mx-auto" id="nologin-dialog" data-testid="login-form">
+    <div className={moduleClass}>
+      <div className="nologin-dialog mx-auto rounded-4 rounded-top-0" id="nologin-dialog" data-testid="login-form">
         <div className="row mx-0">
-          <div className="col-12">
+          <div className="col-12 px-md-4">
             <ReactCardFlip isFlipped={isRegistering} flipDirection="horizontal" cardZIndex="3">
               <div className="front">
                 {isLocalOrLdapStrategiesEnabled && renderLocalOrLdapLoginForm()}
                 {isSomeExternalAuthEnabled && renderExternalAuthLoginForm()}
                 {isLocalOrLdapStrategiesEnabled && isPasswordResetEnabled && (
-                  <div className="text-end mb-2">
-                    <a href="/forgot-password" className="d-block link-switch">
-                      <span className="material-symbols-outlined">vpn_key</span>{t('forgot_password.forgot_password')}
+                  <div className="mt-4">
+                    <a
+                      href="/forgot-password"
+                      className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
+                      style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
+                    >
+                      <span className="material-symbols-outlined">vpn_key</span>
+                      <span className="flex-grow-1">{t('forgot_password.forgot_password')}</span>
                     </a>
                   </div>
                 )}
                 {/* Sign up link */}
                 {isRegistrationEnabled && (
-                  <div className="text-end mb-2">
+                  <div className="mt-2 mb-5">
                     <a
                       href="#register"
-                      id="register"
-                      className="link-switch"
+                      className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
                       style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
                       onClick={switchForm}
                     >
-                      <span className="material-symbols-outlined">check_box</span> {t('Sign up is here')}
+                      <span className="material-symbols-outlined">person_add</span>
+                      <span className="flex-grow-1">{t('Sign up is here')}</span>
                     </a>
                   </div>
                 )}
@@ -592,7 +587,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           </div>
         </div>
         <a href="https://growi.org" className="link-growi-org ps-3">
-          <span className="growi">GROWI</span>.<span className="org">org</span>
+          <span className="growi">GROWI</span><span className="org">.org</span>
         </a>
       </div>
     </div>

+ 3 - 3
apps/app/src/pages/installer.page.tsx

@@ -42,13 +42,13 @@ const InstallerPage: NextPage<Props> = (props: Props) => {
   const navTabMapping = useMemo(() => {
     return {
       user_infomation: {
-        Icon: () => <span className="material-symbols-outlined me-1">person</span>,
+        Icon: () => <span className="material-symbols-outlined me-2">person</span>,
         Content: InstallerForm,
         i18n: t('installer.tab'),
       },
       external_accounts: {
         // TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015
-        Icon: () => <span className="growi-custom-icons">external_link</span>,
+        Icon: () => <span className="growi-custom-icons me-2">external_link</span>,
         Content: DataTransferForm,
         i18n: tCommons('g2g_data_transfer.tab'),
       },
@@ -69,7 +69,7 @@ const InstallerPage: NextPage<Props> = (props: Props) => {
       <Head>
         <title>{title}</title>
       </Head>
-      <div id="installer-form-container" className="nologin-dialog mx-auto">
+      <div id="installer-form-container" className="nologin-dialog mx-auto rounded-4 rounded-top-0">
         <CustomNavAndContents navTabMapping={navTabMapping} tabContentClasses={['p-0']} />
       </div>
     </NoLoginLayout>

+ 6 - 1
apps/app/src/pages/login/index.module.scss

@@ -1,9 +1,14 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
 // layout
 .login-page :global {
 
   .nologin-header,
   .nologin-dialog {
-    width: 320px;
+    width: 420px;
+    @include bs.media-breakpoint-down(sm) {
+      width: 320px;
+    }
   }
 
 }

+ 0 - 64
apps/app/src/styles/atoms/_buttons.scss

@@ -1,64 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-@use '../mixins';
-
-// fill button style
-.btn.btn-fill {
-  position: relative;
-  display: flex;
-  justify-content: space-between;
-  min-width: 130px;
-  padding: 0px;
-  overflow: hidden;
-  color: white;
-  text-align: center;
-  background-color: rgba(lighten(black, 15%), 0.5);
-  border: none;
-
-  &:not(:disabled) {
-    cursor: pointer;
-  }
-
-  .btn-label {
-    position: relative;
-    z-index: 1;
-    padding: 9px 15px;
-    color: white;
-    text-decoration: none;
-  }
-
-  .btn-label-text {
-    position: relative;
-    z-index: 1;
-    margin: auto;
-    color: white;
-    text-align: center;
-    text-decoration: none;
-  }
-
-  // effect
-  .eff {
-    position: absolute;
-    top: -50px;
-    left: 0px;
-    z-index: 0;
-    width: 100%;
-    height: 100%;
-    transition: all 0.5s ease;
-  }
-
-  &:hover {
-    .eff {
-      top: 0;
-    }
-  }
-}
-
-// define disabled button w/o pointer-events, see _override-bootstrap.scss
-.btn.disabled,
-.btn[disabled],
-fieldset[disabled] .btn {
-  &.grw-pointer-events-none {
-    pointer-events: none;
-  }
-}

+ 14 - 0
apps/app/src/styles/atoms/placeholders/_buttons.scss

@@ -0,0 +1,14 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+%btn-nologin {
+  transition: 0.8s ease;
+  --bs-btn-border-color: #{rgba(white, 0.1)};
+  --bs-btn-hover-border-color: #{rgba(white, 0.1)};
+  --bs-btn-active-border-color: #{rgba(white, 0.1)};
+}
+
+%btn-register {
+  --bs-btn-bg: #{rgba(bs.$success, 0.4)};
+  --bs-btn-hover-bg: #{rgba(bs.$success, 0.8)};
+  --bs-btn-active-bg: #{rgba(bs.$success, 0.8)};
+}

+ 0 - 1
apps/app/src/styles/style-app.scss

@@ -4,7 +4,6 @@
 @import 'mixins';
 
 // atoms
-@import 'atoms/buttons';
 @import 'atoms/custom_control';
 @import 'atoms/code';
 @import 'atoms/tag';

+ 1 - 0
packages/custom-icons/svg/openid.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a{fill:none;}</style></defs><g transform="translate(-451 -420)"><path class="a" d="M12.121-1.607h0V-18.75L9.08-17.268V-2.089c-3.446-.433-6.058-2.321-6.058-4.6,0-2.156,2.344-3.964,5.536-4.522v-1.92C3.683-12.536,0-9.879,0-6.687,0-3.37,3.951-.638,9.085-.179ZM20-7.9l-.411-4.353-1.562.884a13.494,13.494,0,0,0-5.371-1.754v1.92A9.539,9.539,0,0,1,15.781-10.1l-1.643.924Z" transform="translate(453 441.465)"/></g></svg>