Skip to content

Commit

Permalink
fix(#128 #124 #153): form item design fixes and notification buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
dabrad26 committed Nov 29, 2023
1 parent 7751e95 commit 2c3f733
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 40 deletions.
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

0 comments on commit 2c3f733

Please sign in to comment.