UNPKG

@kiwicom/orbit-components

Version:

Orbit-components is a React component library which provides developers with the easiest possible way of building Kiwi.com's products.

266 lines (265 loc) 15 kB
"use client"; import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from "react"; import ButtonPrimitive from "../primitives/ButtonPrimitive"; import getIconContainer from "../primitives/ButtonPrimitive/common/getIconContainer"; import getCommonProps from "../primitives/ButtonPrimitive/common/getCommonProps"; import ChevronForwardIcon from "../icons/ChevronForward"; import useTheme from "../hooks/useTheme"; import getSocialButtonStyles from "./helpers/getSocialButtonStyles"; import getSocialButtonIconForeground from "./helpers/getSocialButtonIconForeground"; import getSocialButtonIcon from "./helpers/getSocialButtonIcon"; import { TYPE_OPTIONS } from "./consts"; /** * @orbit-doc-start * README * ---------- * # SocialButton * * To implement SocialButton component into your project you'll need to add the import: * * ```jsx * import SocialButton from "@kiwicom/orbit-components/lib/SocialButton"; * ``` * * After adding import into your project you can use it simply like: * * ```jsx * <SocialButton>Hello World!</SocialButton> * ``` * * ## Props * * Table below contains all types of the props available in SocialButton component. * * | Name | Type | Default | Description | * | :----------- | :------------------------- | :--------- | :-------------------------------------------------------------------------------------------------- | * | ariaControls | `string` | | Id of the element the button controls. | * | ariaExpanded | `boolean` | | Tells screen reader the controlled element from `ariaControls` is expanded. | * | asComponent | `string \| React.Element` | `"button"` | The component used for the root node. | * | fullWidth | `boolean` | `false` | If `true`, the SocialButton will grow up to the full width of its container. | * | children | `React.Node` | | The content of the SocialButton. [See Functional specs](#functional-specs) | * | dataTest | `string` | | Optional prop for testing purposes. | * | id | `string` | | Set `id` for `SocialButton`. | * | disabled | `boolean` | `false` | If `true`, the SocialButton will be disabled. | * | external | `boolean` | `false` | If `true`, the SocialButton opens link in a new tab. [See Functional specs](#functional-specs) | * | href | `string` | | The URL of the link to open when SocialButton is clicked. [See Functional specs](#functional-specs) | * | loading | `boolean` | `false` | If `true`, the loading glyph will be displayed. | * | onClick | `event => void \| Promise` | | Function for handling onClick event. | * | ref | `func` | | Prop for forwarded ref of the SocialButton. | * | role | `string` | | Specifies the role of an element. | * | **size** | [`enum`](#enum) | `"normal"` | The size of the SocialButton. | * | spaceAfter | `enum` | | Additional `margin-bottom` after component. | * | submit | `boolean` | `false` | If `true`, the SocialButton will have `type="submit"` attribute, otherwise `type="button"`. | * | tabIndex | `string \| number` | | Specifies the tab order of an element. | * | title | `string` | | Adds `aria-label`. | * | **type** | [`enum`](#enum) | `"apple"` | The type of SocialButton. | * | width | `string` | | The width of the SocialButton. Can be any string - `100px`, `20%`. | * * ### enum * * | type | size | spaceAfter | * | :----------- | :--------- | :----------- | * | `"apple"` | `"small"` | `"none"` | * | `"facebook"` | `"normal"` | `"smallest"` | * | `"google"` | `"large"` | `"small"` | * | `"X"` | | `"normal"` | * | `"email"` | | `"medium"` | * | | | `"large"` | * | | | `"largest"` | * * ## Functional specs * * - When the `external` is specified, `noopener` value will be automatically added to attribute `rel` for security reason. * * - By passing the `href` prop into SocialButton, it will render into `a` element. If you pass `asComponent` prop it will override this logic. * * - If you want to use the `asComponent` prop then **YourComponent** should accept at least `className`. Otherwise it won't be rendered with proper styling, e.g.: * * ```jsx * const YourComponent = props => <div {...props} /> * * <SocialButton asComponent={YourComponent}>Title</SocialButton> * ``` * * If you specify the children of **YourComponent** component, it will override the children prop of SocialButton component, e.g.: * * ```jsx * const YourComponent = props => <div {...props}>YourComponent</div>; * ``` * * * Accessibility * ------------- * ## Accessibility * * The SocialButton component has been designed with accessibility in mind, providing accessible social authentication buttons with proper semantic structure and screen reader support for sign-in flows. * * ### Accessibility Props * * The following props are available to improve the accessibility of your SocialButton component: * * | Name | Type | Description | * | :------------- | :----------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | * | ariaControls | `string` | Identifies the element controlled by the button, establishing a programmatic relationship for screen readers. Use with unique element IDs. | * | ariaExpanded | `boolean` | Indicates whether the element controlled by the button (via `ariaControls`) is expanded or collapsed. Commonly used with dropdowns, modals, or expandable content triggered by social sign-in. | * | ariaLabelledby | `string` | References the ID(s) of element(s) that provide a label for the button. The referenced elements can be hidden and their text will be announced by screen readers to describe the button. | * | title | `string` | Provides additional context as an `aria-label` attribute. Use when you need to add extra information for screen readers or when there are no `children` to serve as the button label. Should describe the specific social sign-in action. | * | disabled | `boolean` | When `true`, makes the button inactive and removes it from keyboard navigation. Screen readers will announce the button as disabled and users cannot interact with it. | * | role | `string` | Specifies the ARIA role of the element. Should only be used in edge cases when rendering SocialButton as non-actionable HTML elements like `div` or `span`, though this is anti-pattern behavior. | * | tabIndex | `string \| number` | Controls the tab order of the element. Use with caution and only when necessary to modify the default focus flow. | * * ### Automatic Accessibility Features * * - The component automatically manages HTML semantics and ARIA attributes: * * - Renders as a semantic `<button>` element by default, ensuring proper keyboard focusability and button behavior * - Renders as a semantic `<a>` element when `href` is provided, creating a proper link that assistive technologies can identify * - Sets `type="button"` or `type="submit"` based on the `submit` prop when rendered as a button * - Automatically adds `rel="noopener noreferrer"` for external links when `external` prop is true * - Properly handles the `disabled` state by preventing interaction and removing from tab order * * - Focus management is handled automatically: * * - Native focus behavior is preserved based on the rendered element * - Visible focus indicators are provided for keyboard users * - Disabled and loading states are properly excluded from tab navigation * * - Icon accessibility is managed automatically: * * - Icons are rendered with appropriate semantic structure * - Both the social icon and the chevron forward icon are automatically hidden from screen readers with `ariaHidden` attribute * - Icons receive proper color contrast and sizing for accessibility * * - Loading state accessibility: * - When `loading` is true, the button becomes disabled and shows a loading indicator * - The loading spinner inherits the proper color for sufficient contrast * - Screen readers are informed of the disabled state during loading * * ### Best Practices * * - Use clear and specific button text that describes the social sign-in action, such as "Sign in with Google" or "Continue with Apple" rather than just "Google" or "Apple" * - Ensure the button text follows a consistent pattern across all social buttons in your application * - When using the `title` prop, provide additional context that supplements but doesn't duplicate the button text * - For social sign-in flows, consider informing users about account creation vs. existing account sign-in behavior * - Ensure sufficient color contrast between the button and its background, especially for custom styled social buttons * - When social buttons trigger modals or popups, use `ariaControls` and `ariaExpanded` to establish the relationship between the button and the controlled content * - Avoid nesting interactive elements: Do not place SocialButton inside other interactive elements like buttons or links, as this creates invalid HTML and confusing experiences for assistive technology users * * ### Keyboard Navigation * * SocialButton components are fully navigable with keyboard: * * - **When rendered as button (default):** * * - Users can focus on the button using the **Tab** key * - Users can activate the button using both **Enter** and **Space** keys * - When `disabled` or `loading`, the button is skipped in tab navigation * * - **When rendered as link (with href):** * - Users can focus on the link using the **Tab** key * - Users can activate the link using the **Enter** key * - Standard link keyboard behavior applies * * On mobile devices with on-screen keyboards, standard touch interactions apply while maintaining the same keyboard accessibility patterns when an external keyboard is connected. * * ### Examples * * #### Basic Social Sign-in Button * * ```jsx * <SocialButton type="google">Sign in with Google</SocialButton> * ``` * * Screen reader announces: "Sign in with Google, button" * * #### Apple Sign-in with Additional Context * * ```jsx * <SocialButton type="apple" title="Sign in with Apple ID to access your account"> * Continue with Apple * </SocialButton> * ``` * * Screen reader announces: "Sign in with Apple ID to access your account, button" * * #### Social Button with Modal Control * * ```jsx * <SocialButton * type="facebook" * ariaControls="social-auth-modal" * ariaExpanded={isModalOpen} * onClick={handleFacebookSignIn} * > * Sign in with Facebook * </SocialButton> * ``` * * Screen reader announces: "Sign in with Facebook, button, collapsed" (when modal is closed) or "Sign in with Facebook, button, expanded" (when modal is open). * * #### External Social Authentication Link * * ```jsx * <SocialButton type="google" href="https://accounts.google.com/oauth/authorize?..." external> * Sign in with Google * </SocialButton> * ``` * * Screen reader announces: "Sign in with Google, link" * * #### Loading State Social Button * * ```jsx * <SocialButton type="apple" loading={isAuthenticating} disabled={isAuthenticating}> * {isAuthenticating ? "Signing in..." : "Continue with Apple"} * </SocialButton> * ``` * * When loading, screen reader announces: "Signing in..., button, dimmed" (indicating the disabled state). * * * @orbit-doc-end */ const SocialButton = ({ type = TYPE_OPTIONS.APPLE, disabled = false, size, ref, ...props }) => { const theme = useTheme(); const propsWithTheme = { theme, size, ...props }; const commonProps = getCommonProps(propsWithTheme); const buttonStyles = getSocialButtonStyles({ type, disabled, theme }); const icons = getIconContainer({ ...propsWithTheme, iconForeground: getSocialButtonIconForeground({ type, theme }) }); const iconLeft = getSocialButtonIcon(type); return /*#__PURE__*/React.createElement(ButtonPrimitive, _extends({ ref: ref }, props, commonProps, buttonStyles, icons, { disabled: disabled, iconLeft: iconLeft, iconRight: /*#__PURE__*/React.createElement(ChevronForwardIcon, { customColor: type === TYPE_OPTIONS.APPLE ? "#FFF" : "", color: "primary", ariaHidden: true, reverseOnRtl: true }), circled: false })); }; export default SocialButton;