diff --git a/src/components/input/OptionButton.tsx b/src/components/input/OptionButton.tsx
new file mode 100644
index 00000000..6c887946
--- /dev/null
+++ b/src/components/input/OptionButton.tsx
@@ -0,0 +1,76 @@
+import classnames from 'classnames';
+import type { ComponentChildren } from 'preact';
+
+import { CheckIcon } from '../icons/';
+import Button from './Button';
+import type { ButtonProps } from './Button';
+
+export type OptionButtonProps = {
+ /** Optional content to render at the right side of the button */
+ details?: ComponentChildren;
+ /** alias for `pressed`: this option button is selected **/
+ selected?: boolean;
+} & Omit;
+
+/**
+ * Render a button representing one of a set of options, with optional
+ * right-aligned `details` content
+ */
+const OptionButton = function OptionButton({
+ children,
+ details,
+ selected = false,
+
+ pressed,
+ ...buttonProps
+}: OptionButtonProps) {
+ const isPressed = selected || pressed;
+ return (
+
+ );
+};
+
+export default OptionButton;
diff --git a/src/components/input/index.ts b/src/components/input/index.ts
index 36a53a28..b31b55cc 100644
--- a/src/components/input/index.ts
+++ b/src/components/input/index.ts
@@ -4,6 +4,7 @@ export { default as Checkbox } from './Checkbox';
export { default as IconButton } from './IconButton';
export { default as Input } from './Input';
export { default as InputGroup } from './InputGroup';
+export { default as OptionButton } from './OptionButton';
export { default as Select } from './Select';
export type { ButtonProps } from './Button';
@@ -12,4 +13,5 @@ export type { CheckboxProps } from './Checkbox';
export type { IconButtonProps } from './IconButton';
export type { InputProps } from './Input';
export type { InputGroupProps } from './InputGroup';
+export type { OptionButtonProps } from './OptionButton';
export type { SelectProps } from './Select';
diff --git a/src/components/input/test/OptionButton-test.js b/src/components/input/test/OptionButton-test.js
new file mode 100644
index 00000000..a2dbd41f
--- /dev/null
+++ b/src/components/input/test/OptionButton-test.js
@@ -0,0 +1,42 @@
+import { mount } from 'enzyme';
+
+import { testSimpleComponent } from '../../test/common-tests';
+import OptionButton from '../OptionButton';
+
+describe('OptionButton', () => {
+ const createComponent = (props = {}) => {
+ return mount(
+ This is content inside of a Button
+ );
+ };
+
+ testSimpleComponent(OptionButton);
+
+ it('applies appropriate ARIA attributes for button state', () => {
+ const pressed = createComponent({ pressed: true });
+ const selected = createComponent({ selected: true });
+
+ assert.equal(
+ pressed.find('button').getDOMNode().getAttribute('aria-pressed'),
+ 'true'
+ );
+ assert.equal(
+ selected.find('button').getDOMNode().getAttribute('aria-pressed'),
+ 'true'
+ );
+ });
+
+ it('renders optional details content', () => {
+ const noDetails = createComponent();
+ const withDetails = createComponent({ details: 'PDF' });
+
+ function getDetails(wrapper) {
+ return wrapper.find('[data-testid="option-button-details"]');
+ }
+
+ assert.isNotOk(getDetails(noDetails).exists());
+ const details = getDetails(withDetails);
+ assert.isOk(details);
+ assert.equal(details.text(), 'PDF');
+ });
+});
diff --git a/src/index.ts b/src/index.ts
index 90302ec5..5abe7014 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -36,6 +36,7 @@ export {
IconButton,
Input,
InputGroup,
+ OptionButton,
Select,
} from './components/input';
export {
@@ -97,6 +98,7 @@ export type {
IconButtonProps,
InputProps,
InputGroupProps,
+ OptionButtonProps,
SelectProps,
} from './components/input';
diff --git a/src/pattern-library/components/patterns/input/OptionButtonPage.tsx b/src/pattern-library/components/patterns/input/OptionButtonPage.tsx
new file mode 100644
index 00000000..0a314786
--- /dev/null
+++ b/src/pattern-library/components/patterns/input/OptionButtonPage.tsx
@@ -0,0 +1,145 @@
+import { OptionButton } from '../../../../';
+import Library from '../../Library';
+
+export default function OptionButtonPage() {
+ return (
+
+ OptionButton is a simple component for presenting an
+ option as part of a set of options. It can be used in places where it
+ is impractical or undesirable to use an equivalent{' '}
+ {`input type="radio"`} or select element.{' '}
+ OptionButton wraps{' '}
+
+ Button
+
+ .
+
+ }
+ >
+
+
+
+
+
+
+ Option Alpha
+
+
+
+
+
+
+
+
+ OptionButton is designed for use in a set, similar to
+ an option element or a radio button. While not
+ enforced, a maximum of one OptionButton in a set
+ should be selected. There is also disabled
+ styling.
+
+
+ To facilitate alignment, OptionButton stretches to
+ the full width of its container.
+
+
+
+
+
+
+
+
+
+ Optional content to display at right side of button. Be sure to
+ set a useful title (used for generating{' '}
+ aria-label) when constructing such buttons.
+
+
+ preact.ComponentChildren
+
+
+
+
+
+
+ Google Drive
+
+
+
+
+
+
+
+
+ The option represented by the button is selected. A maximum of
+ one OptionButton in a set should be selected at any
+ time. This is an alias for the Button{' '}
+ pressed prop.
+
+
+ boolean
+
+
+ false
+
+
+
+
+
+
+ Google Drive
+
+
+
+
+
+
+
+
+ OptionButton accepts and forwards all{' '}
+
+ Button
+ {' '}
+ component API props. Styling API props are not forwarded.
+
+
+ All Button props except styling API props:
+
+
+ classes
+
+
+ unstyled
+
+
+ variant
+
+
+ size
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pattern-library/components/patterns/prototype/LMSContentButtonPage.tsx b/src/pattern-library/components/patterns/prototype/LMSContentButtonPage.tsx
index 99dc6f35..c91f77a8 100644
--- a/src/pattern-library/components/patterns/prototype/LMSContentButtonPage.tsx
+++ b/src/pattern-library/components/patterns/prototype/LMSContentButtonPage.tsx
@@ -1,62 +1,8 @@
-import classnames from 'classnames';
import type { ComponentChildren } from 'preact';
-import { Button, Card, CardHeader, CardContent, CheckIcon } from '../../../../';
-import type { ButtonProps } from '../../../../';
+import { Card, CardHeader, CardContent, OptionButton } from '../../../../';
import Library from '../../Library';
-type ContentButtonProps = { contentType: string } & ButtonProps;
-
-function ContentButton({
- children,
- classes,
- contentType = 'pdf',
- pressed,
- ...buttonProps
-}: ContentButtonProps) {
- return (
-
- );
-}
-
/**
* A relatively simplified layout representation of the LMS content-selection
* panel
@@ -86,15 +32,20 @@ function ContentPanel({ children }: { children: ComponentChildren }) {
);
}
-export default function LMSContentButtonPage() {
+export default function LMSOptionButtonPage() {
return (
- The proposed LMS ContentButton encapsulates a new button
- design pattern for the assignment content-configuration screen in LMS.
- It is based on a selected design approach in the{' '}
+ The new{' '}
+
+ OptionButton
+ {' '}
+ encapsulates a new button design pattern for selecting an option from
+ a list of options. The assignment content-configuration screen in LMS
+ will use this new button. It is based on a selected design approach in
+ the{' '}
set of available sketches
@@ -103,94 +54,56 @@ export default function LMSContentButtonPage() {
}
>
-
-
-
-
+
-
- NB: There is currently no use of disabled or{' '}
- pressed states in the LMS content-selection
- interface. But the new button provides styling for those states.
-
+
+ NB: There is currently no use of disabled or{' '}
+ pressed states in the LMS content-selection interface.
+ But the new button provides styling for those states.
+
-
-