mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat(ui): add button components (#400)
* feat(ui): add button components add button components * fix(ui): add conncetor name i18n add connector name i18n * fix(ui): remove useless noop click function remove useless noop click function
This commit is contained in:
parent
6f3c762632
commit
0feeee263a
9 changed files with 181 additions and 38 deletions
|
@ -65,14 +65,16 @@ $font-family: 'PingFang SC', 'SF Pro Text', sans-serif;
|
|||
/* Color */
|
||||
--color-primary: #{$color-primary};
|
||||
--color-background: #{$color-neutral-0};
|
||||
--color-secondary-background-active: #{$color-neutral-10};
|
||||
--color-secondary-background-disabled: #{$color-neutral-10};
|
||||
--color-border: #{$color-neutral-100};
|
||||
--color-border-active: #{$color-neutral-70};
|
||||
--color-border-disabled: #{$color-neutral-30};
|
||||
--color-control-background: #{$color-neutral-10};
|
||||
--color-control-focus: #{$color-primary-tint-60};
|
||||
--color-control-action: #{$color-neutral-70};
|
||||
--color-control-action-focus: #{$color-primary-tint-70};
|
||||
|
||||
|
||||
/* Font Color */
|
||||
--color-font-primary: #{$color-neutral-100};
|
||||
--color-font-secondary: #444;
|
||||
|
@ -80,6 +82,7 @@ $font-family: 'PingFang SC', 'SF Pro Text', sans-serif;
|
|||
--color-font-button-text: #{$color-neutral-0};
|
||||
--color-font-button-text-active: #{rgba($color-neutral-0, 0.4)};
|
||||
--color-font-secondary-active: #{$color-neutral-70};
|
||||
--color-font-secondary-disabled: #{rgba($color-neutral-100, 0.4)};
|
||||
|
||||
/* ===== Legacy Styling ===== */
|
||||
--color-heading: #333;
|
||||
|
@ -97,7 +100,7 @@ $font-family: 'PingFang SC', 'SF Pro Text', sans-serif;
|
|||
--font-title: 600 32px/40px #{$font-family};
|
||||
--font-heading-2: 400 18px/22px #{$font-family};
|
||||
--font-heading-2-bold: 600 18px/22px #{$font-family};
|
||||
--font-control: 500 18px/22px #{$font-family};
|
||||
--font-control: 500 18px/20px #{$font-family};
|
||||
--font-button-text: 600 20px/24px #{$font-family};
|
||||
|
||||
/* ===== Legacy Styling ===== */
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.socialButton {
|
||||
font: var(--font-control);
|
||||
border: _.border(var(--color-border));
|
||||
background: var(--color-background);
|
||||
color: var(--color-font-primary);
|
||||
justify-content: flex-start;
|
||||
|
||||
.icon {
|
||||
width: _.unit(5);
|
||||
height: _.unit(5);
|
||||
@include _.image-align-center;
|
||||
margin-right: _.unit(4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--color-secondary-background-active);
|
||||
border: _.border(var(--color-border-active));
|
||||
color: var(--color-font-primary);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: var(--color-secondary-background-disabled);
|
||||
border: _.border(var(--color-border-disabled));
|
||||
color: var(--color-font-secondary-disabled);
|
||||
|
||||
.icon {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
}
|
||||
}
|
39
packages/ui/src/components/Button/SocialLinkButton.tsx
Normal file
39
packages/ui/src/components/Button/SocialLinkButton.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { ConnectorMetadata } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as SocialLinkButtonStyles from './SocialLinkButton.module.scss';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
connector: Pick<ConnectorMetadata, 'id' | 'name' | 'logo'>;
|
||||
onClick?: (id: string) => void;
|
||||
};
|
||||
|
||||
const SocialLinkButton = ({ isDisabled, className, connector, onClick }: Props) => {
|
||||
const { id, name, logo } = connector;
|
||||
|
||||
const {
|
||||
i18n: { language },
|
||||
} = useTranslation();
|
||||
const localName = name[language] ?? name.en;
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={isDisabled}
|
||||
className={classNames(styles.button, SocialLinkButtonStyles.socialButton, className)}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onClick?.(id);
|
||||
}}
|
||||
>
|
||||
{logo && <img src={logo} alt={localName} className={SocialLinkButtonStyles.icon} />}
|
||||
{localName}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default SocialLinkButton;
|
|
@ -5,22 +5,34 @@
|
|||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: _.unit(3) _.unit(8);
|
||||
border-radius: 12px;
|
||||
background: var(--color-button-background);
|
||||
color: var(--color-button-text);
|
||||
font: var(--font-control);
|
||||
box-shadow: var(--shadow-button);
|
||||
padding: _.unit(3) _.unit(10);
|
||||
border-radius: _.unit(2);
|
||||
font: var(--font-button-text);
|
||||
transition: var(--transition-default-control);
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: var(--color-button-background-disabled);
|
||||
color: var(--color-button-text-disabled);
|
||||
}
|
||||
.primary {
|
||||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-font-button-text);
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: var(--color-button-background-hover);
|
||||
&:active {
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 16%), rgba(0, 0, 0, 16%)), var(--color-primary);
|
||||
color: var(--color-font-button-text-active);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary {
|
||||
border: _.border(var(--color-border));
|
||||
background: var(--color-background);
|
||||
color: var(--color-font-primary);
|
||||
|
||||
&:active {
|
||||
border: _.border(var(--color-border-active));
|
||||
color: var(--color-font-secondary-active);
|
||||
}
|
||||
}
|
||||
|
|
52
packages/ui/src/components/Button/index.test.tsx
Normal file
52
packages/ui/src/components/Button/index.test.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { render, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import SocialLinkButton from './SocialLinkButton';
|
||||
import Button from './index';
|
||||
|
||||
describe('Button Component', () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
onClick.mockClear();
|
||||
});
|
||||
|
||||
it('render Button', () => {
|
||||
const { queryByText, container } = render(<Button onClick={onClick}>foo</Button>);
|
||||
expect(queryByText('foo')).not.toBeNull();
|
||||
|
||||
const button = container.querySelector('button');
|
||||
|
||||
if (button) {
|
||||
fireEvent.click(button);
|
||||
expect(onClick).toBeCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('render SocialLinkButton', () => {
|
||||
const connector = {
|
||||
id: 'foo',
|
||||
name: {
|
||||
en: 'Sign in with Logto',
|
||||
},
|
||||
logo: 'http://logto.dev/logto.png',
|
||||
};
|
||||
const { queryByText, container } = render(
|
||||
<SocialLinkButton connector={connector} onClick={onClick} />
|
||||
);
|
||||
|
||||
expect(queryByText(connector.name.en)).not.toBeNull();
|
||||
|
||||
const button = container.querySelector('button');
|
||||
const icon = container.querySelector('img');
|
||||
|
||||
if (icon) {
|
||||
expect(icon.src).toEqual(connector.logo);
|
||||
}
|
||||
|
||||
if (button) {
|
||||
fireEvent.click(button);
|
||||
expect(onClick).toBeCalled();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -4,22 +4,30 @@ import React from 'react';
|
|||
import * as styles from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
htmlType?: 'button' | 'submit' | 'reset';
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
value?: string;
|
||||
children: string; // TODO: make it i18nKey with optional params
|
||||
type?: 'primary' | 'secondary';
|
||||
onClick?: React.MouseEventHandler;
|
||||
};
|
||||
|
||||
const Button = ({ isDisabled, className, value, onClick }: Props) => {
|
||||
return (
|
||||
<input
|
||||
disabled={isDisabled}
|
||||
className={classNames(styles.button, className)}
|
||||
type="button"
|
||||
value={value}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const Button = ({
|
||||
htmlType = 'button',
|
||||
type = 'primary',
|
||||
isDisabled,
|
||||
className,
|
||||
children,
|
||||
onClick,
|
||||
}: Props) => (
|
||||
<button
|
||||
disabled={isDisabled}
|
||||
className={classNames(styles.button, styles[type], className)}
|
||||
type={htmlType}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
export default Button;
|
||||
|
|
|
@ -57,11 +57,9 @@ const Register: FC = () => {
|
|||
{i18n.t<string, LogtoErrorI18nKey>(`errors:${error.code}`)}
|
||||
</MessageBox>
|
||||
)}
|
||||
<Button
|
||||
isDisabled={loading}
|
||||
value={loading ? t('register.loading') : t('register.action')}
|
||||
onClick={signUp}
|
||||
/>
|
||||
<Button isDisabled={loading} onClick={signUp}>
|
||||
{loading ? t('register.loading') : t('register.action')}
|
||||
</Button>
|
||||
|
||||
<div className={styles.haveAccount}>
|
||||
<span className={styles.prefix}>{t('register.have_account')}</span>
|
||||
|
|
|
@ -25,14 +25,15 @@
|
|||
margin-bottom: _.unit(-6);
|
||||
}
|
||||
|
||||
.inputField {
|
||||
> .inputField {
|
||||
width: 100%;
|
||||
margin-top: _.unit(3);
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
> input[type='button'] {
|
||||
> button {
|
||||
margin-top: _.unit(12);
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.createAccount {
|
||||
|
|
|
@ -62,11 +62,9 @@ const SignIn: FC = () => {
|
|||
{i18n.t<string, LogtoErrorI18nKey>(`errors:${error.code}`)}
|
||||
</MessageBox>
|
||||
)}
|
||||
<Button
|
||||
isDisabled={loading}
|
||||
value={loading ? t('sign_in.loading') : t('sign_in.action')}
|
||||
onClick={signInHandler}
|
||||
/>
|
||||
<Button isDisabled={loading} type="primary" onClick={signInHandler}>
|
||||
{loading ? t('sign_in.loading') : t('sign_in.action')}
|
||||
</Button>
|
||||
<TextLink className={styles.createAccount} href="/register">
|
||||
{t('register.create_account')}
|
||||
</TextLink>
|
||||
|
|
Loading…
Add table
Reference in a new issue