From 27396d76ec14b9d9446110dee05843e6d5d3139d Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 1 Aug 2023 14:49:59 -0500 Subject: [PATCH 1/5] docs: include note for recommending TooltipTabstop over Tooltip (#1142) --- docs/pages/components/Tooltip.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/pages/components/Tooltip.mdx b/docs/pages/components/Tooltip.mdx index f20858425..b8ddcb9e3 100644 --- a/docs/pages/components/Tooltip.mdx +++ b/docs/pages/components/Tooltip.mdx @@ -15,6 +15,12 @@ import { Tooltip } from '@deque/cauldron-react' Cauldron's tooltip relies on [Popper](https://popper.js.org/) to position tooltips dynamically. Tooltips can be triggered from any focusable element via a `target` attribute pointed to an HTMLElement or React ref object. + + +`Tooltip` is intended to be used with elements that are interactive or are keyboard accessible such as buttons or links. For non-interactive elements such as informational tooltips attached to an icon, consider using [TooltipTabstop](./TooltipTabstop) instead. + + + ### Text Tooltips Text Tooltips are the default variant and are intended to show short single-line text hints. From 10f53c90fe2466f913bb00c49fc0b8e1aad501b1 Mon Sep 17 00:00:00 2001 From: Yahya Hafez <60084578+yhafez@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:38:01 -0700 Subject: [PATCH 2/5] fix: fixes overflow bug on text-field and text-area (#1143) fix: adjust min-width property of text-field and text-area --- packages/styles/forms.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/styles/forms.css b/packages/styles/forms.css index f4a3cae41..322a069b6 100644 --- a/packages/styles/forms.css +++ b/packages/styles/forms.css @@ -310,7 +310,7 @@ textarea.Field--has-error:focus:hover, box-sizing: border-box; font-size: var(--text-size-small); color: var(--field-content-color); - min-width: var(--input-min-width); + min-width: min(var(--input-min-width), 100%); } .Field__text-input[disabled], @@ -323,7 +323,7 @@ textarea.Field--has-error:focus:hover, display: block; min-height: 56px; font-size: var(--text-size-small); - min-width: var(--input-min-width); + min-width: min(var(--input-min-width), 100%); padding: var(--space-half); max-width: 500px; color: var(--field-content-color); From b103453db0f59b2498de2653c5c5d88328c5bb99 Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 2 Aug 2023 10:01:46 -0500 Subject: [PATCH 3/5] feat(react): allow for NavBar to include additional props for html div elements. (#1144) --- docs/pages/components/NavBar.mdx | 119 ++++++++++++++++++ docs/pages/components/TopBar.mdx | 1 + docs/patterns/components/NavBar/index.css | 7 -- docs/patterns/components/NavBar/index.js | 98 --------------- .../react/src/components/NavBar/NavBar.tsx | 7 +- 5 files changed, 124 insertions(+), 108 deletions(-) create mode 100644 docs/pages/components/NavBar.mdx delete mode 100644 docs/patterns/components/NavBar/index.css delete mode 100644 docs/patterns/components/NavBar/index.js diff --git a/docs/pages/components/NavBar.mdx b/docs/pages/components/NavBar.mdx new file mode 100644 index 000000000..5c04fb237 --- /dev/null +++ b/docs/pages/components/NavBar.mdx @@ -0,0 +1,119 @@ +--- +title: NavBar +description: A navigation bar that contains links to other sections of the website. +source: https://github.com/dequelabs/cauldron/tree/develop/packages/react/src/components/NavBar/NavBar.tsx +--- + +import { NavBar, NavItem } from '@deque/cauldron-react' + +```js +import { NavBar, NavItem } from '@deque/cauldron-react' +``` + +The `NavBar` component is intended to be placed directly below the `TopBar` component to display an additional section of navigational links for the current area. + +Under the hood, `NavBar` renders inside of a `nav` element. If there are multiple `nav` elements present on the page be sure to include `aria-label` or `aria-labelledby` to help uniquely identify each nav region. + +## Examples + +### Default + +```jsx example + + + One + + + Two + + + Three + + +``` + +### NavBar with Active NavItem + +```jsx example + + + One + + + Two + + + Three + + +``` + +### Collapsed + +```jsx example + + + One + + + Two + + + Three + + +``` + +## Props + +### NavBar + + + +### NavItem + + + +## Related Components + +- [TopBar](./TopBar) \ No newline at end of file diff --git a/docs/pages/components/TopBar.mdx b/docs/pages/components/TopBar.mdx index daef6027a..ff6373558 100644 --- a/docs/pages/components/TopBar.mdx +++ b/docs/pages/components/TopBar.mdx @@ -153,3 +153,4 @@ In addition to navigational items, the `MenuBar` can also contain a dropdown men ## Related Components - [OptionsMenu](./OptionsMenu) +- [NavBar](./NavBar) diff --git a/docs/patterns/components/NavBar/index.css b/docs/patterns/components/NavBar/index.css deleted file mode 100644 index e0f57602a..000000000 --- a/docs/patterns/components/NavBar/index.css +++ /dev/null @@ -1,7 +0,0 @@ -.NavBarButton { - margin-top: var(--space-large); -} - -.NavBar--collapsed { - width: 20rem; -} diff --git a/docs/patterns/components/NavBar/index.js b/docs/patterns/components/NavBar/index.js deleted file mode 100644 index 3646f2607..000000000 --- a/docs/patterns/components/NavBar/index.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useState } from 'react'; -import { NavBar, NavItem, Code, Button } from '@deque/cauldron-react'; -import './index.css'; -import { className } from '../../../props'; -import PropDocs from '../../../Demo/PropDocs'; - -const Demo = () => { - const [isMobile, setIsMobile] = useState(false); - const componentsList = new Array(5).fill('NavItem'); - const handleToggle = () => { - setIsMobile(!isMobile); - }; - return ( -
-

NavBar

-

Component Description

-

- A navigation bar that contains links to other sections of the website. -

-

Demo

- - {componentsList.map((name, index) => { - return ( - - {`${name} ${index + 1}`} - - ); - })} - - -

Code Sample

- - {` -import React, { useState } from 'react'; -import { NavBar, NavItem, Code, Button } from '@deque/cauldron-react'; -import './index.css'; - -const Demo = () => { - const [isMobile, setIsMobile] = useState(false); - const componentsList = new Array(5).fill('NavItem'); - const handleToggle = () => { - setIsMobile(!isMobile); - }; - return ( -
-

NavBar

-

Demo

-

Basic NavBar

- - {componentsList.map((name, index) => { - return ( - - {\`\${name} \${index + 1}\`} - - ); - })} - - -
- `} -
-
- -
-
- ); -}; - -export default Demo; diff --git a/packages/react/src/components/NavBar/NavBar.tsx b/packages/react/src/components/NavBar/NavBar.tsx index 5ecfdb7fd..64118132d 100644 --- a/packages/react/src/components/NavBar/NavBar.tsx +++ b/packages/react/src/components/NavBar/NavBar.tsx @@ -9,9 +9,8 @@ import PropTypes from 'prop-types'; import Icon from '../Icon'; import { useId } from 'react-id-generator'; -interface NavBarProps { +interface NavBarProps extends React.HTMLAttributes { children: React.ReactNode; - initialActiveIndex?: number; className?: string; collapsed?: boolean; navTriggerLabel?: string; @@ -23,7 +22,8 @@ const NavBar = ({ className, collapsed = false, navTriggerLabel = 'MAIN MENU', - propId + propId, + ...props }: NavBarProps) => { const navRef = useRef(null); const triggerRef = useRef(null); @@ -69,6 +69,7 @@ const NavBar = ({ 'NavBar--collapsed': collapsed })} ref={navRef} + {...props} > {collapsed && ( + + ); +} +``` + +## Props + + + +## Related Components + +- [Toast](./Toast) diff --git a/docs/pages/components/Toast.mdx b/docs/pages/components/Toast.mdx index 1a3d484f6..fd6d6e797 100644 --- a/docs/pages/components/Toast.mdx +++ b/docs/pages/components/Toast.mdx @@ -4,41 +4,35 @@ description: A banner with text positioned at the top of the page. source: https://github.com/dequelabs/cauldron/tree/develop/packages/react/src/components/Toast/index.tsx --- -import { useState } from 'react' -import { Toast, Button } from '@deque/cauldron-react' +import { useState } from 'react'; +import { Toast, Button } from '@deque/cauldron-react'; ```js -import { Toast } from '@deque/cauldron-react' +import { Toast } from '@deque/cauldron-react'; ``` ## Examples - Only a single toast should be rendered at a given moment as additional toasts will stack on top of each other. + Only a single toast should be rendered at a given moment as additional toasts + will stack on top of each other. ### Confirmation ```jsx example function ConfirmationToastExample() { - const [show, setShow] = useState(false) + const [show, setShow] = useState(false); return ( <> - setShow(false)} - > + setShow(false)}> Your toast is ready! - - ) + ); } ``` @@ -46,24 +40,17 @@ function ConfirmationToastExample() { ```jsx example function CautionToastExample() { - const [show, setShow] = useState(false) + const [show, setShow] = useState(false); return ( <> - setShow(false)} - > + setShow(false)}> Your toast is getting toasty... - - ) + ); } ``` @@ -71,24 +58,20 @@ function CautionToastExample() { ```jsx example function ActionNeededToastExample() { - const [show, setShow] = useState(false) + const [show, setShow] = useState(false); return ( <> - + You burnt the toast! Check yourself before you wreck yourself... - + - - ) + ); } ``` @@ -96,24 +79,17 @@ function ActionNeededToastExample() { ```jsx example function InfoToastExample() { - const [show, setShow] = useState(false) + const [show, setShow] = useState(false); return ( <> - setShow(false)} - > + setShow(false)}> It is getting toasty in here! - - ) + ); } ``` @@ -121,24 +97,17 @@ function InfoToastExample() { ```jsx example function ErrorToastExample() { - const [show, setShow] = useState(false) + const [show, setShow] = useState(false); return ( <> - setShow(false)} - > + setShow(false)}> This toast tastes like toast! - - ) + ); } ``` @@ -146,31 +115,25 @@ function ErrorToastExample() { Non-dismissable toasts are shown inline to prevent clipping of content. - ```jsx example function NonDismissibleToastExample() { - const [show, setShow] = useState(true) + const [show, setShow] = useState(true); return ( <> - - This toast is not dismissible by normal methods. - But you can . + + This toast is not dismissible by normal methods. But you can{' '} + + . {!show && ( - )} - ) + ); } ``` @@ -189,13 +152,14 @@ function NonDismissibleToastExample() { name: 'show', type: 'boolean', defaultValue: 'false', - description: 'Whether or not to show the toast.', + description: 'Whether or not to show the toast.' }, { name: 'focus', type: 'boolean', defaultValue: 'true', - description: 'Whether or not to focus the toast. This will also set the toast to `role="alert"` when set to true.' + description: + 'Whether or not to focus the toast. This will also set the toast to `role="alert"` when set to true.' }, { name: 'onDismiss', @@ -221,3 +185,7 @@ function NonDismissibleToastExample() { } ]} /> + +## Related + +- [Notice](./Notice) diff --git a/packages/react/__tests__/src/components/Notice/index.js b/packages/react/__tests__/src/components/Notice/index.js new file mode 100644 index 000000000..a0e309b58 --- /dev/null +++ b/packages/react/__tests__/src/components/Notice/index.js @@ -0,0 +1,95 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Notice from 'src/components/Notice'; +import axe from '../../../axe'; + +test('handles rendering without errors', done => { + const wrapper = mount(); + expect(wrapper.find('Notice').length).toBe(1); + done(); +}); + +test('should render with defaults when no props passed in', async () => { + const wrapper = mount(child); + + expect(wrapper.find('.Notice').length).toBe(1); +}); +test('should render the correct default icon for a given `type`', async () => { + const cautionNotice = mount(); + + expect(cautionNotice.find('Icon').prop('type')).toBe('caution'); +}); + +test('should return no axe violations', async () => { + const info = mount( + + bar + + ); + + const caution = mount( + + bar + + ); + + const danger = mount( + + bar + + ); + + const infoAxeResults = await axe(info.html()); + const cautionAxeResults = await axe(caution.html()); + const dangerAxeResults = await axe(danger.html()); + expect(infoAxeResults).toHaveNoViolations(); + expect(cautionAxeResults).toHaveNoViolations(); + expect(dangerAxeResults).toHaveNoViolations(); +}); + +test('should return correctly with props passed in', async () => { + const wrapper = mount( + + bar + + ); + + expect(wrapper.find('Notice').length).toBe(1); + expect(wrapper.prop('title')).toBe('foo'); + expect(wrapper.prop('type')).toBe('info'); + expect(wrapper.prop('children')).toBe('bar'); +}); + +test('should render with the correct icon when a valid icon `type` string is passed in', async () => { + const wrapper = mount(); + + expect(wrapper.find('Notice').length).toBe(1); + expect(wrapper.find('Icon').prop('type')).toBe('bolt'); + expect(wrapper.find('.Icon--bolt').length).toBe(1); +}); + +test('should render only a `title` when no children are passed in', async () => { + const wrapper = mount(); + + expect(wrapper.find('Notice').length).toBe(1); + expect(wrapper.find('.Notice__title').contains('foo')).toBeTruthy(); +}); + +test('`title` prop should allow for any valid ContentNode element', async () => { + const wrapper = mount( + foo}> + bar + + ); + + expect(wrapper.find('Notice').length).toBe(1); + expect(wrapper.contains(

foo

)).toBeTruthy(); +}); + +test('should allow a ref to be forwarded', async () => { + const ref = React.createRef(); + const wrapper = mount(); + + expect(wrapper.find('Notice').length).toBe(1); + expect(ref.current).toBeTruthy(); +}); diff --git a/packages/react/src/components/Notice/index.tsx b/packages/react/src/components/Notice/index.tsx new file mode 100644 index 000000000..fb47030c3 --- /dev/null +++ b/packages/react/src/components/Notice/index.tsx @@ -0,0 +1,59 @@ +import React, { forwardRef, ReactNode } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import Icon, { IconType } from '../Icon'; +import { ContentNode } from '../../types'; + +const iconTypeMap = { + caution: 'caution', + danger: 'caution', + info: 'info-circle' +}; + +export interface NoticeProps + extends Omit, 'title'> { + type?: keyof typeof iconTypeMap; + title: ContentNode; + icon?: IconType; + children?: ReactNode; +} + +const Notice = forwardRef( + ( + { type = 'info', title, icon, children, ...otherProps }: NoticeProps, + ref + ) => { + return ( +
+
+ + {title} +
+ {children &&
{children}
} +
+ ); + } +); + +Notice.displayName = 'Notice'; +Notice.propTypes = { + // @ts-expect-error + children: PropTypes.node, + type: PropTypes.oneOf(['caution', 'info', 'danger']), + // @ts-expect-error + title: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.element + ]), + // @ts-expect-error + icon: PropTypes.string +}; + +export default Notice; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2c516cc1e..6c87ba59a 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -119,6 +119,7 @@ export { ColumnRight, ColumnList } from './components/TwoColumnPanel'; +export { default as Notice } from './components/Notice'; /** * Helpers / Utils diff --git a/packages/styles/index.css b/packages/styles/index.css index 4e5d12c07..4bf7cf0fc 100644 --- a/packages/styles/index.css +++ b/packages/styles/index.css @@ -40,3 +40,4 @@ @import './breadcrumb.css'; @import './two-column-panel.css'; @import './accordion.css'; +@import './notice.css'; diff --git a/packages/styles/notice.css b/packages/styles/notice.css new file mode 100644 index 000000000..0dba2d9fc --- /dev/null +++ b/packages/styles/notice.css @@ -0,0 +1,86 @@ +:root { + --notice-info-color: var(--accent-info-active); + --notice-caution-color: var(--accent-caution); + --notice-danger-color: var(--accent-warning-light); + --notice-text-color: var(--accent-dark); + --notice-title-text-color: var(--accent-dark); + --notice-title-font-weight: var(--font-weight-medium); + --notice-background-color: var(--notice-info-color); + --notice-border-color: var(--accent-dark); + --notice-link-hover-color: var(--accent-medium); + --notice-icon-size: 1.2em; +} + +.Notice--info { + --notice-background-color: var(--notice-info-color); +} + +.Notice--caution { + --notice-background-color: var(--notice-caution-color); +} + +.Notice--danger { + --notice-background-color: var(--notice-danger-color); +} + +.Notice { + display: block; + padding: var(--space-smaller) var(--space-small); + border: 1px solid var(--notice-border-color); + background-color: var(--notice-background-color); + color: var(--notice-text-color); + width: 100%; + font-size: var(--text-size-smaller); +} + +.Notice .Notice__title, +.Notice .Notice__title > :is(h1, h2, h3, h4, h5, h6) { + display: flex; + align-items: center; + font-size: var(--text-size-small); + font-weight: var(--notice-title-font-weight); + margin: 0; + padding: 0; + gap: var(--space-three-quarters); + color: var(--notice-title-text-color); +} + +.Notice .Notice__title + .Notice__content { + margin-top: var(--space-smallest); +} + +.Notice .Icon > svg { + height: var(--notice-icon-size); + width: var(--notice-icon-size); +} + +.Notice button.Link, +.Notice a.Link { + background: transparent; + border: 0; + color: currentColor; +} + +.Notice button.Link, +.Notice a.Link { + color: var(--accent-dark); + font-size: var(--text-size-small); + font-weight: var(--font-weight-light); + text-decoration: underline; +} + +.Notice button.Link:focus, +.Notice a.Link:focus { + outline: 2px solid; + color: var(--gray-90); +} + +.Notice button.Link:hover, +.Notice a.Link:hover { + color: var(--notice-link-hover-color); +} + +.Notice .Notice__content > p:first-of-type { + margin-top: 0; + margin-bottom: var(--space-smallest); +} diff --git a/packages/styles/toast.css b/packages/styles/toast.css index 92089be95..2d5591120 100644 --- a/packages/styles/toast.css +++ b/packages/styles/toast.css @@ -82,6 +82,11 @@ outline-offset: var(--space-quarter); } +.Toast__message button.Link:hover, +.Notice a.Link:hover { + color: var(--accent-medium); +} + .Toast:focus { outline: 0; } diff --git a/packages/styles/variables.css b/packages/styles/variables.css index df184f43c..0252bd6f3 100644 --- a/packages/styles/variables.css +++ b/packages/styles/variables.css @@ -34,6 +34,7 @@ --accent-warning-dark: #b88a00; --accent-info: #6cdaf2; --accent-info-light: #83e4fa; + --accent-info-active: #a7e9f7; --accent-primary: #3c7aae; --accent-primary-active: #316091; --accent-secondary: var(--gray-20); From f77599b4d458b8d2d75c42a5936fe63a21c26852 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Aug 2023 19:36:49 +0000 Subject: [PATCH 5/5] chore(cauldron): Release 5.7.0 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/react/package.json | 2 +- packages/styles/package.json | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59f785c21..53c253159 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [5.7.0](https://github.com/dequelabs/cauldron/compare/v5.6.2...v5.7.0) (2023-08-02) + + +### Features + +* **react:** allow for NavBar to include additional props for html div elements. ([#1144](https://github.com/dequelabs/cauldron/issues/1144)) ([b103453](https://github.com/dequelabs/cauldron/commit/b103453db0f59b2498de2653c5c5d88328c5bb99)) +* **react,styles:** add Notice component ([#1136](https://github.com/dequelabs/cauldron/issues/1136)) ([14bfe32](https://github.com/dequelabs/cauldron/commit/14bfe32d8763a2a64e821823e5bd3a2befa76cf6)) + + +### Bug Fixes + +* fixes overflow bug on text-field and text-area ([#1143](https://github.com/dequelabs/cauldron/issues/1143)) ([10f53c9](https://github.com/dequelabs/cauldron/commit/10f53c90fe2466f913bb00c49fc0b8e1aad501b1)) + ### [5.6.2](https://github.com/dequelabs/cauldron/compare/v5.6.1...v5.6.2) (2023-07-28) diff --git a/package.json b/package.json index 6de845898..a1b3838ec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cauldron", "private": true, - "version": "5.6.2", + "version": "5.7.0", "license": "MPL-2.0", "scripts": { "clean": "rimraf dist docs/dist", diff --git a/packages/react/package.json b/packages/react/package.json index e8e072555..2c52b19f4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@deque/cauldron-react", - "version": "5.6.2", + "version": "5.7.0", "description": "Fully accessible react components library for Deque Cauldron", "homepage": "https://cauldron.dequelabs.com/", "publishConfig": { diff --git a/packages/styles/package.json b/packages/styles/package.json index 13e2a3548..68441759f 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -1,6 +1,6 @@ { "name": "@deque/cauldron-styles", - "version": "5.6.2", + "version": "5.7.0", "license": "MPL-2.0", "description": "deque cauldron pattern library styles", "repository": "https://github.com/dequelabs/cauldron",