# React `` UI Components `@rcaferati/react-awesome-button` provides three related React button components: - **`AwesomeButton`** - animated base button - **`AwesomeButtonProgress`** - progress flow wrapper on top of `AwesomeButton` - **`AwesomeButtonSocial`** - social/share wrapper on top of `AwesomeButton` This README is updated for the current `8.2.0` release outputs, including base CSS, bundled theme CSS files, theme mapping modules, animated size transitions, and auto-width text choreography. --- ## Preview [![Awesome Button visual preview](https://caferati.dev/images/rab.gif)](https://caferati.dev/images/rab.gif) --- ## Installation ```bash npm install @rcaferati/react-awesome-button ``` --- ## Quick Start Import the base stylesheet once, then render a button. This is the plain CSS path for buttons that do not pass a `cssModule` theme mapping. ```jsx import React from 'react'; import { AwesomeButton } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/styles.css'; export default function Example() { return ( Button ); } ``` --- ## Styling With Theme CSS Modules The React package uses two theme imports together: 1. the theme CSS file, which contains the actual rules 2. the theme mapping object, which is passed as `cssModule` ### Blue theme example ```jsx import React from 'react'; import { AwesomeButton } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue'; export default function Example() { return ( Button ); } ``` ### Plain and themed buttons in the same app If you mix plain buttons and themed buttons, import both: ```js import '@rcaferati/react-awesome-button/styles.css'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; ``` ### Bundled themes - `theme-amber` - `theme-blue` - `theme-bojack` - `theme-bruce` - `theme-c137` - `theme-eric` - `theme-flat` - `theme-indigo` - `theme-red` - `theme-rickiest` --- ## `AwesomeButton` ### Basic button behavior If `href` is not provided, `AwesomeButton` renders button-like behavior and calls `onPress` on successful activation. ```jsx import React from 'react'; import { AwesomeButton } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue'; export default function Example() { return ( { // do something }}> Save changes ); } ``` ### Anchor mode (`href` provided) If `href` is provided, the component renders anchor-like behavior and lets native navigation happen. Use `containerProps` for forwarded root element props such as `target`, `rel`, and `aria-*`. ```jsx import React from 'react'; import { AwesomeButton } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue'; export default function Example() { return ( Open website ); } ``` ### Icons Use `before` and `after` to render icons. For icon-only buttons, pass the icon with `before` and omit children. ```jsx import React from 'react'; import { AwesomeButton } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue'; function StarIcon() { return ( ); } function ArrowRightIcon() { return ( ); } function PlayIcon() { return ( ); } export default function Example() { return (
} after={}> Continue } containerProps={{ 'aria-label': 'Play' }} />
); } ``` ### Sizes and auto width - `size="small" | "medium" | "large"` uses fixed button dimensions - `size={null}` uses content-driven auto width - fixed-size changes animate by default - measured auto-width changes animate by default - `animateSize={false}` opts out of both fixed-size and measured auto-width animation - fixed-to-auto and auto-to-fixed transitions are intentionally instant in this release Size animation timing is `125ms cubic-bezier(0.3, 0.05, 0.2, 1)`. ```jsx import React from 'react'; import { AwesomeButton } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/styles.css'; export default function Example() { return (
Small Medium Large Auto width grows with content Instant fixed-size change
); } ``` ### Text transition (string labels only) Use `textTransition` when the button label is a plain string and you want label changes to animate. When combined with `size={null}`, the current React behavior is: - larger labels grow width first, then animate the text - smaller labels animate the text first, then shrink the width For the parity flow documented in the current stories, drive the label change from an external rerender rather than mutating the same button label from its own `onPress`. ```jsx import React from 'react'; import { AwesomeButton } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/styles.css'; export default function Example() { const [expanded, setExpanded] = React.useState(false); const label = expanded ? 'Open analytics dashboard' : 'Open'; return (
{label}
); } ``` --- ## `AwesomeButton` Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `type` | `string` | `"primary"` | Visual variant key, such as `primary`, `secondary`, `danger`, `link`, or a social visual variant | | `size` | `"small" \| "medium" \| "large" \| null` | `null` | Size key or `null` for auto width | | `active` | `boolean` | `false` | Controlled pressed/active visual state | | `disabled` | `boolean` | `false` | Disables interactions | | `visible` | `boolean` | `true` | Toggles visible state class | | `placeholder` | `boolean` | `true` | If `true` and `children` is empty, renders placeholder/disabled state | | `lockedHover` | `boolean` | `false` | Locks the visual middle-hover position without changing interactivity | | `animateSize` | `boolean` | `true` | Animates fixed-size changes and measured auto-width changes | | `textTransition` | `boolean` | `false` | Animates string-only label changes with a scrambling transition | | `between` | `boolean` | `false` | Uses `space-between` layout for content | | `ripple` | `boolean` | `false` | Enables ripple effect on successful activation | | `moveEvents` | `boolean` | `true` | Enables pointer move position classes (`left` / `middle` / `right`) | | `href` | `string \| null` | `null` | Enables anchor-like mode | | `element` | `React.ElementType \| null` | `null` | Custom forwarded root element component | | `rootElement` | `string` | `"aws-btn"` | Base class prefix | | `className` | `string \| null` | `null` | Extra class names | | `style` | `React.CSSProperties` | `{}` | Inline styles for the root element | | `containerProps` | `object` | `{}` | Additional props forwarded to the root element | | `cssModule` | `Record \| null` | `null` | CSS module mapping object for themed usage | | `before` | `React.ReactNode` | `null` | Content rendered before the label | | `after` | `React.ReactNode` | `null` | Content rendered after the label | | `extra` | `React.ReactNode` | `null` | Extra content rendered inside the wrapper | | `children` | `React.ReactNode` | `null` | Main label/content | | `onPress` | `(event) => void` | `null` | Called on successful activation/release | | `onPressed` | `(event) => void` | `null` | Called when the press-in transition completes | | `onReleased` | `(element: HTMLElement) => void` | `null` | Called when the release cycle clears | | `onMouseDown` | `(event) => void` | `null` | Pointer/mouse/touch down callback | | `onMouseUp` | `(event) => void` | `null` | Pointer/mouse/touch up callback | ```tsx Locked hover ``` --- ## `AwesomeButtonProgress` `AwesomeButtonProgress` wraps `AwesomeButton` and manages a guarded progress lifecycle. Its `onPress` receives a second callback argument: - `next(true)` starts the success flow - `next(false, 'Failed')` starts the error flow with an optional label override ### Basic success flow ```jsx import React from 'react'; import { AwesomeButtonProgress } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue'; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); export default function Example() { return ( { await sleep(900); next(true); }}> Verify ); } ``` ### Error flow ```jsx import React from 'react'; import { AwesomeButtonProgress } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/styles.css'; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); export default function Example() { return ( { await sleep(900); next(false, 'Failed'); }}> Publish ); } ``` ### Text-only progress and custom bar timing Use `showProgressBar={false}` to keep the loading/result text flow while hiding the dark loading bar. Use `progressLoadingTime` to control how long the progress bar takes to advance during the loading phase. ```jsx import React from 'react'; import { AwesomeButtonProgress } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/styles.css'; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); export default function Example() { return ( { await sleep(900); next(true); }}> Sync account ); } ``` ### With icon (`before`) ```jsx import React from 'react'; import { AwesomeButtonProgress } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue'; function ShieldIcon() { return ( ); } const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); export default function Example() { return ( } loadingLabel="Verifying..." resultLabel="Verified!" onPress={async (_event, next) => { await sleep(900); next(true); }}> Verify signature ); } ``` ### `AwesomeButtonProgress` specific props `AwesomeButtonProgress` accepts all `AwesomeButton` props except public `active`, and replaces the base `onPress` payload with the progress version below. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `onPress` | `(event, next) => void` | `null` | Progress handler. Call `next(true)` or `next(false, label?)` | | `loadingLabel` | `string` | `"Wait.."` | Loading phase label | | `resultLabel` | `string` | `"Success!"` | Success label | | `releaseDelay` | `number` | `500` | Delay before reset after progress end | | `showProgressBar` | `boolean` | `true` | Hides the dark loading bar when `false`, while keeping the progress flow | | `progressLoadingTime` | `number` | `6000` | Loading-phase progress bar duration in milliseconds | --- ## `AwesomeButtonSocial` `AwesomeButtonSocial` wraps `AwesomeButton` and builds a share action when no custom `onPress` is provided. ### Behavior summary On activation, the current React behavior is: 1. if you provide `onPress`, your handler is called and the built-in sharer logic is skipped 2. if `href` is also present, native anchor navigation still happens because `href` is forwarded to the base button 3. if there is no `href`, mobile-capable environments attempt `navigator.share(...)` 4. otherwise, the built-in sharer uses a type-specific web share URL, direct URL, or centered popup where supported If you want a pure custom action with no native navigation, avoid combining `onPress` and `href`. ### Basic share example (LinkedIn) ```jsx import React from 'react'; import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue'; function LinkedinIcon() { return ( ); } export default function Example() { return ( } sharer={{ url: 'https://example.com', message: 'Check this out', }}> LinkedIn ); } ``` ### Visual-only social style (GitHub look, link behavior) `github` is a visual style in the bundled themes. If you want a GitHub-looking button that opens a profile or repo, use `href`. ```jsx import React from 'react'; import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/themes/theme-blue.css'; import themeBlue from '@rcaferati/react-awesome-button/themes/theme-blue'; function GithubIcon() { return ( ); } export default function Example() { return ( }> Open GitHub ); } ``` ### WhatsApp example ```jsx import React from 'react'; import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/styles.css'; export default function Example() { return ( WhatsApp ); } ``` ### Instagram-style button (direct URL fallback) `instagram` is treated as a direct URL fallback. It does not use a dedicated web popup sharer endpoint. ```jsx import React from 'react'; import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/styles.css'; export default function Example() { return ( Instagram ); } ``` ### `href` mode (bypass built-in sharer logic) If `href` is present, the component behaves like an anchor and does not execute the built-in share flow. ```jsx import React from 'react'; import { AwesomeButtonSocial } from '@rcaferati/react-awesome-button'; import '@rcaferati/react-awesome-button/styles.css'; export default function Example() { return ( Open GitHub ); } ``` ### `AwesomeButtonSocial` specific props `AwesomeButtonSocial` accepts all `AwesomeButton` props and adds the props below. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `sharer` | `object` | `{}` | Share payload source, with page metadata fallbacks where available | | `dimensions` | `{ width: number; height: number }` | `{ width: 640, height: 480 }` | Popup window dimensions for share flows | | `sharer.url` | `string` | current page URL | URL to share, falling back to `window.location.href` | | `sharer.message` | `string` | page title | Share message/text, falling back to the document title | | `sharer.image` | `string` | `og:image` meta | Image URL used by supported sharers such as Pinterest | | `sharer.phone` | `string` | `null` | Phone number for WhatsApp | | `sharer.user` | `string` | `null` | Username for Messenger direct flow | ### Supported sharer `type` values The built-in sharer recognizes: - `facebook` - `twitter` - `pinterest` - `linkedin` - `reddit` - `whatsapp` - `messenger` - `mail` - `instagram` You can still use other `type` values as visual styles if your theme includes them, but the built-in sharer URLs do not exist for unsupported types. --- ## Recommended Patterns ### Icon-only buttons Use `before` and omit `children`: ```jsx