diff --git a/.changeset/little-colts-happen.md b/.changeset/little-colts-happen.md
new file mode 100644
index 000000000..015a28f38
--- /dev/null
+++ b/.changeset/little-colts-happen.md
@@ -0,0 +1,8 @@
+---
+'@cloudfour/patterns': minor
+---
+
+- Add Subscribe component
+- Update CI workflow to use Node 16
+- Add a `button_class` template prop to the Button Swap component
+- Add a `class` template prop to the Input Group component
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cfeb5243f..87ac0e83c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- - name: Use Node.js 12
+ - name: Use Node.js 16
uses: actions/setup-node@v3
with:
- node-version: 12
+ node-version: 16
- name: Cache node modules
uses: actions/cache@v2
with:
@@ -35,10 +35,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- - name: Use Node.js 12
+ - name: Use Node.js 16
uses: actions/setup-node@v3
with:
- node-version: 12
+ node-version: 16
- name: Cache node modules
uses: actions/cache@v2
with:
@@ -58,10 +58,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- - name: Use Node.js 12
+ - name: Use Node.js 16
uses: actions/setup-node@v3
with:
- node-version: 12
+ node-version: 16
- name: Cache node modules
uses: actions/cache@v2
with:
@@ -80,10 +80,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- - name: Use Node.js 12
+ - name: Use Node.js 16
uses: actions/setup-node@v3
with:
- node-version: 12
+ node-version: 16
- name: Cache node modules
uses: actions/cache@v2
with:
diff --git a/src/components/button-swap/button-swap.stories.mdx b/src/components/button-swap/button-swap.stories.mdx
index fdca9eb46..0cd11f983 100644
--- a/src/components/button-swap/button-swap.stories.mdx
+++ b/src/components/button-swap/button-swap.stories.mdx
@@ -73,7 +73,8 @@ You have the ability to pass `initialCallback` and `swappedCallback` callback fu
## Template Properties
-- `class` (string): Appends a CSS class(es) to the root element.
+- `class` (string): Appends to the CSS class of the root element.
+- `button_class` (string): Appends to the CSS class of the button element.
- `content_start_icon` (string, default `'bell'`): The button [icon](/docs/design-icons--page) (the same icon is applied to both buttons).
- `initial_label` (string, default `'Unsubscribed from notifications'`): The first button visually hidden text.
- `initial_visual_label` (string, default `'Get notifications'`): The first button label.
diff --git a/src/components/button-swap/button-swap.twig b/src/components/button-swap/button-swap.twig
index 5518bc747..a236594db 100644
--- a/src/components/button-swap/button-swap.twig
+++ b/src/components/button-swap/button-swap.twig
@@ -9,7 +9,7 @@
{% include '@cloudfour/components/button/button.twig' with {
- class: 'js-button-swap__button',
+ class: ['js-button-swap__button', button_class]|join(' '),
content_start_icon: content_start_icon|default('bell'),
label: initial_visual_label|default('Get notifications')
} only %}
@@ -20,7 +20,12 @@
{% include '@cloudfour/components/button/button.twig' with {
- class: 'c-button--secondary is-slashed js-button-swap__button',
+ class: [
+ 'c-button--secondary',
+ 'is-slashed',
+ 'js-button-swap__button',
+ button_class
+ ]|join(' '),
content_start_icon: content_start_icon|default('bell'),
label: swapped_visual_label|default('Turn off notifications')
} only %}
diff --git a/src/components/subscribe/demo/demo.twig b/src/components/subscribe/demo/demo.twig
new file mode 100644
index 000000000..f953cf61d
--- /dev/null
+++ b/src/components/subscribe/demo/demo.twig
@@ -0,0 +1,5 @@
+{% embed '@cloudfour/components/subscribe/subscribe.twig' with {
+ form_id: 'test'
+} only %}{% endembed %}
+
+
This is only for demo purposes. I am a link
diff --git a/src/components/subscribe/subscribe.scss b/src/components/subscribe/subscribe.scss
new file mode 100644
index 000000000..339071183
--- /dev/null
+++ b/src/components/subscribe/subscribe.scss
@@ -0,0 +1,55 @@
+@use '../../compiled/tokens/scss/breakpoint';
+@use '../../compiled/tokens/scss/color';
+@use '../../compiled/tokens/scss/size';
+@use '../../mixins/a11y';
+@use '../../mixins/theme';
+
+///
+/// Subscribe component
+///
+
+.c-subscribe {
+ position: relative;
+}
+
+.c-subscribe__controls {
+ display: grid;
+ gap: 1em;
+ margin: size.$rhythm-default 0;
+
+ @media (min-width: breakpoint.$s) {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+.c-subscribe__control {
+ inline-size: 100%;
+}
+
+///
+/// 1. Ensures the form background color matches the current theme
+///
+
+.c-subscribe__form {
+ background-color: var(--theme-color-background-base); // 1
+ block-size: 100%;
+ inline-size: 100%;
+ inset-block-start: 0;
+ inset-inline-start: 0;
+ position: absolute;
+
+ ///
+ /// 1. Allows for the visually hidden form to be keyboard accessible, will be
+ /// visually hidden when elements within the form are not in focus.
+ ///
+
+ .c-subscribe__form-input-label,
+ .c-subscribe:not(.activate-form) // 1
+ &:not(:target):not(:focus-within) {
+ @include a11y.sr-only;
+ }
+}
+
+.c-subscribe__form-input-group {
+ margin: size.$rhythm-default 0;
+}
diff --git a/src/components/subscribe/subscribe.stories.mdx b/src/components/subscribe/subscribe.stories.mdx
new file mode 100644
index 000000000..7ac3aeed4
--- /dev/null
+++ b/src/components/subscribe/subscribe.stories.mdx
@@ -0,0 +1,254 @@
+import { Story, Canvas, Meta, ArgsTable } from '@storybook/addon-docs';
+import { useEffect } from '@storybook/client-api';
+import { makeTwigInclude } from '../../make-twig-include';
+import template from './subscribe.twig';
+import { initSubscribe } from './subscribe.ts';
+// Helper function to initialize toggling button JS
+const templateStory = (args) => {
+ useEffect(() => {
+ const templateEls = [...document.querySelectorAll('.js-subscribe')].map(
+ (templateEl) => initSubscribe(templateEl)
+ );
+ return () => {
+ for (const templateEl of templateEls) templateEl.destroy();
+ };
+ });
+ return template(args);
+};
+
+` value',
+ table: {
+ defaultValue: { summary: 'Email' },
+ },
+ },
+ email_input_placeholder: {
+ type: 'string',
+ description: 'The email input placeholder text',
+ table: {
+ defaultValue: { summary: 'Your Email Address' },
+ },
+ },
+ submit_btn_label: {
+ type: 'string',
+ description: 'The subscription form submit button label',
+ table: {
+ defaultValue: { summary: 'Subscribe' },
+ },
+ },
+ }}
+/>
+
+# Subscribe
+
+The Subscribe component provides the UI to subscribe to notifications and/or email weekly digests.
+
+This component embeds the [Button Swap component](/?path=/docs/components-button-swap--default-story) for the "Get Notifications" button.
+
+
+
+## Template Properties
+
+
+
+## JavaScript
+
+The Subscribe component UX can be progressively enhanced using JavaScript. Enhancements include:
+
+- A one second form hide delay is added when `:focus` is moved away from the Subscribe component elements
+- The form can be hidden by pressing the Escape key
+
+### Syntax
+
+```js
+initSubscribe(subscribeEl);
+```
+
+### Parameters
+
+#### `subscribeEl`
+
+The Subscribe component `.js-subscribe` root element.
+
+### Return value
+
+An object with a `destroy` function that removes all event listeners added by this component.
+
+```js
+{
+ destroy: () => void
+}
+```
+
+### Examples
+
+#### Single Subscribe component on the page
+
+```js
+// Initialize
+const subscribeEl = initSubscribe(document.querySelector('.js-subscribe'));
+
+// Remove all event listeners
+subscribeEl.destroy();
+```
+
+#### Multiple Subscribe components on the page
+
+```js
+// Initialize
+const subscribeEls = [...document.querySelectorAll('.js-subscribe')].map(
+ (subscribeEl) => initSubscribe(subscribeEl)
+);
+
+// Remove all event listeners
+for (const subscribeEl of subscribeEls) {
+ subscribeEl.destroy();
+}
+```
+
+### Note
+
+You will need to initialize the "Get Notifications" button as well, see the
+[Button Swap component](/?path=/docs/components-button-swap--default-story#javascript)
+for initialization docs.
diff --git a/src/components/subscribe/subscribe.test.ts b/src/components/subscribe/subscribe.test.ts
new file mode 100644
index 000000000..744af568b
--- /dev/null
+++ b/src/components/subscribe/subscribe.test.ts
@@ -0,0 +1,264 @@
+import path from 'path';
+import type { ElementHandle, PleasantestUtils } from 'pleasantest';
+import { withBrowser, getAccessibilityTree } from 'pleasantest';
+import { loadTwigTemplate, loadGlobalCSS } from '../../../test-utils';
+
+// Helper to load the Twig template file
+const componentMarkup = loadTwigTemplate(
+ path.join(__dirname, './subscribe.twig')
+);
+// Helper to load the demo Twig template file
+const demoMarkup = loadTwigTemplate(path.join(__dirname, './demo/demo.twig'));
+
+/**
+ * Helper function that checks the `clientHeight` and `clientWidth` of
+ * a given element, expects dimensions to be smaller than or equal to `1`
+ * meaning the element is visually hidden.
+ */
+const expectElementToBeVisuallyHidden = async (
+ element: ElementHandle
+) => {
+ const { elHeight, elWidth } = await element.evaluate((el: HTMLElement) => ({
+ elHeight: el.clientHeight,
+ elWidth: el.clientWidth,
+ }));
+ expect(elHeight).toBeLessThanOrEqual(1);
+ expect(elWidth).toBeLessThanOrEqual(1);
+};
+
+/**
+ * Helper function that checks the `clientHeight` and `clientWidth` of
+ * given element, expects dimensions to be greater than or equal to `1`
+ * meaning the element is not visually hidden.
+ */
+const expectElementNotToBeVisuallyHidden = async (
+ form: ElementHandle
+) => {
+ const { elHeight, elWidth } = await form.evaluate((el: HTMLElement) => ({
+ elHeight: el.clientHeight,
+ elWidth: el.clientWidth,
+ }));
+
+ expect(elHeight).toBeGreaterThan(1);
+ expect(elWidth).toBeGreaterThan(1);
+};
+
+// Helper to initialize the component JS
+const initJS = (utils: PleasantestUtils) =>
+ utils.runJS(`
+ import { initSubscribe } from './subscribe'
+ export default () => initSubscribe(
+ document.querySelector('.js-subscribe')
+ )
+ `);
+
+describe('Subscription', () => {
+ test(
+ 'should use semantic markup',
+ withBrowser(async ({ utils, page }) => {
+ await loadGlobalCSS(utils);
+ await utils.loadCSS('./subscribe.scss');
+ await utils.injectHTML(await componentMarkup());
+ await initJS(utils);
+
+ expect(await getAccessibilityTree(page)).toMatchInlineSnapshot(`
+ document
+ heading "Never miss an article!"
+ text "Never miss an article!"
+ status
+ text "Notifications have been turned off."
+ button "Get notifications"
+ link "Get Weekly Digests"
+ text "Get Weekly Digests"
+ form "Get Weekly Digests"
+ heading "Get Weekly Digests"
+ text "Get Weekly Digests"
+ text "Email"
+ textbox
+ button "Subscribe"
+ `);
+ })
+ );
+
+ test(
+ 'should be keyboard accessible',
+ withBrowser(async ({ utils, screen, waitFor, page }) => {
+ await loadGlobalCSS(utils);
+ await utils.loadCSS('./subscribe.scss');
+ await utils.injectHTML(await demoMarkup());
+ await initJS(utils);
+
+ // Confirm the form is visually hidden by default
+ const form = await screen.getByRole('form', {
+ name: 'Get Weekly Digests',
+ });
+ await expectElementToBeVisuallyHidden(form);
+
+ // Tab all the way to the form email input
+ await page.keyboard.press('Tab'); // Notifications button
+ await page.keyboard.press('Tab'); // Weekly Digests link
+ await page.keyboard.press('Tab'); // Email input
+
+ // Confirm the form is now "active" (not visually hidden)
+ await expectElementNotToBeVisuallyHidden(form);
+
+ // Email input should be in focus
+ const emailInput = await screen.getByRole('textbox', { name: 'Email' });
+ await expect(emailInput).toHaveFocus();
+
+ // Tab again to get to the Submit button
+ await page.keyboard.press('Tab');
+
+ // Submit button should be in focus
+ const subscribeBtn = await screen.getByRole('button', {
+ name: 'Subscribe',
+ });
+ await expect(subscribeBtn).toHaveFocus();
+
+ // Confirm the form is still "active" (not visually hidden)
+ await expectElementNotToBeVisuallyHidden(form);
+
+ // Navigate back up to the Weekly Digests link
+ await page.keyboard.down('Shift'); // Navigate backwards
+ await page.keyboard.press('Tab'); // Email input
+ await page.keyboard.press('Tab'); // Weekly Digests link
+ await page.keyboard.up('Shift'); // Release Shift key
+
+ // Confirm the focus has moved to the Weekly Digests link
+ const weeklyDigestsBtn = await screen.getByRole('link', {
+ name: 'Get Weekly Digests',
+ });
+ await expect(weeklyDigestsBtn).toHaveFocus();
+
+ // The form should now be visually hidden again
+ await expectElementToBeVisuallyHidden(form);
+
+ // Navigate forward past the Submit to activate the form hide timeout
+ await page.keyboard.press('Tab'); // Email input
+ await page.keyboard.press('Tab'); // Submit button
+ await page.keyboard.press('Tab'); // Out of the form
+
+ // Confirm the form is still "active" (not visually hidden)
+ await expectElementNotToBeVisuallyHidden(form);
+
+ // Navigate back quickly to confirm timeout getting cancelled
+ await page.keyboard.down('Shift'); // Navigate backwards
+ await page.keyboard.press('Tab'); // Submit button
+ await page.keyboard.up('Shift'); // Release Shift key
+
+ // Confirm the form is still "active" (not visually hidden)
+ await expectElementNotToBeVisuallyHidden(form);
+
+ await page.keyboard.press('Tab'); // Out of the form
+
+ // Confirm the form is still "active" (not visually hidden)
+ await expectElementNotToBeVisuallyHidden(form);
+
+ // After a timeout, the form eventually visually hides
+ await waitFor(
+ async () => {
+ await expectElementToBeVisuallyHidden(form);
+ },
+ {
+ timeout: 2000,
+ interval: 1000,
+ }
+ );
+
+ // Navigate back into the form
+ await page.keyboard.down('Shift'); // Navigate backwards
+ await page.keyboard.press('Tab'); // Submit button
+ await page.keyboard.up('Shift'); // Release Shift key
+
+ // Confirm the form is "active" again (not visually hidden)
+ await expectElementNotToBeVisuallyHidden(form);
+
+ // Should hide the form
+ await page.keyboard.press('Escape');
+
+ // Confirm the form should is visually hidden
+ await expectElementToBeVisuallyHidden(form);
+
+ // The focus should reset back to the "weekly digests" link
+ await expect(weeklyDigestsBtn).toHaveFocus();
+ })
+ );
+
+ test(
+ 'should be customizable',
+ withBrowser(async ({ utils, screen }) => {
+ // Set up CSS
+ await loadGlobalCSS(utils);
+ await utils.loadCSS('./subscribe.scss');
+
+ // No customization
+ await utils.injectHTML(await componentMarkup({ form_id: 'test' }));
+
+ // Confirm default heading tag
+ await screen.getByRole('heading', {
+ name: 'Never miss an article!',
+ level: 2,
+ });
+ await screen.getByRole('heading', {
+ name: 'Get Weekly Digests',
+ level: 2,
+ });
+
+ // Confirm default form values
+ let emailInput = await screen.getByRole('textbox', { name: 'Email' });
+ expect(emailInput).toHaveAttribute('placeholder', 'Your Email Address');
+
+ // Customize the component
+ await utils.injectHTML(
+ await componentMarkup({
+ form_id: 'test',
+ form_action: 'test-action.com',
+ heading_tag: 'h3',
+ weekly_digests_heading: 'Weekly digests available',
+ never_miss_article_heading: "Don't miss out!",
+ notifications_btn_class: 'hello',
+ notifications_btn_initial_visual_label: 'Yes to notifications',
+ weekly_digests_btn_class: 'world',
+ weekly_digests_btn_label: 'I want weekly digests',
+ email_input_placeholder: 'Gimme email',
+ submit_btn_label: 'Sign up',
+ })
+ );
+
+ // Confirm custom headings
+ await screen.getByRole('heading', {
+ name: 'Weekly digests available',
+ level: 3,
+ });
+ await screen.getByRole('heading', {
+ name: "Don't miss out!",
+ level: 3,
+ });
+
+ // Confirm custom form values
+ const form = await screen.getByRole('form', {
+ name: 'Weekly digests available',
+ });
+ expect(form).toHaveAttribute('action', 'test-action.com');
+ emailInput = await screen.getByRole('textbox', { name: 'Email' });
+ expect(emailInput).toHaveAttribute('placeholder', 'Gimme email');
+
+ // Confirm custom notifications button
+ const notificationsBtn = await screen.getByRole('button', {
+ name: 'Yes to notifications',
+ });
+ await expect(notificationsBtn).toHaveClass('hello');
+
+ // Confirm custom weekly digests link
+ const weeklyDigestsLink = await screen.getByRole('link', {
+ name: 'I want weekly digests',
+ });
+ await expect(weeklyDigestsLink).toHaveClass('world');
+
+ // Confirm custom weekly digests submit button
+ await screen.getByRole('button', {
+ name: 'Sign up',
+ });
+ })
+ );
+});
diff --git a/src/components/subscribe/subscribe.ts b/src/components/subscribe/subscribe.ts
new file mode 100644
index 000000000..788b9b2ca
--- /dev/null
+++ b/src/components/subscribe/subscribe.ts
@@ -0,0 +1,105 @@
+/**
+ * Subscribe
+ *
+ * Progressively enhances the UX for the Subscribe component.
+ *
+ * Enhancements include:
+ * - A one second form hide delay is added when `:focus` is moved away from the
+ * Subscribe component elements
+ * - The form can be hidden by pressing the `Escape` key
+ */
+export const initSubscribe = (containerEl: HTMLElement) => {
+ const SHOW_FORM_CLASS = 'activate-form';
+ const BLUR_TIMEOUT = 1000; // Milliseconds
+
+ // Keeps track of active setTimeouts
+ let blurTimeoutId: number;
+ // Keeps the current state of the form
+ let isFormOpen = false;
+
+ // Query all the required elements
+ const getWeeklyDigestsBtn = containerEl.querySelector(
+ '.js-subscribe__get-weekly-digests-btn'
+ );
+ const formEl = containerEl.querySelector('form');
+ const formFocusableEls = containerEl.querySelectorAll('label, input, button');
+ const controlEls = containerEl.querySelectorAll('.js-subscribe__control');
+
+ // Confirm we have what we need to proceed
+ if (!getWeeklyDigestsBtn || !formEl) {
+ return;
+ }
+
+ // Hide the form anytime a `js-subscribe__control` gets focus
+ const onControlFocus = () => {
+ clearTimeout(blurTimeoutId);
+ containerEl.classList.remove(SHOW_FORM_CLASS);
+ isFormOpen = false;
+ };
+
+ // Show the form anytime any form element gets focus
+ // The form is always accessible by keyboard, it's only visually hidden
+ const onFormFocus = () => {
+ clearTimeout(blurTimeoutId);
+ containerEl.classList.add(SHOW_FORM_CLASS);
+ isFormOpen = true;
+ };
+
+ // Hide the form after a delay anytime focus is removed from a form element
+ const onFormBlur = () => {
+ clearTimeout(blurTimeoutId);
+ blurTimeoutId = window.setTimeout(() => {
+ containerEl.classList.remove(SHOW_FORM_CLASS);
+ isFormOpen = false;
+ }, BLUR_TIMEOUT);
+ };
+
+ // Handler for the button click
+ const onGetWeeklyDigestsClick = (event: Event) => {
+ event.preventDefault();
+ containerEl.classList.add(SHOW_FORM_CLASS);
+ isFormOpen = true;
+ // Jump the focus to the first input element
+ formEl.querySelector('input')?.focus();
+ };
+
+ // Handler for the Escape keydown event
+ const onKeydown = (event: KeyboardEvent) => {
+ // We need to hide the form and reset the focus, we can get both by setting
+ // the focus back to the "Get Weekly Digests" link.
+ if (event.key === 'Escape' && isFormOpen) {
+ (getWeeklyDigestsBtn as HTMLElement).focus();
+ }
+ };
+
+ // Clean up event listeners
+ const destroy = () => {
+ getWeeklyDigestsBtn.removeEventListener('click', onGetWeeklyDigestsClick);
+ for (const formFocusableEl of formFocusableEls) {
+ formFocusableEl.removeEventListener('blur', onFormBlur);
+ formFocusableEl.removeEventListener('focus', onFormFocus);
+ }
+ for (const controlEl of controlEls) {
+ controlEl.removeEventListener('focus', onControlFocus);
+ }
+ document.removeEventListener('keydown', onKeydown);
+ };
+
+ // Set up all event listeners
+ const init = () => {
+ getWeeklyDigestsBtn.addEventListener('click', onGetWeeklyDigestsClick);
+ for (const formFocusableEl of formFocusableEls) {
+ formFocusableEl.addEventListener('blur', onFormBlur);
+ formFocusableEl.addEventListener('focus', onFormFocus);
+ }
+ for (const controlEl of controlEls) {
+ controlEl.addEventListener('focus', onControlFocus);
+ }
+ document.addEventListener('keydown', onKeydown);
+ };
+
+ init();
+
+ // Return a public API for consumers of this component
+ return { destroy };
+};
diff --git a/src/components/subscribe/subscribe.twig b/src/components/subscribe/subscribe.twig
new file mode 100644
index 000000000..559ece996
--- /dev/null
+++ b/src/components/subscribe/subscribe.twig
@@ -0,0 +1,74 @@
+{% set _heading_tag = heading_tag|default('h2') %}
+
+
+ <{{_heading_tag}}>
+ {{never_miss_article_heading|default('Never miss an article!')}}
+ {{_heading_tag}}>
+
+
+ {% include '@cloudfour/components/button-swap/button-swap.twig' with {
+ button_class: [
+ 'c-subscribe__control',
+ 'js-subscribe__control',
+ notifications_btn_class|default('')
+ ]|join(' '),
+ initial_label: notifications_btn_initial_label|default('Notifications have been turned off.'),
+ initial_visual_label: notifications_btn_initial_visual_label|default('Get notifications'),
+ swapped_label: notifications_btn_swapped_label|default('Notifications have been turned on.'),
+ swapped_visual_label: notifications_btn_swapped_visual_label|default('Turn off notifications'),
+ } only %}
+
+ {% include '@cloudfour/components/button/button.twig' with {
+ href: "#subscribe-#{form_id}",
+ class: [
+ 'c-subscribe__control',
+ 'js-subscribe__control',
+ 'js-subscribe__get-weekly-digests-btn',
+ weekly_digests_btn_class|default('')
+ ]|join(' '),
+ label: weekly_digests_btn_label|default('Get Weekly Digests'),
+ content_start_icon: weekly_digests_btn_icon|default('envelope')
+ } only %}
+
+
+
+
diff --git a/src/objects/input-group/input-group.stories.mdx b/src/objects/input-group/input-group.stories.mdx
index 9643d3503..3dc578247 100644
--- a/src/objects/input-group/input-group.stories.mdx
+++ b/src/objects/input-group/input-group.stories.mdx
@@ -1,4 +1,4 @@
-import { Story, Canvas, Meta } from '@storybook/addon-docs';
+import { Story, Canvas, Meta, ArgsTable } from '@storybook/addon-docs';
// The '!!raw-loader!' syntax is a non-standard, Webpack-specific, syntax.
// See: https://github.com/webpack-contrib/raw-loader#examples
// For now, it seems likely Storybook is pretty tied to Webpack, therefore, we are
@@ -9,9 +9,21 @@ import { Story, Canvas, Meta } from '@storybook/addon-docs';
// eslint-disable-next-line @cloudfour/import/no-webpack-loader-syntax
import inputGroupDemoSource from '!!raw-loader!./demo/input-group-demo.twig';
import inputGroupDemo from './demo/input-group-demo.twig';
-import './input-group.scss';
-
+
# Input Group
@@ -30,6 +42,10 @@ Buttons and inputs can be put in whatever order you choose.
},
}}
>
- {inputGroupDemo}
+ {(args) => inputGroupDemo(args)}
+
+## Template Properties
+
+
diff --git a/src/objects/input-group/input-group.twig b/src/objects/input-group/input-group.twig
index 802652fa8..01a8da79b 100644
--- a/src/objects/input-group/input-group.twig
+++ b/src/objects/input-group/input-group.twig
@@ -1,3 +1,3 @@
-