Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(#128 #124 #153): form item design fixes and notification buttons #169

Merged
merged 1 commit into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions example/src/Views/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,32 @@ export default class TestButton extends React.Component {
this.alert('Long pressed long text with icon');
}}
/>
<Button
style={itemStyle}
disabled={disabled}
kind="high-contrast"
icon={AddIcon}
text="High contrast"
onPress={() => {
this.alert('Pressed high contrast');
}}
onLongPress={() => {
this.alert('Long pressed high contrast');
}}
/>
<Button
style={itemStyle}
disabled={disabled}
kind="high-contrast-inverse"
icon={AddIcon}
text="High contrast inverse"
onPress={() => {
this.alert('Pressed high contrast inverse');
}}
onLongPress={() => {
this.alert('Long pressed high contrast inverse');
}}
/>
</ScrollView>
);
}
Expand Down
4 changes: 3 additions & 1 deletion example/src/Views/FormItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default class TestFormItem extends React.Component {
birthDay: '',
toggleTest: false,
renderLeft: false,
toggleInlineTest: false,
volume: '35',
paymentType: 'card',
};
Expand All @@ -41,7 +42,7 @@ export default class TestFormItem extends React.Component {
};

render(): React.ReactNode {
const { firstName, password, textArea, age, birthDay, toggleTest, volume, paymentType, renderLeft } = this.state;
const { firstName, password, textArea, age, birthDay, toggleTest, volume, paymentType, renderLeft, toggleInlineTest } = this.state;

return (
<ScrollView keyboardShouldPersistTaps="handled" contentInsetAdjustmentBehavior="automatic" contentContainerStyle={styles.container} style={styles.view}>
Expand All @@ -51,6 +52,7 @@ export default class TestFormItem extends React.Component {
<FormItem type="checkbox" renderToggleCheckboxLeft={renderLeft} overrideActiveCheckboxIcon={RadioIcon} label="Cash on delivery" value={paymentType === 'cash'} onChange={() => this.setState({ paymentType: 'cash' })} />
<FormItem type="header" label="Example header" helperText="Helper text to explain what the form section below includes" />
<FormItem type="text" label="First name" helperText="This will be your first name" value={firstName} onChange={(value) => this.setState({ firstName: value })} textInputProps={{ required: true, getErrorText: () => 'Item is required', placeholder: 'Required' }} />
<FormItem type="toggle-inline" label="Inline toggle" value={toggleInlineTest} renderToggleCheckboxLeft={renderLeft} onChange={(value) => this.setState({ toggleInlineTest: value })} toggleValueText={(value) => (value ? 'Yes' : 'No')} />
<FormItem type="toggle" label="Raise to wake" value={toggleTest} renderToggleCheckboxLeft={renderLeft} onChange={(value) => this.setState({ toggleTest: value })} toggleValueText={(value) => (value ? 'On' : 'Off')} />
<FormItem type="toggle" label="Move to left" helperText="Move checkbox/toggle to left side" value={renderLeft} renderToggleCheckboxLeft={renderLeft} onChange={(value) => this.setState({ renderLeft: value })} toggleValueText={(value) => (value ? 'On' : 'Off')} />
<FormItem type="password" label="Password" value={password} onChange={(value) => this.setState({ password: value })} />
Expand Down
6 changes: 4 additions & 2 deletions example/src/Views/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default class TestNotification extends React.Component {
longSubTitle: false,
lowContrast: false,
multiLine: false,
shadow: false,
};

private get styles() {
Expand Down Expand Up @@ -41,7 +42,7 @@ export default class TestNotification extends React.Component {
};

render(): React.ReactNode {
const { showSubtitle, showAction, showDismiss, lowContrast, multiLine, longTitle, longSubTitle } = this.state;
const { showSubtitle, showAction, showDismiss, lowContrast, multiLine, longTitle, longSubTitle, shadow } = this.state;
const types: NotificationTypes[] = ['info', 'error', 'warning', 'success'];

return (
Expand All @@ -53,8 +54,9 @@ export default class TestNotification extends React.Component {
<Checkbox checked={multiLine} id="multi" onPress={(value) => this.setState({ multiLine: value })} label="Multi line mode" />
<Checkbox checked={longTitle} id="long-title" onPress={(value) => this.setState({ longTitle: value })} label="Very long title" />
<Checkbox checked={longSubTitle} id="long-sub" onPress={(value) => this.setState({ longSubTitle: value })} label="Very long sub title" />
<Checkbox checked={shadow} id="lshadow" onPress={(value) => this.setState({ shadow: value })} label="Render shadow" />
{types.map((type) => {
return <Notification style={this.styles.baseSpacing} key={type} kind={type} multiLine={multiLine} lowContrast={lowContrast} actionArea={showAction ? <Button kind={multiLine ? 'tertiary' : 'ghost'} overrideColor={getColor('linkInverse')} style={this.styles.button} onPress={this.actionCallback} text="Action" /> : undefined} title={longTitle ? 'Awesome notification with a crazy long title that is really weird just to be long' : 'Awesome notification'} subTitle={showSubtitle ? (longSubTitle ? 'Useful subtitle information about this notification with even longer info that will be useful for testing extreme edge cases.' : 'Useful subtitle information about this notification') : undefined} onDismiss={showDismiss ? this.onDismiss : undefined} />;
return <Notification style={this.styles.baseSpacing} key={type} dropShadow={shadow} kind={type} multiLine={multiLine} lowContrast={lowContrast} actionArea={showAction ? <Button forceTheme={lowContrast && multiLine ? 'light' : undefined} kind={multiLine ? (lowContrast ? 'high-contrast' : 'high-contrast-inverse') : 'ghost'} overrideColor={multiLine ? undefined : getColor('linkInverse')} style={this.styles.button} onPress={this.actionCallback} text="Action" /> : undefined} title={longTitle ? 'Awesome notification with a crazy long title that is really weird just to be long' : 'Awesome notification'} subTitle={showSubtitle ? (longSubTitle ? 'Useful subtitle information about this notification with even longer info that will be useful for testing extreme edge cases.' : 'Useful subtitle information about this notification') : undefined} onDismiss={showDismiss ? this.onDismiss : undefined} />;
})}
</ScrollView>
);
Expand Down
8 changes: 4 additions & 4 deletions src/components/BaseTextInputs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,16 @@ export const getTextInputStyle = (light?: boolean, hasLabelLink?: boolean, fullB
helperText: {
color: getColor('textHelper'),
marginTop: 8,
marginBottom: fullBleed ? 8 : undefined,
marginBottom: fullBleed ? 20 : undefined,
},
errorText: {
color: getColor('textError'),
marginTop: 8,
marginBottom: fullBleed ? 8 : undefined,
marginBottom: fullBleed ? 20 : undefined,
},
warningText: {
marginTop: 8,
marginBottom: fullBleed ? 8 : undefined,
marginBottom: fullBleed ? 20 : undefined,
},
textBox: baseTextBox,
textBoxDisabled: {
Expand Down Expand Up @@ -135,7 +135,7 @@ export const getTextInputStyle = (light?: boolean, hasLabelLink?: boolean, fullB
errorIcon: {
position: 'absolute',
padding: 13,
top: 0,
top: fullBleed ? '100%' : 0,
right: 0,
},
numberActions: {
Expand Down
71 changes: 53 additions & 18 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Ref } from 'react';
import { GestureResponderEvent, Keyboard, Pressable, PressableProps, PressableStateCallbackType, StyleProp, StyleSheet, TextStyle, View, ViewStyle } from 'react-native';
import type { CarbonIcon } from '../../types/shared';
import { createIcon, pressableFeedbackStyle, styleReferenceBreaker } from '../../helpers';
import { getColor } from '../../styles/colors';
import { ThemeChoices, getColor } from '../../styles/colors';
import { Text, TextBreakModes, TextTypes } from '../Text';

/** Props for Button component */
Expand All @@ -16,7 +16,7 @@ export type ButtonProps = {
/** Indicate if button is disabled */
disabled?: boolean;
/** Button kind. Primary is default */
kind?: 'primary' | 'secondary' | 'tertiary' | 'danger' | 'ghost' | 'danger-tertiary' | 'danger-ghost';
kind?: 'primary' | 'secondary' | 'tertiary' | 'danger' | 'ghost' | 'danger-tertiary' | 'danger-ghost' | 'high-contrast' | 'high-contrast-inverse';
/** Text type to render (Standard is default) */
textType?: TextTypes;
/** onPress event */
Expand All @@ -37,6 +37,8 @@ export type ButtonProps = {
disableDesignPadding?: boolean;
/** Break mode used on string type content (default is tail) */
breakMode?: TextBreakModes;
/** Force theme color (useful for rendering on light items in other mode) */
forceTheme?: ThemeChoices;
};

/**
Expand Down Expand Up @@ -68,37 +70,44 @@ export class Button extends React.Component<ButtonProps> {
}

private getBackgroundColor(active?: boolean): string {
const { kind, disabled } = this.props;
const { kind, disabled, forceTheme } = this.props;

switch (kind) {
case 'secondary':
return getColor(disabled ? 'buttonDisabled' : active ? 'buttonSecondaryActive' : 'buttonSecondary');
return getColor(disabled ? 'buttonDisabled' : active ? 'buttonSecondaryActive' : 'buttonSecondary', forceTheme);
case 'tertiary':
return active ? getColor('buttonTertiaryActive') : 'transparent';
return active ? getColor('buttonTertiaryActive', forceTheme) : 'transparent';
case 'high-contrast':
return active ? getColor('buttonHighContrastActive', forceTheme) : 'transparent';
case 'high-contrast-inverse':
return active ? getColor('buttonHighContrastInverseActive', forceTheme) : 'transparent';
case 'danger':
return getColor(disabled ? 'buttonDisabled' : active ? 'buttonDangerActive' : 'buttonDangerPrimary');
return getColor(disabled ? 'buttonDisabled' : active ? 'buttonDangerActive' : 'buttonDangerPrimary', forceTheme);
case 'ghost':
return active ? getColor('layerActive01') : 'transparent';
return active ? getColor('layerActive01', forceTheme) : 'transparent';
case 'danger-ghost':
case 'danger-tertiary':
return active ? getColor('buttonDangerActive') : 'transparent';
return active ? getColor('buttonDangerActive', forceTheme) : 'transparent';
case 'primary':
default:
return getColor(disabled ? 'buttonDisabled' : active ? 'buttonPrimaryActive' : 'buttonPrimary');
return getColor(disabled ? 'buttonDisabled' : active ? 'buttonPrimaryActive' : 'buttonPrimary', forceTheme);
}
}

private getStateStyle = (state: PressableStateCallbackType): StyleProp<ViewStyle> => {
const { kind } = this.props;
const keepBorder = ['high-contrast', 'high-contrast-inverse'].includes(kind as string);

return state.pressed
? {
backgroundColor: this.getBackgroundColor(true),
borderWidth: 0,
borderWidth: keepBorder ? 1 : 0,
}
: undefined;
};

private get buttonStyle(): StyleProp<ViewStyle> {
const { kind, style, disabled, iconOnlyMode, icon, overrideColor, disableDesignPadding } = this.props;
const { kind, style, disabled, iconOnlyMode, icon, overrideColor, disableDesignPadding, forceTheme } = this.props;
let finalStyle: any = {};

switch (kind) {
Expand All @@ -114,7 +123,27 @@ export class Button extends React.Component<ButtonProps> {
finalStyle = styleReferenceBreaker(
{
backgroundColor: this.getBackgroundColor(),
borderColor: getColor(disabled ? 'buttonDisabled' : 'buttonTertiary'),
borderColor: getColor(disabled ? 'buttonDisabled' : 'buttonTertiary', forceTheme),
borderWidth: 1,
},
this.basicButton
);
break;
case 'high-contrast':
finalStyle = styleReferenceBreaker(
{
backgroundColor: this.getBackgroundColor(),
borderColor: getColor(disabled ? 'buttonDisabled' : 'buttonHighContrast', forceTheme),
borderWidth: 1,
},
this.basicButton
);
break;
case 'high-contrast-inverse':
finalStyle = styleReferenceBreaker(
{
backgroundColor: this.getBackgroundColor(),
borderColor: getColor(disabled ? 'buttonDisabled' : 'buttonHighContrastInverse', forceTheme),
borderWidth: 1,
},
this.basicButton
Expand All @@ -124,7 +153,7 @@ export class Button extends React.Component<ButtonProps> {
finalStyle = styleReferenceBreaker(
{
backgroundColor: this.getBackgroundColor(),
borderColor: getColor(disabled ? 'buttonDisabled' : 'buttonDangerSecondary'),
borderColor: getColor(disabled ? 'buttonDisabled' : 'buttonDangerSecondary', forceTheme),
borderWidth: 1,
},
this.basicButton
Expand Down Expand Up @@ -183,21 +212,25 @@ export class Button extends React.Component<ButtonProps> {
}

private get iconTextColor(): string {
const { kind, disabled, overrideColor } = this.props;
const { kind, disabled, overrideColor, forceTheme } = this.props;

switch (kind) {
case 'danger-ghost':
case 'danger-tertiary':
return overrideColor || getColor(disabled ? 'textDisabled' : 'buttonDangerSecondary');
return overrideColor || getColor(disabled ? 'textDisabled' : 'buttonDangerSecondary', forceTheme);
case 'tertiary':
return overrideColor || getColor(disabled ? 'textDisabled' : 'buttonTertiary');
return overrideColor || getColor(disabled ? 'textDisabled' : 'buttonTertiary', forceTheme);
case 'high-contrast':
return overrideColor || getColor(disabled ? 'textDisabled' : 'buttonHighContrast', forceTheme);
case 'high-contrast-inverse':
return overrideColor || getColor(disabled ? 'textDisabled' : 'buttonHighContrastInverse', forceTheme);
case 'ghost':
return overrideColor || getColor(disabled ? 'textDisabled' : 'linkPrimary');
return overrideColor || getColor(disabled ? 'textDisabled' : 'linkPrimary', forceTheme);
case 'primary':
case 'secondary':
case 'danger':
default:
return overrideColor || getColor(disabled ? 'textOnColorDisabled' : 'textOnColor');
return overrideColor || getColor(disabled ? 'textOnColorDisabled' : 'textOnColor', forceTheme);
}
}

Expand All @@ -208,6 +241,8 @@ export class Button extends React.Component<ButtonProps> {
switch (kind) {
case 'tertiary':
case 'ghost':
case 'high-contrast':
case 'high-contrast-inverse':
finalStyle = styleReferenceBreaker({
color: this.iconTextColor,
textAlign: 'left',
Expand Down
31 changes: 25 additions & 6 deletions src/components/FormItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import { Slider } from '../Slider';
* `password` - secure text input. Use textInputProps for advanced controls.
* `textarea` - plain text input with multi line support. Use textInputProps for advanced controls.
* `toggle` - Toggle field
* `toggle-inline` - Toggle inline (single line) field. Label and toggle are on single line (not stacked)
* `header` - Header with text and supported secondary text. This is for logical breaking up of form items.
* `static` - Static data to render (view only)
* `slider` - Slider bar to render
* `checkbox` - Checkbox to render. This can also be used as radio for checking proper items in the list. Use `overrideActiveCheckboxIcon` to override the icon
* `button` - Button to render. Supports icon via `buttonIcon`.
* `divider` - Empty space to divide form items.
*/
export type FormItemType = 'text' | 'password' | 'text-area' | 'number' | 'date' | 'toggle' | 'header' | 'header-compact' | 'static' | 'slider' | 'checkbox' | 'button' | 'divider';
export type FormItemType = 'text' | 'password' | 'text-area' | 'number' | 'date' | 'toggle' | 'toggle-inline' | 'header' | 'header-compact' | 'static' | 'slider' | 'checkbox' | 'button' | 'divider';

/** Props for FormItem component */
export type FormItemProps = {
Expand Down Expand Up @@ -59,7 +60,7 @@ export type FormItemProps = {
toggleValueText?: (value: boolean) => string;
/** Override icon for select item (default is Checkbox) */
overrideActiveCheckboxIcon?: CarbonIcon;
/** Indicate that toggle or checkbox should render on the left side. Default is right */
/** Indicate that toggle or checkbox should render on the left side. Default is right. This does not apply to `toggle-inline` */
renderToggleCheckboxLeft?: boolean;
/** Slider props for customizing slider */
sliderProps?: {
Expand Down Expand Up @@ -106,7 +107,7 @@ export class FormItem extends React.Component<FormItemProps> {

private get noDirectLabel(): boolean {
const { type } = this.props;
return ['header', 'header-compact', 'divider', 'button', 'checkbox'].includes(type);
return ['header', 'header-compact', 'divider', 'button', 'checkbox', 'toggle-inline'].includes(type);
}

private get mainColor(): string {
Expand Down Expand Up @@ -173,6 +174,18 @@ export class FormItem extends React.Component<FormItemProps> {
paddingBottom: 13,
paddingLeft: renderToggleCheckboxLeft ? 30 : undefined,
},
toggleInlineText: {
paddingTop: 13,
paddingBottom: 13,
marginRight: 12,
color: getColor(disabled ? 'textDisabled' : 'textSecondary'),
},
toggleInlineLabel: {
color: this.mainColor,
flex: 1,
paddingTop: 13,
paddingBottom: 13,
},
toggleWrapper: {
paddingTop: 0,
},
Expand Down Expand Up @@ -262,15 +275,20 @@ export class FormItem extends React.Component<FormItemProps> {
}

private get toggleContent(): React.ReactNode {
const { label, textBreakMode, disabled, value, toggleValueText, renderToggleCheckboxLeft } = this.props;
const { label, textBreakMode, disabled, value, toggleValueText, renderToggleCheckboxLeft, type } = this.props;
const inline = type === 'toggle-inline';

const changeValue = (textValue: boolean) => {
this.triggerChange(textValue);
};

const items = [<Text key="label" text={typeof toggleValueText === 'function' ? toggleValueText(!!value) : String(value)} style={this.styles.toggleText} breakMode={textBreakMode} />, <Toggle key="toggle" label={label || ''} style={this.styles.toggleWrapper} toggleWrapperStyle={this.styles.toggleDirectWrapper} hideLabel={true} disabled={disabled} toggled={!!value} onChange={changeValue} />];
const items = [<Text key="label" text={typeof toggleValueText === 'function' ? toggleValueText(!!value) : String(value)} style={inline ? this.styles.toggleInlineText : this.styles.toggleText} breakMode={textBreakMode} />, <Toggle key="toggle" label={label || ''} style={this.styles.toggleWrapper} toggleWrapperStyle={this.styles.toggleDirectWrapper} hideLabel={true} disabled={disabled} toggled={!!value} onChange={changeValue} />];

if (inline) {
items.unshift(<Text style={this.styles.toggleInlineLabel} text={label} />);
}

return renderToggleCheckboxLeft ? items.reverse() : items;
return renderToggleCheckboxLeft && !inline ? items.reverse() : items;
}

private get staticContent(): React.ReactNode {
Expand Down Expand Up @@ -339,6 +357,7 @@ export class FormItem extends React.Component<FormItemProps> {
content = this.headerContent;
break;
case 'toggle':
case 'toggle-inline':
content = this.toggleContent;
finalStyle.flexDirection = 'row';
break;
Expand Down
Loading
Loading