From ac321364ef046a150125400f08602012c0c937db Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 31 Oct 2024 11:13:42 +0100 Subject: [PATCH 1/2] Remove old prototypes which already served their purpose --- .../FakeAnnotationPublishControl.tsx | 139 --- .../prototype/LMSContentButtonPage.tsx | 111 --- .../prototype/LMSContentSelectionPage.tsx | 790 ------------------ .../components/patterns/prototype/Menu.tsx | 258 ------ .../patterns/prototype/MenuArrow.tsx | 32 - .../patterns/prototype/MenuItem.tsx | 299 ------- .../patterns/prototype/MenuSection.tsx | 44 - .../prototype/SharedAnnotationsPage.tsx | 312 ------- .../patterns/prototype/TabbedDialogPage.tsx | 322 ------- src/pattern-library/routes.ts | 28 - 10 files changed, 2335 deletions(-) delete mode 100644 src/pattern-library/components/patterns/prototype/FakeAnnotationPublishControl.tsx delete mode 100644 src/pattern-library/components/patterns/prototype/LMSContentButtonPage.tsx delete mode 100644 src/pattern-library/components/patterns/prototype/LMSContentSelectionPage.tsx delete mode 100644 src/pattern-library/components/patterns/prototype/Menu.tsx delete mode 100644 src/pattern-library/components/patterns/prototype/MenuArrow.tsx delete mode 100644 src/pattern-library/components/patterns/prototype/MenuItem.tsx delete mode 100644 src/pattern-library/components/patterns/prototype/MenuSection.tsx delete mode 100644 src/pattern-library/components/patterns/prototype/SharedAnnotationsPage.tsx delete mode 100644 src/pattern-library/components/patterns/prototype/TabbedDialogPage.tsx diff --git a/src/pattern-library/components/patterns/prototype/FakeAnnotationPublishControl.tsx b/src/pattern-library/components/patterns/prototype/FakeAnnotationPublishControl.tsx deleted file mode 100644 index 4a83c9bde..000000000 --- a/src/pattern-library/components/patterns/prototype/FakeAnnotationPublishControl.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import classnames from 'classnames'; - -import { - Button, - CancelIcon, - GlobeIcon, - GroupsIcon, - InfoIcon, - LockIcon, - MenuExpandIcon, -} from '../../../..'; -import Menu from './Menu'; -import MenuItem from './MenuItem'; - -export type AnnotationPublishControlProps = { - /** The group this annotation or draft would publish to */ - group: string; - - /** - * Should the save button be disabled? Hint: it will be if the annotation has - * no content - */ - isDisabled?: boolean; - - /** Annotation or draft is "Only Me" */ - isPrivate?: boolean; - - noSharing?: boolean; - - noSharingMessage?: string; - - /** Callback for cancel button click */ - onCancel?: () => void; - - /** Callback for save button click */ - onSave?: () => void; -}; - -/** - * Render a compound control button for publishing (saving) an annotation: - * - Save the annotation — left side of button - * - Choose sharing/privacy option - drop-down menu on right side of button - * - * @param {AnnotationPublishControlProps} props - */ -function AnnotationPublishControl({ - group, - isDisabled = false, - isPrivate = false, - noSharing = false, - noSharingMessage = "Why can't I share this annotation?", - onCancel = () => {}, - onSave = () => {}, -}: AnnotationPublishControlProps) { - const menuLabel = ( -
- -
- ); - - return ( -
-
- - {/* This wrapper div is necessary because of peculiarities with - Safari: see https://github.com/hypothesis/client/issues/2302 */} -
- - - - {noSharing && ( -
-
-
- -
-
- {noSharingMessage} -
-
-
- )} - -
-
-
-
- -
-
- ); -} - -export default AnnotationPublishControl; diff --git a/src/pattern-library/components/patterns/prototype/LMSContentButtonPage.tsx b/src/pattern-library/components/patterns/prototype/LMSContentButtonPage.tsx deleted file mode 100644 index c91f77a89..000000000 --- a/src/pattern-library/components/patterns/prototype/LMSContentButtonPage.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import type { ComponentChildren } from 'preact'; - -import { Card, CardHeader, CardContent, OptionButton } from '../../../../'; -import Library from '../../Library'; - -/** - * A relatively simplified layout representation of the LMS content-selection - * panel - */ -function ContentPanel({ children }: { children: ComponentChildren }) { - return ( - - - -
-
-
- Assignment content -
-
-

Select content for your assignment

-
-
-
-
- {children} -
-
-
-
-
- ); -} - -export default function LMSOptionButtonPage() { - return ( - - 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 - - . -

- } - > - - - -
- - URL - Canvas - Google Drive - JSTOR - OneDrive - VitalSource - YouTube - -
-
- - - 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. - - - -
- - URL - Canvas - - Google Drive - - JSTOR - OneDrive - VitalSource - YouTube - -
-
- - -
- - URL - Canvas - Google Drive - OneDrive - - VitalSource - - -
-
-
-
-
- ); -} diff --git a/src/pattern-library/components/patterns/prototype/LMSContentSelectionPage.tsx b/src/pattern-library/components/patterns/prototype/LMSContentSelectionPage.tsx deleted file mode 100644 index c5d6d54f8..000000000 --- a/src/pattern-library/components/patterns/prototype/LMSContentSelectionPage.tsx +++ /dev/null @@ -1,790 +0,0 @@ -import classnames from 'classnames'; -import type { ComponentChildren } from 'preact'; - -import { - ArrowRightIcon, - Button, - Card, - CardHeader, - CardContent, - FilePdfIcon, - FilePdfFilledIcon, - GoogleDriveIcon, - IconButton, - InputGroup, - Input, - LinkIcon, - OneDriveIcon, - VitalSourceIcon, -} from '../../../../'; -import type { IconComponent } from '../../../../types'; -import Library from '../../Library'; - -function Button1({ children }: { children: ComponentChildren }) { - return ( - - ); -} - -function Button2({ - children, - contentType = 'pdf', -}: { - children: ComponentChildren; - contentType?: string; -}) { - return ( - - ); -} - -function Button3({ - children, - contentType = 'pdf', -}: { - children: ComponentChildren; - contentType?: string; -}) { - return ( - - ); -} - -function Button4({ - children, - contentType = 'pdf', -}: { - children: ComponentChildren; - contentType?: string; -}) { - return ( - - ); -} - -function Button5({ - children, - icon, - contentType = 'pdf', -}: { - children: ComponentChildren; - contentType?: string; - icon?: IconComponent; -}) { - const Icon = icon ?? FilePdfFilledIcon; - return ( - - ); -} - -function Button6({ - children, - icon, - contentType = 'pdf', - selected = false, -}: { - children: ComponentChildren; - contentType?: string; - icon?: IconComponent; - selected?: boolean; -}) { - const Icon = icon ?? FilePdfIcon; - return ( - - ); -} - -export default function LMSContentSelectionPage() { - return ( - - UI problem: There are too many heavy, identical, wordy buttons on the - LMS content-selection screen, and they are starting to exceed - available space. -

- } - > - - - - - - - -
-
- - Assignment content - -
-
-

- You can select content for your assignment from one of - the following sources: -

-
-
- - - - - - -
-
-
-
-
- - - - - - - - -

- It is quick to put the buttons in a two-column grid layout. - However, because of their heavy, dark color, one ends up with an - unpleasant optical illusion between button corners. -

- - - - -
-
- - Assignment content - -
-
-

- You can select content for your assignment from one of - the following sources: -

-
-
- - - - - - -
-
-
-
-
- - - - - - -

- Switching buttons from the primary variant to the{' '} - secondary variant helps the visual artifacts, but - buttons are hard to scan. -

- - - - - -
-
- - Assignment content - -
-
-

- You can select content for your assignment from one of - the following sources: -

-
-
- - - - - - -
-
-
-
-
- - - - - - - - - - -
-
- - Assignment content - -
-
-

- You can select content for your assignment from one of - the following sources: -

-
-
- Enter URL of web page or PDF - Select PDF from Canvas - Select PDF from Google Drive - Select JSTOR article - Select PDF from OneDrive - Select book from VitalSource -
-
-
-
-
- - - - - - -

- As a first step, we can shorten the button text and use some - selective bolding. However, this still feels hard to scan. -

- - - - - -
-
- - Assignment content - -
-
-

- You can select content for your assignment from one of - the following sources: -

-
-
- - URL to web page or PDF - - - PDF from Canvas - - - PDF from Google Drive - - - JSTOR article - - - PDF from OneDrive - - - Book from VitalSource - -
-
-
-
-
- - - - - - -

- We can re-arrange the button text for consistency. This is not - amazing, but may be sensible enough as a starting point. -

- - - - - -
-
- - Assignment content - -
-
-

- You can select content for your assignment from one of - the following sources: -

-
-
- - URL to web page or PDF - - - Canvas PDF - - - Google Drive PDF - - - JSTOR article - - - OneDrive PDF - - - VitalSource book - -
-
-
-
-
- - - - - - -

- We can style the content type (e.g. PDF) quieter and aligned - right. -

- - - - - -
-
- - Assignment content - -
-
-

- You can select content for your assignment from one of - the following sources: -

-
-
- URL - Canvas - Google Drive - JSTOR - OneDrive - VitalSource -
-
-
-
-
-
-
-
- - -

- We can introduce a little subtle color (slate blue), increase - overall spacing, and tighten up the instructive text. The content - source name is emphasized and the content types are quieter. -

- - - - -
-
-
-
- Assignment content -
-
-

Select content for your assignment

-
-
-
-
-
- URL - Canvas - Google Drive - JSTOR - OneDrive - VitalSource -
-
-
-
-
-
-
-
- - -

- Do we really need any explanatory text? -

- - - - -
-
-
-
- Select content -
-
-
-
-
- URL - Canvas - Google Drive - JSTOR - OneDrive - VitalSource -
-
-
-
-
-
-
-
- - -

- It could be convenient to allow URL entry without having to go to - another modal dialog, but it might be too busy of an interface. -

- - -
- - -
-
-
-
- Assignment content -
-
-
-
- -
- - - - -
-
-
- OR select content from these options: -
-
-
- Canvas - Google Drive - JSTOR - OneDrive - VitalSource -
-
-
-
-
-
-
-
-
- - - - -

- {"Let's"} start this round by consolidating some parts of the - ideas in round 1. -

- - - - -
-
-
-
- Assignment content -
-
-

Select content for your assignment

-
-
-
-
-
- URL - Canvas - Google Drive - JSTOR - OneDrive - VitalSource -
-
-
-
-
-
-
-
- - -

- We could add SVG icons to the buttons. Here is shown only - GoogleDrive, VitalSource and OneDrive as creating SVG icons for - all of these content sources would take some time. This sketch - also adds a hover interaction to the buttons. -

- - - Regarding icons: icons in these sketches are - intended to show how icons might be used. I was able to quickly - find icons for GoogleDrive and OneDrive and fumble something - together for VitalSource. We would want to obtain icons for Canvas - and JSTOR if we moved forward, and adjust icon sizing to be more - consistent. - - - - - -
-
-
-
- Assignment content -
-
-

Select content for your assignment

-
-
-
-
-
- - URL - - Canvas - Google Drive - JSTOR - OneDrive - - VitalSource - -
-
-
-
-
-
-
-
- - -

- This sketch demonstrates a possible tile layout, with a{' '} - {'"selected"'} styling (and hover). -

- - - - -
-
-
-
- Content -
-
-

Select content for your assignment

-
-
-
-
-
- - URL - - Canvas - Google Drive - JSTOR - - OneDrive - - - VitalSource - -
-
-
-
-
-
-
-
-
- - - ); -} diff --git a/src/pattern-library/components/patterns/prototype/Menu.tsx b/src/pattern-library/components/patterns/prototype/Menu.tsx deleted file mode 100644 index 1d8b50fb4..000000000 --- a/src/pattern-library/components/patterns/prototype/Menu.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import classnames from 'classnames'; -import type { ComponentChildren } from 'preact'; -import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; - -import { usePopoverShouldClose } from '../../../../'; -import { MenuExpandIcon } from '../../../../'; -import MenuArrow from './MenuArrow'; - -/** - * Flag indicating whether the next click event on the menu's toggle button - * should be ignored, because the action it would trigger has already been - * triggered by a preceding "mousedown" event. - */ -let ignoreNextClick = false; - -export type MenuProps = { - /** - * Whether the menu content is aligned with the left (default) or right edges - * of the toggle element. - */ - align?: 'left' | 'right'; - - /** - * Additional CSS class for the arrow caret at the edge of the menu content - * that "points" toward the menu's toggle button. This can be used to adjust - * the position of that caret respective to the toggle button. - */ - arrowClass?: string; - - /** - * Label element or string for the toggle button that hides and shows the menu - */ - label: ComponentChildren; - - /** Menu content, typically `MenuSection` and `MenuItem` components */ - children: ComponentChildren; - - /** - * Whether the menu elements should be positioned relative to the Menu - * container. When `false`, the consumer is responsible for positioning. - */ - containerPositioned?: boolean; - - /** Additional CSS classes to apply to the Menu */ - contentClass?: string; - - /** - * Whether the menu is open when initially rendered. Ignored if `open` is - * present. - */ - defaultOpen?: boolean; - - /** Whether to render an (arrow) indicator next to the Menu label */ - menuIndicator?: boolean; - - /** Callback when the Menu is opened or closed. */ - onOpenChanged?: (open: boolean) => void; - - /** - * Whether the Menu is currently open, when the Menu is being used as a - * controlled component. In these cases, an `onOpenChanged` handler should - * be provided to respond to the user opening or closing the menu. - */ - open?: boolean; - - /** - * A title for the menu. This is important for accessibility if the menu's - * toggle button has only an icon as a label. - */ - title: string; -}; - -const noop = () => {}; - -/** - * A drop-down menu. - * - * Menus consist of a button which toggles whether the menu is open, an - * an arrow indicating the state of the menu and content when is shown when - * the menu is open. The children of the menu component are rendered as the - * content of the menu when open. Typically this consists of a list of - * `MenuSection` and/or `MenuItem` components. - * - * @example - * - * - * - * - * - * - * - */ -export default function Menu({ - align = 'left', - arrowClass = '', - children, - containerPositioned = true, - contentClass, - defaultOpen = false, - label, - open, - onOpenChanged, - menuIndicator = true, - title, -}: MenuProps) { - let [isOpen, setOpen]: [boolean, (open: boolean) => void] = - useState(defaultOpen); - if (typeof open === 'boolean') { - isOpen = open; - setOpen = onOpenChanged || noop; - } - - // Notify parent when menu is opened or closed. - const wasOpen = useRef(isOpen); - useEffect(() => { - if (typeof onOpenChanged === 'function' && wasOpen.current !== isOpen) { - wasOpen.current = isOpen; - onOpenChanged(isOpen); - } - }, [isOpen, onOpenChanged]); - - /** - * Toggle menu when user presses toggle button. The menu is shown on mouse - * press for a more responsive/native feel but also handles a click event for - * activation via other input methods. - */ - const toggleMenu = (event: Event) => { - // If the menu was opened on press, don't close it again on the subsequent - // mouse up ("click") event. - if (event.type === 'mousedown') { - ignoreNextClick = true; - } else if (event.type === 'click' && ignoreNextClick) { - // Ignore "click" event triggered from the mouse up action. - ignoreNextClick = false; - event.stopPropagation(); - event.preventDefault(); - return; - } - - setOpen(!isOpen); - }; - const closeMenu = useCallback(() => setOpen(false), [setOpen]); - - // Set up an effect which adds document-level event handlers when the menu - // is open and removes them when the menu is closed or removed. - // - // These handlers close the menu when the user taps or clicks outside the - // menu or presses Escape. - const menuRef = useRef(null); - - // Menu element should close via `closeMenu` whenever it's open and there - // are user interactions outside of it (e.g. clicks) in the document - usePopoverShouldClose(menuRef, closeMenu, { enabled: isOpen }); - - const stopPropagation = (e: Event) => e.stopPropagation(); - - // It should also close if the user presses a key which activates menu items. - const handleMenuKeyDown = (event: KeyboardEvent) => { - const key = event.key; - if (key === 'Enter' || key === ' ') { - // The browser will not open the link if the link element is removed - // from within the keypress event that triggers it. Add a little - // delay to work around that. - setTimeout(() => { - closeMenu(); - }); - } - }; - - const containerStyle = { - position: containerPositioned ? 'relative' : 'static', - }; - - return ( - // See https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md#case-the-event-handler-is-only-being-used-to-capture-bubbled-events - // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -
- - {isOpen && ( - <> - -
- {children} -
- - )} -
- ); -} diff --git a/src/pattern-library/components/patterns/prototype/MenuArrow.tsx b/src/pattern-library/components/patterns/prototype/MenuArrow.tsx deleted file mode 100644 index 4c5e3af25..000000000 --- a/src/pattern-library/components/patterns/prototype/MenuArrow.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import classnames from 'classnames'; - -import { PointerDownIcon, PointerUpIcon } from '../../../../'; - -type MenuArrowProps = { - classes?: string; - direction?: 'up' | 'down'; -}; - -/** - * Render a white-filled "pointer" arrow for use in menus and menu-like - * elements - * - * This will set up absolute positioning for this arrow, but the vertical and - * horizontal positioning will need to be tuned in the component using the - * arrow by adding additional utility classes (`classes` prop here). - */ -export default function MenuArrow({ - classes, - direction = 'up', -}: MenuArrowProps) { - const Icon = direction === 'up' ? PointerUpIcon : PointerDownIcon; - return ( - - ); -} diff --git a/src/pattern-library/components/patterns/prototype/MenuItem.tsx b/src/pattern-library/components/patterns/prototype/MenuItem.tsx deleted file mode 100644 index bf0a1de4b..000000000 --- a/src/pattern-library/components/patterns/prototype/MenuItem.tsx +++ /dev/null @@ -1,299 +0,0 @@ -import classnames from 'classnames'; -import type { ComponentChildren, Ref } from 'preact'; -import { useEffect, useRef } from 'preact/hooks'; - -import { CaretUpIcon, MenuExpandIcon } from '../../../../'; -import type { IconComponent } from '../../../../'; - -type SubmenuToggleProps = { - title: string; - isExpanded: boolean; - onToggleSubmenu?: (e: Event) => void; -}; - -function SubmenuToggle({ - title, - isExpanded, - onToggleSubmenu, -}: SubmenuToggleProps) { - // FIXME: Use `MenuCollapseIcon` instead of `CaretUpIcon` once size - // disparities are addressed - const Icon = isExpanded ? CaretUpIcon : MenuExpandIcon; - return ( -
inside of the menu item itself - // but we have a non-standard mechanism with the toggle control - // requiring an onClick event nested inside a "menuitemradio|menuitem". - // Therefore, a static element with a role="none" is necessary here. - role="none" - className={classnames( - // Center content in a 40px square. The entire element is clickable - 'flex flex-col items-center justify-center w-10 h-10', - 'text-grey-6 bg-grey-1', - // Clip the background (color) such that it only shows within the - // content box, which is a 24px rounded square formed by the large - // borders - 'bg-clip-content border-[8px] border-transparent rounded-xl', - // When the menu item is hovered AND this element is hovered, darken - // the text color so it is clear that the toggle is the hovered element - 'group-hover:hover:text-grey-8', - { - // When the submenu is expanded, this element always has a darker - // background color regardless of hover state. - 'bg-grey-4': isExpanded, - // When the parent menu item is hovered, it gets a darker background. - // Make the toggle background darker also. - 'group-hover:bg-grey-3': !isExpanded, - }, - )} - onClick={onToggleSubmenu} - title={title} - > - -
- ); -} - -export type MenuItemProps = { - /** - * URL of the external link to open when this item is clicked. Either the - * `href` or an `onClick` callback should be supplied. - */ - href?: string; - - /** - * Icon to render for this item. This will show to the left of the item label - * unless this is a submenu item, in which case it goes on the right. Ignored - * if this is not a submenu item and `leftChannelContent` is also provided. - */ - icon?: IconComponent; - - /** - * Dim the label to indicate that this item is not currently available. The - * `onClick` callback will still be invoked when this item is clicked and the - * submenu, if any, can still be toggled. - */ - isDisabled?: boolean; - - /** Indicates that the submenu associated with this item is currently open */ - isExpanded?: boolean; - - /** - * Display an indicator to show that this menu item represents something which - * is currently selected/active/focused. - */ - isSelected?: boolean; - - /** - * True if this item is part of a submenu, in which case it is rendered with a - * different style (shaded background) - */ - isSubmenuItem?: boolean; - - /** - * If present, display a button to toggle the sub-menu associated with this - * item and indicate the current state; `true` if the submenu is visible. - * Note. Omit this prop, or set it to null, if there is no `submenu`. - */ - isSubmenuVisible?: boolean; - - label: ComponentChildren; - - /** - * Optional content to render into a left channel. This accommodates small - * non-icon images or spacing and will supersede any provided icon if this - * is not a submenu item. - */ - leftChannelContent?: ComponentChildren; - - onClick?: (e: Event) => void; - onToggleSubmenu?: (e: Event) => void; - /** - * Contents of the submenu for this item. This is typically a list of - * `MenuItem` components with the `isSubmenuItem` prop set to `true`, but can - * include other content as well. The submenu is only rendered if - * `isSubmenuVisible` is `true`. - */ - submenu?: ComponentChildren; -}; - -/** - * An item in a dropdown menu. - * - * Dropdown menu items display an icon, a label and can optionally have a submenu - * associated with them. - * - * When clicked, menu items either open an external link, if the `href` prop - * is provided, or perform a custom action via the `onClick` callback. - * - * The icon can either be an external SVG image, referenced by URL, or the - * name of an icon registered in the application. - * - * For items that have submenus, the `MenuItem` will call the `renderSubmenu` - * prop to render the content of the submenu, when the submenu is visible. - * Note that the `submenu` is not supported for link (`href`) items. - */ -export default function MenuItem({ - href, - icon: Icon, - isDisabled, - isExpanded, - isSelected, - isSubmenuItem, - isSubmenuVisible, - label, - leftChannelContent, - onClick, - onToggleSubmenu, - submenu, -}: MenuItemProps) { - const menuItemRef = useRef(null); - - let focusTimer: number | undefined; - - // menuItem can be either a link or a button - let menuItem; - const hasSubmenuVisible = typeof isSubmenuVisible === 'boolean'; - const isRadioButtonType = typeof isSelected === 'boolean'; - - useEffect(() => { - return () => { - // unmount - clearTimeout(focusTimer); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const onKeyDown = (event: KeyboardEvent) => { - switch (event.key) { - case 'ArrowRight': - if (onToggleSubmenu) { - event.stopPropagation(); - event.preventDefault(); - onToggleSubmenu(event); - } - break; - case 'Enter': - case ' ': - if (onClick) { - // Let event propagate so the menu closes - onClick(event); - } - } - }; - - const renderedIcon = Icon ? : null; - const leftIcon = !isSubmenuItem ? renderedIcon : null; - const rightIcon = isSubmenuItem ? renderedIcon : null; - - const hasLeftChannel = leftChannelContent || isSubmenuItem || !!leftIcon; - const hasRightContent = !!rightIcon; - - const menuItemContent = ( - <> - {hasLeftChannel && ( -
- {leftChannelContent ?? leftIcon} -
- )} - - {label} - - {hasRightContent && ( -
- {rightIcon} -
- )} - {hasSubmenuVisible && ( - - )} - - ); - - const wrapperClasses = classnames( - 'focus-visible-ring ring-inset', - 'w-full min-w-[150px] flex items-center select-none', - 'border-b', - // Set this container as a "group" so that children may style based on its - // layout state. - // See https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state - 'group', - { - 'min-h-[30px] font-normal': isSubmenuItem, - 'min-h-[40px] font-medium': !isSubmenuItem, - 'bg-grey-1 hover:bg-grey-3': isSubmenuItem || isExpanded, - 'bg-white hover:bg-grey-1': !isSubmenuItem && !isExpanded && !isDisabled, - 'bg-grey-0': isDisabled, - // visual "padding" on the right is part of SubmenuToggle when rendered, - // but when not rendering a SubmenuToggle, we need to add some padding here - 'pr-1': !hasSubmenuVisible, - }, - { - // When the item is selected, show a left border to indicate it - 'border-l-[4px] border-l-brand': isSelected, - // Add equivalent padding to border size when not selected. This instead - // of a transparent left border to make focus ring cover the full - // menu item. Otherwise the focus ring will be inset on the left too far. - 'pl-[4px]': !isSelected, - 'border-b-grey-3': isExpanded, - 'border-b-transparent': !isExpanded, - 'text-color-text-light': isDisabled, - 'text-color-text': !isDisabled, - }, - ); - - if (href) { - // The menu item is a link - menuItem = ( - } - className={wrapperClasses} - data-testid="menu-item" - href={href} - target="_blank" - tabIndex={-1} - rel="noopener noreferrer" - role="menuitem" - onKeyDown={onKeyDown} - > - {menuItemContent} - - ); - } else { - // The menu item is a clickable button or radio button. - // In either case there may be an optional submenu. - menuItem = ( -
} - className={wrapperClasses} - data-testid="menu-item" - tabIndex={-1} - onKeyDown={onKeyDown} - onClick={onClick} - role={isRadioButtonType ? 'menuitemradio' : 'menuitem'} - aria-checked={isRadioButtonType ? isSelected : undefined} - aria-haspopup={hasSubmenuVisible} - aria-expanded={hasSubmenuVisible ? isSubmenuVisible : undefined} - > - {menuItemContent} -
- ); - } - return ( - <> - {menuItem} - {hasSubmenuVisible &&
{submenu}
} - - ); -} diff --git a/src/pattern-library/components/patterns/prototype/MenuSection.tsx b/src/pattern-library/components/patterns/prototype/MenuSection.tsx deleted file mode 100644 index 570286a41..000000000 --- a/src/pattern-library/components/patterns/prototype/MenuSection.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { toChildArray } from 'preact'; -import type { ComponentChildren, VNode } from 'preact'; - -export type MenuSectionProps = { - /** Heading displayed at the top of the menu. */ - heading?: string; - - /** Menu items to display in this section. */ - children: ComponentChildren; -}; - -/** - * Group a set of menu items together visually, with an optional header. - * - * @example - * - * - * - * - * - * - * - * - * - * - * - * @param {MenuSectionProps} props - */ -export default function MenuSection({ heading, children }: MenuSectionProps) { - return ( - <> - {heading && ( -

- {heading} -

- )} -
    - {toChildArray(children).map(child => ( -
  • {child}
  • - ))} -
- - ); -} diff --git a/src/pattern-library/components/patterns/prototype/SharedAnnotationsPage.tsx b/src/pattern-library/components/patterns/prototype/SharedAnnotationsPage.tsx deleted file mode 100644 index fe75c845e..000000000 --- a/src/pattern-library/components/patterns/prototype/SharedAnnotationsPage.tsx +++ /dev/null @@ -1,312 +0,0 @@ -import type { ComponentChildren } from 'preact'; - -import { - Card, - CardActions, - CardContent, - EditIcon, - EllipsisIcon, - GlobeIcon, - GroupsFilledIcon, - IconButton, - PinIcon, - ReplyIcon, - TrashIcon, - Tab, - TabList, -} from '../../../../'; -import Library from '../../Library'; -import { LoremIpsum } from '../samples'; -import FakeAnnotationPublishControl from './FakeAnnotationPublishControl'; -import FakeMenu from './Menu'; -import FakeMenuItem from './MenuItem'; -import FakeMenuSection from './MenuSection'; - -type FakeAnnotationProps = { - children?: ComponentChildren; - hasReplies?: boolean; - isOwn?: boolean; - isPinned?: boolean; - isShared?: boolean; - withManageMenu?: boolean; -}; - -function FakeAnnotation({ - children, - hasReplies = false, - isOwn = false, - isPinned = false, - isShared = false, - withManageMenu = false, -}: FakeAnnotationProps) { - const actions = ['reply']; - if (isOwn) { - actions.push('pin', 'edit'); - if (!withManageMenu) { - actions.push('delete'); - } - } - const content = children ?? ; - const groupName = isShared ? 'All course participants' : 'Section 1'; - return ( - -
- {isPinned && ( -
- -
- )} - {isShared && ( -
- -
- )} -
- -
-
-
Fred Pickle
-
- {withManageMenu && ( - } - > - - - - {!hasReplies && } - {hasReplies && ( - -
Move to...
-
- Annotations with replies cannot be moved -
-
- } - /> - )} - - - Delete} - /> - - - )} -
-
-
-
- -
{groupName}
-
-
-
- (edited Apr 23) -
-
Apr 22
-
-
-
- {content} - -
- {actions.includes('edit') && ( - - )} - {actions.includes('delete') && ( - - )} - {actions.includes('pin') && ( - - )} - {actions.includes('reply') && ( - - )} -
-
-
-
- ); -} - -function FakeSidebar({ children }: { children: ComponentChildren }) { - return ( -
- {children} -
- ); -} - -export default function SharedAnnotationsPrototypePage() { - return ( - - Give instructors the ability to create and manage{' '} - assignment-shared content that all - assignment participants can see regardless of section group - membership. -

- } - > - - - Note: The UI sketches here are intended as - low-fidelity wireframes to demonstrate UX and flow intent. They are - not intended to represent polished design. - - -

- An instructor may create top-level annotations that are visible to - everyone in the assignment, regardless of which segment they belong - to. An instructor may edit an annotation and change its sharing - target. -

- -

- A proposed approach is to extend the existing publish-annotation - interface. -

- - -
- -
-
-
-
- - -

- Content visible to all assignment participants should be{' '} - {'"merged into" '} - the annotation threads for the active segment (section/reading group - as indicated by the group selector in the top bar), but it should be - easy to distinguish which annotation threads are shared to all - participants. -

- -

- It should be easy to visually distinguish which annotations in the - sidebar are shared. The treatment shown here is not proposed as a - design solution. -

- - - - Annotations - Page Notes - Orphans - - - - - - - - - -
-
-
- - - Several proposed future features may have some interplay with - annotation assignment sharing. -

- } - > - - Note: None of the following features are scoped or - committed yet. These sketches merely posit some potential UX interplay - and may be useful as reference later. - - -

- A top-level annotation can be {'"pinned"'} by authorized users, - which makes the annotation(s) show up at the top of the sidebar - above the annotation-type tabs at all times. This feature could help - with the use case of instructors wanting to put certain annotations - front and center, or provide instructions or prompts for the - assignment as a whole.{' '} -

- -

- As pinning is a toggling function, we could add an additional icon - to the annotation footer. -

- - - - - -
-
- - - -

An annotation may be both shared and pinned.

-

- Pinned content could be shown above all other content, - including tabs (i.e. a pinned Page Note would also show up on the - Annotations tab, but above the tabs). This could satisfy use cases - relating to creating prompts or instructions for assignments, or - for otherwise showing certain instructor content at the top. -

- - - - - - - Annotations - Page Notes - Orphans - - - - - - - -
- - -

- In the future, we might provide both share/move and copy options - for a root-level annotation. -

-

- One option: it might be possible to consolidate some annotation - actions into a {'"manage-annotation menu"'} at the top right of a - top-level annotation card. -

- - - - - -
-
-
-
- ); -} diff --git a/src/pattern-library/components/patterns/prototype/TabbedDialogPage.tsx b/src/pattern-library/components/patterns/prototype/TabbedDialogPage.tsx deleted file mode 100644 index 099300797..000000000 --- a/src/pattern-library/components/patterns/prototype/TabbedDialogPage.tsx +++ /dev/null @@ -1,322 +0,0 @@ -//import type { ComponentChildren } from 'preact'; -import classnames from 'classnames'; -import type { ComponentChildren, JSX } from 'preact'; -import { useState } from 'preact/hooks'; - -import { - Button, - Card, - CardActions, - CardTitle, - CloseButton, - Dialog, - IconButton, - Input, - InputGroup, - OptionButton, - Slider, - TabList, - Tab, -} from '../../../../'; -import { - CopyIcon, - EmailIcon, - SocialTwitterIcon, - SocialFacebookIcon, -} from '../../../../'; -import type { PresentationalProps } from '../../../../types'; -import Library from '../../Library'; - -type DividerProps = PresentationalProps & { - variant: 'full' | 'center' | 'custom'; -} & JSX.HTMLAttributes; - -function Divider({ variant }: DividerProps) { - return ( -
- ); -} - -type TabListHeaderProps = PresentationalProps & { - onClose?: () => void; -}; - -function TabListHeader({ children, onClose }: TabListHeaderProps) { - return ( -
- {onClose && ( - // This might be extractable as, say, a CloseButton component - - )} - {children} -
- ); -} - -type TabPanelProps = PresentationalProps & { - active?: boolean; - title?: ComponentChildren; -} & JSX.HTMLAttributes; - -function TabPanel({ - children, - active, - title, - ...htmlAttributes -}: TabPanelProps) { - return ( -
- {title && {title}} -
{children}
-
- ); -} - -function TabbedDialog() { - const [panelOpen, setPanelOpen] = useState(false); - const [selectedTab, setSelectedTab] = useState('one'); - return ( -
-
- setPanelOpen(!panelOpen)} - selected={panelOpen} - > - Toggle dialog - -
- -
- {panelOpen && ( - setPanelOpen(false)} - restoreFocus - > - setPanelOpen(false)}> - setSelectedTab('one')} - > - One - - setSelectedTab('two')} - > - Two - - setSelectedTab('three')} - disabled - > - Three - - - - -

- This tab panel has no focusable elements, so - the tabpanel itself can take focus. -

-
- -

- This tab panel has focusable elements, so the tabpanel itself - does not take focus. -

- - - - -
- -

Nothing to see here.

-
-
-
- )} -
-
- ); -} - -function TabbedSharePanel() { - const [panelOpen, setPanelOpen] = useState(false); - const [selectedTab, setSelectedTab] = useState('share'); - return ( -
-
- setPanelOpen(!panelOpen)} - selected={panelOpen} - > - Toggle dialog - -
- -
- {panelOpen && ( - setPanelOpen(false)} - transitionComponent={Slider} - restoreFocus - > - setPanelOpen(false)}> - setSelectedTab('share')} - > - Share - - setSelectedTab('export')} - > - Export - - - - -

- - Use this link to share these annotations with anyone: - -

- - - - - -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
- -

- Export 2 annotations in a file named: -

- - - - -
-
-
- )} -
-
- ); -} - -export default function TabbedDialogPage() { - return ( - - - -

- This design pattern extends the panel-like dialog layout with tabs - and an integrated close button. Tabs may be disabled. -

- - - -
-
- - - -

- Client-specific font sizes and sidebar background color are - applied to this tabbed dialog to show how it would appear in situ. -

- - - -
-
-
-
- ); -} diff --git a/src/pattern-library/routes.ts b/src/pattern-library/routes.ts index 2ae50ed22..67422f0f4 100644 --- a/src/pattern-library/routes.ts +++ b/src/pattern-library/routes.ts @@ -31,11 +31,7 @@ import LinkButtonPage from './components/patterns/navigation/LinkButtonPage'; import LinkPage from './components/patterns/navigation/LinkPage'; import PointerButtonPage from './components/patterns/navigation/PointerButtonPage'; import TabPage from './components/patterns/navigation/TabPage'; -import LMSContentButtonPage from './components/patterns/prototype/LMSContentButtonPage'; -import LMSContentSelectionPage from './components/patterns/prototype/LMSContentSelectionPage'; import SelectPage from './components/patterns/prototype/SelectPage'; -import SharedAnnotationsPage from './components/patterns/prototype/SharedAnnotationsPage'; -import TabbedDialogPage from './components/patterns/prototype/TabbedDialogPage'; import SliderPage from './components/patterns/transition/SliderPage'; export const componentGroups = { @@ -270,30 +266,6 @@ const routes: PlaygroundRoute[] = [ component: UseClickAwayPage, route: '/hooks-use-click-away', }, - { - title: 'Import/Export Dialog', - group: 'prototype', - component: TabbedDialogPage, - route: '/tabbed-share-dialog', - }, - { - title: 'LMS: Content Button', - group: 'prototype', - component: LMSContentButtonPage, - route: '/lms-content-button', - }, - { - title: 'Sketches: Shared Annotations', - group: 'prototype', - component: SharedAnnotationsPage, - route: '/shared-annotations-ui', - }, - { - title: 'Sketches: Content selection', - group: 'prototype', - component: LMSContentSelectionPage, - route: '/lms-content-selection', - }, ]; /** From 0c8c12469edfdc0abe6c533f1ec5ae6fd70bbfe3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 31 Oct 2024 11:15:05 +0100 Subject: [PATCH 2/2] Move SelectPage to input module --- .../components/patterns/{prototype => input}/SelectPage.tsx | 0 src/pattern-library/routes.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/pattern-library/components/patterns/{prototype => input}/SelectPage.tsx (100%) diff --git a/src/pattern-library/components/patterns/prototype/SelectPage.tsx b/src/pattern-library/components/patterns/input/SelectPage.tsx similarity index 100% rename from src/pattern-library/components/patterns/prototype/SelectPage.tsx rename to src/pattern-library/components/patterns/input/SelectPage.tsx diff --git a/src/pattern-library/routes.ts b/src/pattern-library/routes.ts index 67422f0f4..385d180f9 100644 --- a/src/pattern-library/routes.ts +++ b/src/pattern-library/routes.ts @@ -23,6 +23,7 @@ import InputGroupPage from './components/patterns/input/InputGroupPage'; import InputPage from './components/patterns/input/InputPage'; import OptionButtonPage from './components/patterns/input/OptionButtonPage'; import RadioGroupPage from './components/patterns/input/RadioGroupPage'; +import SelectPage from './components/patterns/input/SelectPage'; import TextareaPage from './components/patterns/input/TextareaPage'; import CardPage from './components/patterns/layout/CardPage'; import OverlayPage from './components/patterns/layout/OverlayPage'; @@ -31,7 +32,6 @@ import LinkButtonPage from './components/patterns/navigation/LinkButtonPage'; import LinkPage from './components/patterns/navigation/LinkPage'; import PointerButtonPage from './components/patterns/navigation/PointerButtonPage'; import TabPage from './components/patterns/navigation/TabPage'; -import SelectPage from './components/patterns/prototype/SelectPage'; import SliderPage from './components/patterns/transition/SliderPage'; export const componentGroups = {