mirror of
https://github.com/wassname/talk.git
synced 2026-09-11 12:51:33 +08:00
[CORL-294] Moderate a single story + quick search (#2286)
* feat: allow passing a `storyID` to `Query.moderationQueues` * feat: moderate by story * feat: implement search story combobox * feat: add translations * fix: tests * fix: duplicate id * fix: rename file * chore: add more comments * fix: add missing translation * review: use query parameter "q" instead of url path * chore: move placeholder logic inside, maybe this makes it clearer :-D
This commit is contained in:
@@ -3,10 +3,7 @@
|
||||
}
|
||||
|
||||
.keyboardFocus {
|
||||
outline-width: 3px;
|
||||
outline-color: Highlight;
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto;
|
||||
@mixin outline;
|
||||
}
|
||||
|
||||
.mouseHover {
|
||||
|
||||
@@ -40,10 +40,7 @@
|
||||
|
||||
/* Box focus */
|
||||
.label.focus:before {
|
||||
outline-width: 3px;
|
||||
outline-color: Highlight;
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto;
|
||||
@mixin outline;
|
||||
}
|
||||
|
||||
/* Box checked */
|
||||
|
||||
@@ -7,6 +7,7 @@ import { withStyles } from "talk-ui/hocs";
|
||||
|
||||
import AriaInfo from "../AriaInfo";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
import styles from "./Popover.css";
|
||||
|
||||
type Placement =
|
||||
@@ -40,15 +41,19 @@ interface ChildrenRenderProps {
|
||||
interface PopoverProps {
|
||||
body: (props: BodyRenderProps) => React.ReactNode | React.ReactElement<any>;
|
||||
children: (props: ChildrenRenderProps) => React.ReactNode;
|
||||
description: string;
|
||||
description?: string;
|
||||
id: string;
|
||||
className?: string;
|
||||
placement?: Placement;
|
||||
visible?: boolean;
|
||||
classes: typeof styles;
|
||||
modifiers?: PropTypesOf<typeof Popper>["modifiers"];
|
||||
eventsEnabled?: PropTypesOf<typeof Popper>["eventsEnabled"];
|
||||
positionFixed?: PropTypesOf<typeof Popper>["positionFixed"];
|
||||
}
|
||||
|
||||
interface State {
|
||||
visible: false;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
class Popover extends React.Component<PopoverProps> {
|
||||
@@ -109,10 +114,15 @@ class Popover extends React.Component<PopoverProps> {
|
||||
className,
|
||||
placement,
|
||||
classes,
|
||||
visible: controlledVisible,
|
||||
positionFixed,
|
||||
modifiers,
|
||||
eventsEnabled,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
const { visible } = this.state;
|
||||
const visible =
|
||||
controlledVisible !== undefined ? controlledVisible : this.state.visible;
|
||||
const popoverClassName = cn(classes.popover, {
|
||||
[classes.top]: placement!.startsWith("top"),
|
||||
[classes.left]: placement!.startsWith("left"),
|
||||
@@ -128,11 +138,16 @@ class Popover extends React.Component<PopoverProps> {
|
||||
children({
|
||||
ref: props.ref,
|
||||
toggleVisibility: this.toggleVisibility,
|
||||
visible: this.state.visible,
|
||||
visible,
|
||||
})
|
||||
}
|
||||
</Reference>
|
||||
<Popper placement={placement} eventsEnabled positionFixed={false}>
|
||||
<Popper
|
||||
placement={placement}
|
||||
eventsEnabled={eventsEnabled}
|
||||
positionFixed={positionFixed}
|
||||
modifiers={modifiers}
|
||||
>
|
||||
{props => (
|
||||
<div
|
||||
id={id}
|
||||
@@ -140,7 +155,9 @@ class Popover extends React.Component<PopoverProps> {
|
||||
aria-labelledby={`${id}-ariainfo`}
|
||||
aria-hidden={!visible}
|
||||
>
|
||||
<AriaInfo id={`${id}-ariainfo`}>{description}</AriaInfo>
|
||||
{description && (
|
||||
<AriaInfo id={`${id}-ariainfo`}>{description}</AriaInfo>
|
||||
)}
|
||||
{visible && (
|
||||
<div
|
||||
style={props.style}
|
||||
@@ -151,7 +168,7 @@ class Popover extends React.Component<PopoverProps> {
|
||||
? body({
|
||||
scheduleUpdate: props.scheduleUpdate,
|
||||
toggleVisibility: this.toggleVisibility,
|
||||
visible: this.state.visible,
|
||||
visible,
|
||||
})
|
||||
: body}
|
||||
</div>
|
||||
|
||||
@@ -35,10 +35,7 @@
|
||||
|
||||
/* Box focus */
|
||||
.label.focus:before {
|
||||
outline-width: 3px;
|
||||
outline-color: Highlight;
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto;
|
||||
@mixin outline;
|
||||
}
|
||||
|
||||
/* Box checked */
|
||||
|
||||
@@ -15,10 +15,7 @@
|
||||
}
|
||||
|
||||
.keyboardFocus:focus {
|
||||
outline-width: 3px;
|
||||
outline-color: Highlight;
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto;
|
||||
@mixin outline;
|
||||
}
|
||||
|
||||
.select {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { withStyles } from "talk-ui/hocs";
|
||||
|
||||
import styles from "./Spinner.css";
|
||||
|
||||
type Size = "sm" | "md";
|
||||
type Size = "xs" | "sm" | "md";
|
||||
|
||||
export interface SpinnerProps {
|
||||
/**
|
||||
@@ -21,6 +21,8 @@ export interface SpinnerProps {
|
||||
|
||||
function calculateSize(size: Size): number {
|
||||
switch (size) {
|
||||
case "xs":
|
||||
return 15;
|
||||
case "sm":
|
||||
return 30;
|
||||
case "md":
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
import React, { HTMLAttributes, StatelessComponent } from "react";
|
||||
|
||||
import { Flex } from "talk-ui/components";
|
||||
import { withStyles } from "talk-ui/hocs";
|
||||
|
||||
import styles from "./SubBar.css";
|
||||
|
||||
interface Props {
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
gutterBegin?: boolean;
|
||||
|
||||
@@ -7,13 +7,11 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
composes: bodyCopy from "talk-ui/shared/typography.css";
|
||||
color: var(--palette-grey-dark);
|
||||
composes: tableHeading from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.body {
|
||||
composes: detail from "talk-ui/shared/typography.css";
|
||||
color: var(--palette-grey-dark);
|
||||
composes: tableData from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.alignCenter {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as BaseButton } from "./BaseButton";
|
||||
export { default as Button } from "./Button";
|
||||
export { default as Backdrop } from "./Backdrop";
|
||||
export { default as ButtonIcon } from "./Button/ButtonIcon";
|
||||
export { default as Typography } from "./Typography";
|
||||
export { default as Popover } from "./Popover";
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/** blur removes focus from current active element */
|
||||
export default function blur() {
|
||||
if (window.document.activeElement) {
|
||||
(window.document.activeElement as any).blur();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
export default function combineEventHandlers<A, B, C, D>(
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
d: D
|
||||
): A & B & C & D;
|
||||
export default function combineEventHandlers<A, B, C>(
|
||||
a: A,
|
||||
b: B,
|
||||
c: C
|
||||
): A & B & C;
|
||||
export default function combineEventHandlers<A, B>(a: A, b: B): A & B;
|
||||
|
||||
/**
|
||||
* combineEventHandlers expects prop objects with React
|
||||
* like onEvent handlers e.g. onClick, onKeydown and combine them
|
||||
* by calling them one after another.
|
||||
*/
|
||||
export default function combineEventHandlers(...propObjects: any[]): any {
|
||||
const result: any = {};
|
||||
propObjects.forEach(o => {
|
||||
Object.keys(o).forEach(k => {
|
||||
if (k in result) {
|
||||
const prev = result[k];
|
||||
result[k] = (...args: any[]) => {
|
||||
prev(...args);
|
||||
(o as any)[k](...args);
|
||||
};
|
||||
} else {
|
||||
result[k] = (o as any)[k];
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as blur } from "./blur";
|
||||
export { default as combineEventHandlers } from "./combineEventHandlers";
|
||||
@@ -0,0 +1,4 @@
|
||||
export { default as useFocus } from "./useFocus";
|
||||
export { default as usePreventFocusLoss } from "./usePreventFocusLoss";
|
||||
export { default as useBlurOnEsc } from "./useBlurOnEsc";
|
||||
export { default as useComboBox } from "./useComboBox";
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { blur } from "../helpers";
|
||||
|
||||
/** useBlurOnEsc returns handlers that calls blur when pressing ESC */
|
||||
export default function useBlurOnEsc(active: boolean) {
|
||||
return {
|
||||
onKeyDown: useCallback(
|
||||
(evt: React.KeyboardEvent) => {
|
||||
if (evt.keyCode === 27) {
|
||||
blur();
|
||||
}
|
||||
},
|
||||
[active]
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
/**
|
||||
* ActiveDescendant is the id of the current active selection, which needs to be passed to `aria-activedescendant` prop
|
||||
*/
|
||||
export type ActiveDescendant = string | undefined;
|
||||
|
||||
/** EventHandlers are the ones that should be passed to the TextField */
|
||||
export interface EventHandlers {
|
||||
onBlur: React.EventHandler<React.FocusEvent>;
|
||||
onChange: React.EventHandler<React.FormEvent<HTMLInputElement>>;
|
||||
onKeyDown: React.EventHandler<React.KeyboardEvent>;
|
||||
}
|
||||
|
||||
export type ListBoxOptionElement = React.ReactElement<{
|
||||
id: string;
|
||||
"aria-selected": boolean;
|
||||
onClick: React.EventHandler<React.MouseEvent>;
|
||||
href?: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* ListBoxOptionClickOrEnterHandler is called whenever a click or enter
|
||||
* occurs on a list box option
|
||||
*/
|
||||
export type ListBoxOptionClickOrEnterHandler = (
|
||||
evt: React.KeyboardEvent | React.MouseEvent,
|
||||
element: ListBoxOptionElement
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* ListBoxOption is the data for the options to pass to `useComboBox`.
|
||||
*/
|
||||
export interface ListBoxOption {
|
||||
element: ListBoxOptionElement;
|
||||
onClickOrEnter?: ListBoxOptionClickOrEnterHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* useComboBox accepts an `id` and a list of `ListBoxOption` and returns
|
||||
* a managed list of `ListBoxOption`, the active descendant and event handlers
|
||||
* for the textfield. You can find the managed props of the elements in
|
||||
* the type `ListBoxOptionElements`.
|
||||
*
|
||||
* @param id unique identifier for accessibility purposes
|
||||
* @param opts options to show in the listbox
|
||||
*/
|
||||
export default function useComboBox<T extends ListBoxOption>(
|
||||
id: string,
|
||||
opts: T[]
|
||||
): [T[], ActiveDescendant, EventHandlers] {
|
||||
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
||||
return [
|
||||
opts.map((item, i) => ({
|
||||
...item,
|
||||
element: React.cloneElement(item.element, {
|
||||
id: `${id}-${i}`,
|
||||
"aria-selected": activeIndex === i,
|
||||
key: `${id}-${i}`,
|
||||
onClick:
|
||||
item.element.props.onClick ||
|
||||
((evt: React.MouseEvent) => {
|
||||
if (item.onClickOrEnter) {
|
||||
item.onClickOrEnter!(evt, item.element);
|
||||
}
|
||||
}),
|
||||
}),
|
||||
})),
|
||||
activeIndex !== null ? `${id}-${activeIndex}` : undefined,
|
||||
{
|
||||
onBlur: () => {
|
||||
/** Reset active selection when the textfield is blurred */
|
||||
setActiveIndex(null);
|
||||
},
|
||||
onChange: () => {
|
||||
/** Reset active selection when the text changes */
|
||||
setActiveIndex(null);
|
||||
},
|
||||
onKeyDown: (evt: React.KeyboardEvent) => {
|
||||
// On Arror Down
|
||||
if (evt.keyCode === 40) {
|
||||
if (activeIndex !== opts.length - 1) {
|
||||
if (activeIndex === null) {
|
||||
setActiveIndex(0);
|
||||
} else {
|
||||
setActiveIndex((activeIndex || 0) + 1);
|
||||
}
|
||||
}
|
||||
evt.preventDefault();
|
||||
}
|
||||
// On Arrow Up
|
||||
else if (evt.keyCode === 38) {
|
||||
if (activeIndex !== 0) {
|
||||
if (activeIndex === null) {
|
||||
setActiveIndex(0);
|
||||
} else {
|
||||
setActiveIndex((activeIndex || 0) - 1);
|
||||
}
|
||||
}
|
||||
evt.preventDefault();
|
||||
}
|
||||
// On ENTER
|
||||
else if (evt.keyCode === 13) {
|
||||
if (activeIndex !== null && opts[activeIndex].onClickOrEnter) {
|
||||
opts[activeIndex].onClickOrEnter!(evt, opts[activeIndex].element);
|
||||
}
|
||||
} else {
|
||||
// Any other key.
|
||||
setActiveIndex(null);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { EventHandler, FocusEvent, useCallback, useState } from "react";
|
||||
|
||||
/** useFocus tracks the focus state and returns the event handlers to do so */
|
||||
export default function useFocus(): [
|
||||
boolean,
|
||||
{ onFocus: EventHandler<FocusEvent>; onBlur: EventHandler<FocusEvent> }
|
||||
] {
|
||||
const [focused, setFocused] = useState<boolean>(false);
|
||||
const onFocus = useCallback(() => setFocused(true), [setFocused]);
|
||||
const onBlur = useCallback(() => setFocused(false), [setFocused]);
|
||||
return [focused, { onFocus, onBlur }];
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
/** usePreventFocusLoss returns event handlers that will prevent the current focus from being lost */
|
||||
export default function usePreventFocusLoss(active: boolean) {
|
||||
return {
|
||||
onMouseDown: useCallback(
|
||||
(evt: React.MouseEvent) => {
|
||||
if (!active || evt.target === window.document.activeElement) {
|
||||
return;
|
||||
}
|
||||
evt.preventDefault();
|
||||
},
|
||||
[active]
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -260,3 +260,19 @@
|
||||
letter-spacing: calc(0.1em / 18);
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
|
||||
.tableHeading {
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: calc(14rem / var(--rem-base));
|
||||
line-height: calc(20em / 14);
|
||||
color: var(--palette-grey-dark);
|
||||
}
|
||||
|
||||
.tableData {
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-weight: var(--font-weight-regular);
|
||||
font-size: calc(14rem / var(--rem-base));
|
||||
line-height: calc(14em / 14);
|
||||
color: var(--palette-grey-dark);
|
||||
}
|
||||
|
||||
@@ -94,3 +94,9 @@
|
||||
letter-spacing: calc(0.2em / 16);
|
||||
color: var(--palette-grey-darkest);
|
||||
}
|
||||
@define-mixin outline {
|
||||
outline-width: 3px;
|
||||
outline-color: Highlight;
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user