mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 17:43:35 -06:00
* RolePicker: Handle inherited with * Small ammendment to Create Service Account layout * RolePicker: introduce maxWidth prop * Clean up * Change VerticalGroup spacing to large on Team Settings page * Introduce constant for submenu width * Update public/app/core/components/RolePicker/RolePicker.tsx Simplify style parameter Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Add description to the improved calculation Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
189 lines
4.8 KiB
TypeScript
189 lines
4.8 KiB
TypeScript
import { css, cx } from '@emotion/css';
|
|
import React, { FormEvent, HTMLProps, useEffect, useRef } from 'react';
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
import { useStyles2, getInputStyles, sharedInputStyle, styleMixins, Tooltip, Icon } from '@grafana/ui';
|
|
|
|
import { Role } from '../../../types';
|
|
|
|
import { ValueContainer } from './ValueContainer';
|
|
import { ROLE_PICKER_WIDTH } from './constants';
|
|
|
|
const stopPropagation = (event: React.MouseEvent<HTMLDivElement>) => event.stopPropagation();
|
|
|
|
interface InputProps extends HTMLProps<HTMLInputElement> {
|
|
appliedRoles: Role[];
|
|
basicRole?: string;
|
|
query: string;
|
|
showBasicRole?: boolean;
|
|
isFocused?: boolean;
|
|
disabled?: boolean;
|
|
onQueryChange: (query?: string) => void;
|
|
onOpen: (event: FormEvent<HTMLElement>) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export const RolePickerInput = ({
|
|
appliedRoles,
|
|
basicRole,
|
|
disabled,
|
|
isFocused,
|
|
query,
|
|
showBasicRole,
|
|
onOpen,
|
|
onClose,
|
|
onQueryChange,
|
|
...rest
|
|
}: InputProps): JSX.Element => {
|
|
const styles = useStyles2((theme) => getRolePickerInputStyles(theme, false, !!isFocused, !!disabled, false));
|
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (isFocused) {
|
|
inputRef.current?.focus();
|
|
}
|
|
});
|
|
|
|
const onInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const query = event.target?.value;
|
|
onQueryChange(query);
|
|
};
|
|
|
|
const numberOfRoles = appliedRoles.length;
|
|
|
|
return !isFocused ? (
|
|
<div className={cx(styles.wrapper, styles.selectedRoles)} onMouseDown={onOpen}>
|
|
{showBasicRole && <ValueContainer>{basicRole}</ValueContainer>}
|
|
<RolesLabel appliedRoles={appliedRoles} numberOfRoles={numberOfRoles} showBuiltInRole={showBasicRole} />
|
|
</div>
|
|
) : (
|
|
<div className={styles.wrapper}>
|
|
{showBasicRole && <ValueContainer>{basicRole}</ValueContainer>}
|
|
{appliedRoles.map((role) => (
|
|
<ValueContainer key={role.uid}>{role.displayName}</ValueContainer>
|
|
))}
|
|
|
|
{!disabled && (
|
|
<input
|
|
{...rest}
|
|
className={styles.input}
|
|
ref={inputRef}
|
|
onMouseDown={stopPropagation}
|
|
onChange={onInputChange}
|
|
data-testid="role-picker-input"
|
|
placeholder={isFocused ? 'Select role' : ''}
|
|
value={query}
|
|
/>
|
|
)}
|
|
<div className={styles.suffix}>
|
|
<Icon name="angle-up" className={styles.dropdownIndicator} onMouseDown={onClose} />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
RolePickerInput.displayName = 'RolePickerInput';
|
|
|
|
interface RolesLabelProps {
|
|
appliedRoles: Role[];
|
|
showBuiltInRole?: boolean;
|
|
numberOfRoles: number;
|
|
}
|
|
|
|
export const RolesLabel = ({ showBuiltInRole, numberOfRoles, appliedRoles }: RolesLabelProps): JSX.Element => {
|
|
const styles = useStyles2((theme) => getTooltipStyles(theme));
|
|
|
|
return (
|
|
<>
|
|
{!!numberOfRoles ? (
|
|
<Tooltip
|
|
content={
|
|
<div className={styles.tooltip}>
|
|
{appliedRoles?.map((role) => (
|
|
<p key={role.uid}>{role.displayName}</p>
|
|
))}
|
|
</div>
|
|
}
|
|
>
|
|
<ValueContainer>{`${showBuiltInRole ? '+' : ''}${numberOfRoles} role${
|
|
numberOfRoles > 1 ? 's' : ''
|
|
}`}</ValueContainer>
|
|
</Tooltip>
|
|
) : (
|
|
!showBuiltInRole && <ValueContainer>No roles assigned</ValueContainer>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
const getRolePickerInputStyles = (
|
|
theme: GrafanaTheme2,
|
|
invalid: boolean,
|
|
focused: boolean,
|
|
disabled: boolean,
|
|
withPrefix: boolean
|
|
) => {
|
|
const styles = getInputStyles({ theme, invalid });
|
|
|
|
return {
|
|
wrapper: cx(
|
|
styles.wrapper,
|
|
sharedInputStyle(theme, invalid),
|
|
focused &&
|
|
css`
|
|
${styleMixins.focusCss(theme.v1)}
|
|
`,
|
|
disabled && styles.inputDisabled,
|
|
css`
|
|
min-width: ${ROLE_PICKER_WIDTH}px;
|
|
min-height: 32px;
|
|
height: auto;
|
|
flex-direction: row;
|
|
padding-right: 24px;
|
|
max-width: 100%;
|
|
align-items: center;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-start;
|
|
position: relative;
|
|
box-sizing: border-box;
|
|
cursor: default;
|
|
`,
|
|
withPrefix &&
|
|
css`
|
|
padding-left: 0;
|
|
`
|
|
),
|
|
input: cx(
|
|
sharedInputStyle(theme, invalid),
|
|
css`
|
|
max-width: 120px;
|
|
border: none;
|
|
cursor: ${focused ? 'default' : 'pointer'};
|
|
`
|
|
),
|
|
suffix: styles.suffix,
|
|
dropdownIndicator: css`
|
|
cursor: pointer;
|
|
`,
|
|
selectedRoles: css`
|
|
display: flex;
|
|
align-items: center;
|
|
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
|
`,
|
|
tooltip: css`
|
|
p {
|
|
margin-bottom: ${theme.spacing(0.5)};
|
|
}
|
|
`,
|
|
};
|
|
};
|
|
|
|
const getTooltipStyles = (theme: GrafanaTheme2) => ({
|
|
tooltip: css`
|
|
p {
|
|
margin-bottom: ${theme.spacing(0.5)};
|
|
}
|
|
`,
|
|
});
|