mattermost-mobile-upstream/app/components/attribute_chip/index.tsx
Pablo Vélez 4d842026f6
Some checks failed
ci / lint-typecheck (push) Has been cancelled
ci / detox-lint-typecheck (push) Has been cancelled
ci / test (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Mm 70564 channel labels use in channel creation (#10146)
* MM-70516 - Add ability to edit channel attribute values from Channel Info

* Add API methods for patching property fields and setting channel attribute requirements

* Update channel attribute clearing behavior for required and non-required fields

* MM-70564 - use channel attributes on channel creation

* Fix undefined propertyValues and ternary formatting in channel attributes

* Fix spelling of "colour" to "color" in attribute chip tests and related comments

* Add visibility checks for channel attribute labels in tests

* Fix data-integrity, permission, and accessibility issues in channel attributes: scoped pruning, atomic writes, race serialization, required clear/stale-option guards

* Fix handling of null values in apiGetChannelAttributeValue and update test assertions for channel attributes

* Improve channel attribute editor sizing for long labels, keep Save visible, and prevent keyboard overlap on Android

* Address CodeRabbit feedback for nullable values, bottom-sheet state, async validation, and reliable E2E cleanup

* lock-safe field fetch, create-time gate recheck, scoped value pruning, and tappable header chips

* Address CodeRabbit feedback: drop queued attribute saves on channel switch and use page object selectors in E2E

* Respect the ChannelAttributesRequired flag on channel creation and enable it for channel attribute E2E

* Skip required channel attribute E2E tests and stop enabling ChannelAttributesRequired on the shared server until  they move to SITE_3

* Fix clearing channel attributes on iOS by sending an empty value instead of null, and stop E2E suites from turning  off the ChannelAttributes flag mid-run

* Refactor getEmptyPropertyValue to accept string type for fieldType parameter

* E2E: give channel attribute test fields a suite-unique suffix

Pool shards may only create optional property fields named <name>_<6 hex>; fixed names were blocked by the pool guard.

* E2E: restore channel attribute cleanup checks; name network error codes

Brings back the PR's afterEach/afterAll error handling that the main merge replaced, and makes a no-response error show its codes (an AggregateError has an empty message).

* E2E: run channel attribute creation and editing specs on server B

Their 11 required-attribute tests could not run on the shared servers. Both specs now run on the mutation server with ChannelAttributesRequired on for their run, and the tests are re-enabled. setChannelAttributesRequired moves to the shared helper.

* Check header chips instead of their wrapper and use toExist for the create screen in channel attribute E2E

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
2026-10-30 15:03:55 +01:00

142 lines
5.1 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useMemo} from 'react';
import {Text, View} from 'react-native';
import {NEUTRAL_CHIP_HEADER_BG, NEUTRAL_CHIP_HEADER_TEXT} from '@constants/channel_attributes';
import {useTheme} from '@context/theme';
import {getContrastingSimpleColor} from '@utils/general';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
// Hard character cap per chip. Values longer than this are truncated with an
// ellipsis so one long label cannot consume the whole header row. The 'option'
// variant opts out: in a picker the whole option name has to be readable, since
// two markings can share their first fifteen characters.
const MAX_CHARS = 15;
// Wraps several AttributeChips for one multi-valued attribute so they wrap
// onto additional lines instead of running off-screen, right-aligned to match
// a single chip's position. Shared by every surface that renders more than
// one chip per attribute (header overflow sheet, Channel Info), so the
// wrapping behavior can't drift between them.
export const attributeChipGroupStyle = {
flex: 1,
flexDirection: 'row' as const,
alignItems: 'center' as const,
flexWrap: 'wrap' as const,
justifyContent: 'flex-end' as const,
gap: 4,
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
paddingHorizontal: 6,
borderRadius: 4,
justifyContent: 'center',
},
neutralContainer: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
},
// Neutral chip for the dark channel header: solid light-gray so it reads
// clearly on any sidebarBg color. Text is dark to contrast the light background.
neutralContainerHeader: {
backgroundColor: NEUTRAL_CHIP_HEADER_BG,
},
text: {
...typography('Body', 25, 'SemiBold'),
textTransform: 'uppercase',
letterSpacing: 0.2,
},
neutralText: {
color: changeOpacity(theme.centerChannelColor, 0.75),
},
neutralTextHeader: {
color: NEUTRAL_CHIP_HEADER_TEXT,
},
}));
type Props = {
// The attribute's name, used for the accessibility label so a screen reader
// hears which attribute the value belongs to.
label: string;
value: string;
// Hex from the option definition. Absent or malformed falls back to the
// neutral treatment rather than guessing at a readable foreground.
color?: string;
// False where the label is already visible beside the chip, so it is not
// announced twice.
announceLabel?: boolean;
// 'header' for chips inside the dark channel header; 'info' (default) for
// chips on a light surface such as Channel Info or the overflow sheet;
// 'option' for a chip in a picker, which renders the value in full.
// Only affects the neutral fallback colors — option colors are unchanged.
variant?: 'header' | 'info' | 'option';
testID?: string;
};
/**
* One channel attribute value, as a chip.
*
* The value is always rendered as text: color must never be the only carrier of
* meaning. The background is administrator-chosen, so the foreground is derived
* from its luminance with getContrastingSimpleColor, the same helper the channel
* banner uses.
*
* A chip is information, not a control. It reports nothing about access, and no
* string here may suggest otherwise.
*/
const AttributeChip = ({label, value, color, announceLabel = true, variant = 'info', testID}: Props) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const {containerStyle, textStyle} = useMemo(() => {
if (color) {
const foreground = getContrastingSimpleColor(color);
if (foreground) {
return {
containerStyle: [styles.container, {backgroundColor: color}],
textStyle: [styles.text, {color: foreground}],
};
}
}
// Malformed hex or no color: use the neutral fallback for this variant.
const neutralBg = variant === 'header' ? styles.neutralContainerHeader : styles.neutralContainer;
const neutralFg = variant === 'header' ? styles.neutralTextHeader : styles.neutralText;
return {
containerStyle: [styles.container, neutralBg],
textStyle: [styles.text, neutralFg],
};
}, [color, variant, styles]);
const displayValue = variant !== 'option' && value.length > MAX_CHARS ? `${value.slice(0, MAX_CHARS)}…` : value;
return (
<View
style={containerStyle}
testID={testID}
>
<Text
style={textStyle}
numberOfLines={variant === 'option' ? undefined : 1}
ellipsizeMode={variant === 'option' ? undefined : 'tail'}
accessibilityLabel={announceLabel ? `${label}: ${value}` : value}
testID={testID ? `${testID}.value` : undefined}
>
{displayValue}
</Text>
</View>
);
};
export default AttributeChip;