mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-28 05:05:48 +08:00
refactor: mentions cssinjs (#34536)
* refactor: mentions cssinjs * chore: add fixme * chore: code clean
This commit is contained in:
parent
ae6240d9cc
commit
fbf9311100
@ -12,6 +12,7 @@ import {
|
|||||||
getStatusClassNames,
|
getStatusClassNames,
|
||||||
InputStatus,
|
InputStatus,
|
||||||
} from '../_util/statusUtils';
|
} from '../_util/statusUtils';
|
||||||
|
import useStyle from './style';
|
||||||
|
|
||||||
export const { Option } = RcMentions;
|
export const { Option } = RcMentions;
|
||||||
|
|
||||||
@ -62,6 +63,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
|||||||
children,
|
children,
|
||||||
notFoundContent,
|
notFoundContent,
|
||||||
status: customStatus,
|
status: customStatus,
|
||||||
|
dropdownClassName,
|
||||||
...restProps
|
...restProps
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@ -69,7 +71,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
|||||||
const [focused, setFocused] = React.useState(false);
|
const [focused, setFocused] = React.useState(false);
|
||||||
const innerRef = React.useRef<HTMLElement>();
|
const innerRef = React.useRef<HTMLElement>();
|
||||||
const mergedRef = composeRef(ref, innerRef);
|
const mergedRef = composeRef(ref, innerRef);
|
||||||
const { getPrefixCls, renderEmpty, direction } = React.useContext(ConfigContext);
|
const { getPrefixCls, renderEmpty, direction, iconPrefixCls } = React.useContext(ConfigContext);
|
||||||
const { status: contextStatus, hasFeedback } = React.useContext(FormItemStatusContext);
|
const { status: contextStatus, hasFeedback } = React.useContext(FormItemStatusContext);
|
||||||
const mergedStatus = getMergedStatus(contextStatus, customStatus);
|
const mergedStatus = getMergedStatus(contextStatus, customStatus);
|
||||||
|
|
||||||
@ -117,6 +119,9 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
|||||||
|
|
||||||
const prefixCls = getPrefixCls('mentions', customizePrefixCls);
|
const prefixCls = getPrefixCls('mentions', customizePrefixCls);
|
||||||
|
|
||||||
|
// Style
|
||||||
|
const [wrapSSR, hashId] = useStyle(prefixCls, iconPrefixCls);
|
||||||
|
|
||||||
const mergedClassName = classNames(
|
const mergedClassName = classNames(
|
||||||
{
|
{
|
||||||
[`${prefixCls}-disabled`]: disabled,
|
[`${prefixCls}-disabled`]: disabled,
|
||||||
@ -125,6 +130,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
|||||||
},
|
},
|
||||||
getStatusClassNames(prefixCls, mergedStatus),
|
getStatusClassNames(prefixCls, mergedStatus),
|
||||||
!hasFeedback && className,
|
!hasFeedback && className,
|
||||||
|
hashId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const mentions = (
|
const mentions = (
|
||||||
@ -138,6 +144,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
|||||||
filterOption={getFilterOption()}
|
filterOption={getFilterOption()}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
dropdownClassName={classNames(dropdownClassName, hashId)}
|
||||||
ref={mergedRef as any}
|
ref={mergedRef as any}
|
||||||
>
|
>
|
||||||
{getOptions()}
|
{getOptions()}
|
||||||
@ -151,6 +158,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
|||||||
`${prefixCls}-affix-wrapper`,
|
`${prefixCls}-affix-wrapper`,
|
||||||
getStatusClassNames(`${prefixCls}-affix-wrapper`, mergedStatus, hasFeedback),
|
getStatusClassNames(`${prefixCls}-affix-wrapper`, mergedStatus, hasFeedback),
|
||||||
className,
|
className,
|
||||||
|
hashId,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{mentions}
|
{mentions}
|
||||||
@ -159,7 +167,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mentions;
|
return wrapSSR(mentions);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Mentions = React.forwardRef<unknown, MentionProps>(InternalMentions) as CompoundedComponent;
|
const Mentions = React.forwardRef<unknown, MentionProps>(InternalMentions) as CompoundedComponent;
|
||||||
|
@ -1,168 +1,168 @@
|
|||||||
@import '../../style/themes/index';
|
//@import '../../style/themes/index';
|
||||||
@import '../../style/mixins/index';
|
//@import '../../style/mixins/index';
|
||||||
@import '../../input/style/mixin';
|
//@import '../../input/style/mixin';
|
||||||
@import './status';
|
//@import './status';
|
||||||
|
//
|
||||||
@mention-prefix-cls: ~'@{ant-prefix}-mentions';
|
//@mention-prefix-cls: ~'@{ant-prefix}-mentions';
|
||||||
|
//
|
||||||
.@{mention-prefix-cls} {
|
//.@{mention-prefix-cls} {
|
||||||
.reset-component();
|
// .reset-component();
|
||||||
.input();
|
// .input();
|
||||||
|
//
|
||||||
position: relative;
|
// position: relative;
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
height: auto;
|
// height: auto;
|
||||||
padding: 0;
|
// padding: 0;
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
line-height: @line-height-base;
|
// line-height: @line-height-base;
|
||||||
white-space: pre-wrap;
|
// white-space: pre-wrap;
|
||||||
vertical-align: bottom;
|
// vertical-align: bottom;
|
||||||
|
//
|
||||||
// =================== Status ===================
|
// // =================== Status ===================
|
||||||
&-disabled {
|
// &-disabled {
|
||||||
> textarea {
|
// > textarea {
|
||||||
.disabled();
|
// .disabled();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
&-focused {
|
// &-focused {
|
||||||
.active();
|
// .active();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// ================= Input Area =================
|
// // ================= Input Area =================
|
||||||
> textarea,
|
// > textarea,
|
||||||
&-measure {
|
// &-measure {
|
||||||
min-height: @input-height-base - 2px;
|
// min-height: @input-height-base - 2px;
|
||||||
margin: 0;
|
// margin: 0;
|
||||||
padding: @input-padding-vertical-base @input-padding-horizontal-base;
|
// padding: @input-padding-vertical-base @input-padding-horizontal-base;
|
||||||
overflow: inherit;
|
// overflow: inherit;
|
||||||
overflow-x: hidden;
|
// overflow-x: hidden;
|
||||||
overflow-y: auto;
|
// overflow-y: auto;
|
||||||
/* stylelint-disable declaration-block-no-redundant-longhand-properties */
|
// /* stylelint-disable declaration-block-no-redundant-longhand-properties */
|
||||||
font-weight: inherit;
|
// font-weight: inherit;
|
||||||
font-size: inherit;
|
// font-size: inherit;
|
||||||
font-family: inherit;
|
// font-family: inherit;
|
||||||
font-style: inherit;
|
// font-style: inherit;
|
||||||
font-variant: inherit;
|
// font-variant: inherit;
|
||||||
font-size-adjust: inherit;
|
// font-size-adjust: inherit;
|
||||||
font-stretch: inherit;
|
// font-stretch: inherit;
|
||||||
line-height: inherit;
|
// line-height: inherit;
|
||||||
/* stylelint-enable declaration-block-no-redundant-longhand-properties */
|
// /* stylelint-enable declaration-block-no-redundant-longhand-properties */
|
||||||
direction: inherit;
|
// direction: inherit;
|
||||||
letter-spacing: inherit;
|
// letter-spacing: inherit;
|
||||||
white-space: inherit;
|
// white-space: inherit;
|
||||||
text-align: inherit;
|
// text-align: inherit;
|
||||||
vertical-align: top;
|
// vertical-align: top;
|
||||||
word-wrap: break-word;
|
// word-wrap: break-word;
|
||||||
word-break: inherit;
|
// word-break: inherit;
|
||||||
tab-size: inherit;
|
// tab-size: inherit;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
> textarea {
|
// > textarea {
|
||||||
width: 100%;
|
// width: 100%;
|
||||||
border: none;
|
// border: none;
|
||||||
outline: none;
|
// outline: none;
|
||||||
resize: none;
|
// resize: none;
|
||||||
& when (@theme = dark) {
|
// & when (@theme = dark) {
|
||||||
background-color: transparent;
|
// background-color: transparent;
|
||||||
}
|
// }
|
||||||
.placeholder();
|
// .placeholder();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
&-measure {
|
// &-measure {
|
||||||
position: absolute;
|
// position: absolute;
|
||||||
top: 0;
|
// top: 0;
|
||||||
right: 0;
|
// right: 0;
|
||||||
bottom: 0;
|
// bottom: 0;
|
||||||
left: 0;
|
// left: 0;
|
||||||
z-index: -1;
|
// z-index: -1;
|
||||||
color: transparent;
|
// color: transparent;
|
||||||
pointer-events: none;
|
// pointer-events: none;
|
||||||
|
//
|
||||||
> span {
|
// > span {
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
min-height: 1em;
|
// min-height: 1em;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// ================== Dropdown ==================
|
// // ================== Dropdown ==================
|
||||||
&-dropdown {
|
// &-dropdown {
|
||||||
// Ref select dropdown style
|
// // Ref select dropdown style
|
||||||
.reset-component();
|
// .reset-component();
|
||||||
|
//
|
||||||
position: absolute;
|
// position: absolute;
|
||||||
top: -9999px;
|
// top: -9999px;
|
||||||
left: -9999px;
|
// left: -9999px;
|
||||||
z-index: @zindex-dropdown;
|
// z-index: @zindex-dropdown;
|
||||||
box-sizing: border-box;
|
// box-sizing: border-box;
|
||||||
font-size: @font-size-base;
|
// font-size: @font-size-base;
|
||||||
font-variant: initial;
|
// font-variant: initial;
|
||||||
background-color: @mentions-dropdown-bg;
|
// background-color: @mentions-dropdown-bg;
|
||||||
border-radius: @border-radius-base;
|
// border-radius: @border-radius-base;
|
||||||
outline: none;
|
// outline: none;
|
||||||
box-shadow: @box-shadow-base;
|
// box-shadow: @box-shadow-base;
|
||||||
|
//
|
||||||
&-hidden {
|
// &-hidden {
|
||||||
display: none;
|
// display: none;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
&-menu {
|
// &-menu {
|
||||||
max-height: 250px;
|
// max-height: 250px;
|
||||||
margin-bottom: 0;
|
// margin-bottom: 0;
|
||||||
padding-left: 0; // Override default ul/ol
|
// padding-left: 0; // Override default ul/ol
|
||||||
overflow: auto;
|
// overflow: auto;
|
||||||
list-style: none;
|
// list-style: none;
|
||||||
outline: none;
|
// outline: none;
|
||||||
|
//
|
||||||
&-item {
|
// &-item {
|
||||||
position: relative;
|
// position: relative;
|
||||||
display: block;
|
// display: block;
|
||||||
min-width: 100px;
|
// min-width: 100px;
|
||||||
padding: 5px @control-padding-horizontal;
|
// padding: 5px @control-padding-horizontal;
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
color: @text-color;
|
// color: @text-color;
|
||||||
font-weight: normal;
|
// font-weight: normal;
|
||||||
line-height: @line-height-base;
|
// line-height: @line-height-base;
|
||||||
white-space: nowrap;
|
// white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
// text-overflow: ellipsis;
|
||||||
cursor: pointer;
|
// cursor: pointer;
|
||||||
transition: background 0.3s ease;
|
// transition: background 0.3s ease;
|
||||||
|
//
|
||||||
&:hover {
|
// &:hover {
|
||||||
background-color: @item-hover-bg;
|
// background-color: @item-hover-bg;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
&:first-child {
|
// &:first-child {
|
||||||
border-radius: @border-radius-base @border-radius-base 0 0;
|
// border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
&:last-child {
|
// &:last-child {
|
||||||
border-radius: 0 0 @border-radius-base @border-radius-base;
|
// border-radius: 0 0 @border-radius-base @border-radius-base;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
&-disabled {
|
// &-disabled {
|
||||||
color: @disabled-color;
|
// color: @disabled-color;
|
||||||
cursor: not-allowed;
|
// cursor: not-allowed;
|
||||||
|
//
|
||||||
&:hover {
|
// &:hover {
|
||||||
color: @disabled-color;
|
// color: @disabled-color;
|
||||||
background-color: @mentions-dropdown-menu-item-hover-bg;
|
// background-color: @mentions-dropdown-menu-item-hover-bg;
|
||||||
cursor: not-allowed;
|
// cursor: not-allowed;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
&-selected {
|
// &-selected {
|
||||||
color: @text-color;
|
// color: @text-color;
|
||||||
font-weight: @select-item-selected-font-weight;
|
// font-weight: @select-item-selected-font-weight;
|
||||||
background-color: @background-color-light;
|
// background-color: @background-color-light;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
&-active {
|
// &-active {
|
||||||
background-color: @item-hover-bg;
|
// background-color: @item-hover-bg;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
@import './rtl';
|
//@import './rtl';
|
||||||
|
@ -1,7 +1,221 @@
|
|||||||
import './index.less';
|
// deps-lint-skip-all
|
||||||
|
import {
|
||||||
|
GenerateStyle,
|
||||||
|
resetComponent,
|
||||||
|
UseComponentStyleResult,
|
||||||
|
useStyleRegister,
|
||||||
|
useToken,
|
||||||
|
} from '../../_util/theme';
|
||||||
|
import {
|
||||||
|
genActiveStyle,
|
||||||
|
genBasicInputStyle,
|
||||||
|
genDisabledStyle,
|
||||||
|
genPlaceholderStyle,
|
||||||
|
initInputToken,
|
||||||
|
InputToken,
|
||||||
|
} from '../../input/style';
|
||||||
|
|
||||||
// style dependencies
|
interface MentionsToken extends InputToken {
|
||||||
import '../../empty/style';
|
mentionsCls: string;
|
||||||
import '../../spin/style';
|
}
|
||||||
|
|
||||||
// deps-lint-skip: form
|
const genMentionsStyle: GenerateStyle<MentionsToken> = token => {
|
||||||
|
const {
|
||||||
|
mentionsCls,
|
||||||
|
backgroundLight,
|
||||||
|
textColorDisabled,
|
||||||
|
itemHoverBackground,
|
||||||
|
controlPaddingHorizontal,
|
||||||
|
textColor,
|
||||||
|
duration,
|
||||||
|
lineHeight,
|
||||||
|
controlHeight,
|
||||||
|
inputPaddingHorizontal,
|
||||||
|
inputPaddingVertical,
|
||||||
|
fontSize,
|
||||||
|
componentBackground,
|
||||||
|
borderRadius,
|
||||||
|
boxShadow,
|
||||||
|
} = token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
[`${mentionsCls}`]: {
|
||||||
|
...resetComponent(token),
|
||||||
|
...genBasicInputStyle(token),
|
||||||
|
|
||||||
|
position: 'relative',
|
||||||
|
display: 'inline-block',
|
||||||
|
height: 'auto',
|
||||||
|
padding: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
lineHeight,
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
verticalAlign: 'bottom',
|
||||||
|
|
||||||
|
'&-disabled': {
|
||||||
|
'> textarea': {
|
||||||
|
...genDisabledStyle(token),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-focused': {
|
||||||
|
...genActiveStyle(token),
|
||||||
|
},
|
||||||
|
|
||||||
|
// ================= Input Area =================
|
||||||
|
[`> textarea, ${mentionsCls}-measure`]: {
|
||||||
|
minHeight: controlHeight - 2,
|
||||||
|
margin: 0,
|
||||||
|
padding: `${inputPaddingVertical}px ${inputPaddingHorizontal}px`,
|
||||||
|
overflow: 'inherit',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
overflowY: 'auto',
|
||||||
|
fontWeight: 'inherit',
|
||||||
|
fontSize: 'inherit',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
fontStyle: 'inherit',
|
||||||
|
fontVariant: 'inherit',
|
||||||
|
fontSizeAdjust: 'inherit',
|
||||||
|
fontStretch: 'inherit',
|
||||||
|
lineHeight: 'inherit',
|
||||||
|
direction: 'inherit',
|
||||||
|
letterSpacing: 'inherit',
|
||||||
|
whiteSpace: 'inherit',
|
||||||
|
textAlign: 'inherit',
|
||||||
|
verticalAlign: 'top',
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
wordBreak: 'inherit',
|
||||||
|
tabSize: 'inherit',
|
||||||
|
},
|
||||||
|
|
||||||
|
'> textarea': {
|
||||||
|
width: '100%',
|
||||||
|
border: 'none',
|
||||||
|
outline: 'none',
|
||||||
|
resize: 'none',
|
||||||
|
...genPlaceholderStyle(),
|
||||||
|
},
|
||||||
|
|
||||||
|
[`${mentionsCls}-measure`]: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
insetInlineEnd: 0,
|
||||||
|
bottom: 0,
|
||||||
|
insetInlineStart: 0,
|
||||||
|
zIndex: -1,
|
||||||
|
color: 'transparent',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
|
||||||
|
'> span': {
|
||||||
|
display: 'inline-block',
|
||||||
|
minHeight: '1em',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ================== Dropdown ==================
|
||||||
|
'&-dropdown': {
|
||||||
|
// Ref select dropdown style
|
||||||
|
...resetComponent(token),
|
||||||
|
|
||||||
|
position: 'absolute',
|
||||||
|
top: -9999,
|
||||||
|
insetInlineStart: -9999,
|
||||||
|
zIndex: 1050, // FIXME: magic
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
fontSize,
|
||||||
|
fontVariant: 'initial',
|
||||||
|
backgroundColor: componentBackground,
|
||||||
|
borderRadius,
|
||||||
|
outline: 'none',
|
||||||
|
boxShadow,
|
||||||
|
|
||||||
|
'&-hidden': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
|
||||||
|
[`${mentionsCls}-dropdown-menu`]: {
|
||||||
|
maxHeight: 250, // FIXME: magic
|
||||||
|
marginBottom: 0,
|
||||||
|
paddingInlineStart: 0, // Override default ul/ol
|
||||||
|
overflow: 'auto',
|
||||||
|
listStyle: 'none',
|
||||||
|
outline: 'none',
|
||||||
|
|
||||||
|
'&-item': {
|
||||||
|
position: 'relative',
|
||||||
|
display: 'block',
|
||||||
|
minWidth: 100, // FIXME: magic
|
||||||
|
padding: `5px ${controlPaddingHorizontal}px`, // FIXME: magic
|
||||||
|
overflow: 'hidden',
|
||||||
|
color: textColor,
|
||||||
|
fontWeight: 'normal',
|
||||||
|
lineHeight,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: `background ${duration} ease`,
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: itemHoverBackground,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:first-child': {
|
||||||
|
borderStartStartRadius: borderRadius,
|
||||||
|
borderStartEndRadius: borderRadius,
|
||||||
|
borderEndStartRadius: 0,
|
||||||
|
borderEndEndRadius: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:last-child': {
|
||||||
|
borderStartStartRadius: 0,
|
||||||
|
borderStartEndRadius: 0,
|
||||||
|
borderEndStartRadius: borderRadius,
|
||||||
|
borderEndEndRadius: borderRadius,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-disabled': {
|
||||||
|
color: textColorDisabled,
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
color: textColorDisabled,
|
||||||
|
backgroundColor: itemHoverBackground,
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-selected': {
|
||||||
|
color: textColor,
|
||||||
|
fontWeight: 600, // FIXME: Need design token?
|
||||||
|
backgroundColor: backgroundLight,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-active': {
|
||||||
|
backgroundColor: itemHoverBackground,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================== Export ==============================
|
||||||
|
export default function useStyle(
|
||||||
|
prefixCls: string,
|
||||||
|
iconPrefixCls: string,
|
||||||
|
): UseComponentStyleResult {
|
||||||
|
const [theme, token, hashId] = useToken();
|
||||||
|
|
||||||
|
const mentionsToken: MentionsToken = {
|
||||||
|
...initInputToken(token, prefixCls, iconPrefixCls),
|
||||||
|
mentionsCls: `.${prefixCls}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => [
|
||||||
|
genMentionsStyle(mentionsToken),
|
||||||
|
]),
|
||||||
|
hashId,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user