# All files / src/icon TextIconSpacing.tsx

100% Statements 198/198
100% Branches 19/19
100% Functions 1/1
100% Lines 198/198

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    1991x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    461x
    461x
    1349x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1810x
    1330x
    1330x
    1330x
    1330x
    1802x
    19x
    19x
    19x
    19x
    19x
    19x
    1349x
    1349x
    1349x
    1349x
    1349x
    1349x
    1349x
    1349x
    1349x
    1349x
    1349x
    1349x
    1349x
     import { cnb } from "cnbuilder";
    import {
      Children,
      cloneElement,
      isValidElement,
      type ReactElement,
      type ReactNode,
    } from "react";

    export interface TextIconSpacingProps {
      /**
       * An optional className to apply to the surrounding `<span>` when the
       * `forceIconWrap` prop is enabled or the icon is not a valid React Element.
       * If the `forceIconWrap` prop is not enabled, it will be cloned into the icon
       * instead.
       */
      className?: string;

      /**
       * An optional icon to display with a text button. This is invalid for icon
       * buttons. If this is a single element, a new class name will be cloned into
       * the element to get correct spacing so if you have a custom icon element,
       * you **must** also pass that class name down. If you are using one of the
       * react-md icon component packages, this is handled automatically.
       *
       * If this is not a valid react element, the icon will be wrapped in a
       * `<span>` instead with the class names applied.
       */
      icon?: ReactElement | ReactNode;

      /**
       * Boolean if the icon should appear after the text instead of before.
       *
       * @defaultValue `false`
       */
      iconAfter?: boolean;

      /**
       * The children to render before or after the provided icon. This is defaulted
       * to `null` so that providing a `null` icon will correctly render without
       * React crashing.
       *
       * @defaultValue `null`
       */
      children?: ReactNode;

      /**
       * The class name to use for an icon that is placed before text.
       *
       * @defaultValue `"rmd-icon--before"`
       */
      beforeClassName?: string;

      /**
       * The class name to use for an icon that is placed after text.
       *
       * @defaultValue `"rmd-icon--after"`
       */
      afterClassName?: string;

      /**
       * The class name to use for an icon that is placed before above the text.
       * This is used when the `stacked` prop is enabled and the `iconAfter` prop is
       * disabled or omitted.
       *
       * @defaultValue `"rmd-icon--above"`
       */
      aboveClassName?: string;

      /**
       * The class name to use for an icon that is placed before above the text.
       * This is used when the `stacked` prop is enabled and the `iconAfter` prop is
       * enabled.
       *
       * @defaultValue `"rmd-icon--below"`
       */
      belowClassName?: string;

      /**
       * Boolean if the icon should be forced into a `<span>` with the class names
       * applied instead of attempting to clone into the provided icon.
       *
       * @defaultValue `false`
       */
      forceIconWrap?: boolean;

      /**
       * Boolean if the icon and text should be stacked instead of inline. Note:
       * You'll normally want to update the container element to have
       * `display: flex` and `flex-direction: column` for this to work.
       *
       * @defaultValue `false`
       */
      stacked?: boolean;

      /**
       * Boolean if the icon and content are in a `column-reverse` or `row-reverse`
       * `flex-direction`. This will swap the different classnames as needed.
       *
       * @remarks \@since 2.5.0
       * @defaultValue `false`
       */
      flexReverse?: boolean;
    }

    /**
     * **Server Component**
     *
     * Note: This component **must** be rendered within a flex container unless the
     * {@link TextIconSpacingProps.forceIconWrap} is set to `true`.
     *
     * @example
     * Simple Example
     * ```tsx
     * import { TextIconSpacing, FontIcon } from "@react-md/core";
     * import type { ReactElement } from "react";
     *
     * function Example(): ReactElement {
     *   return (
     *     <TextIconSpacing icon={<FontIcon>favorite</FontIcon>}>
     *       Favorite
     *     </TextIconSpacing>
     *   );
     * }
     * ```
     *
     * @example
     * Stacked Example
     * ```tsx
     * import { TextIconSpacing, FontIcon } from "@react-md/core";
     * import type { ReactElement } from "react";
     *
     * function Example(): ReactElement {
     *   return (
     *     <TextIconSpacing icon={<FontIcon>favorite</FontIcon>} stacked>
     *       Favorite
     *     </TextIconSpacing>
     *   );
     * }
     * ```
     */
    export function TextIconSpacing(props: TextIconSpacingProps): ReactElement {
      const {
        className,
        icon: propIcon,
        children = null,
        stacked = false,
        iconAfter = false,
        flexReverse = false,
        forceIconWrap = false,
        beforeClassName = "rmd-icon--before",
        afterClassName = "rmd-icon--after",
        aboveClassName = "rmd-icon--above",
        belowClassName = "rmd-icon--below",
      } = props;

      if (!propIcon) {
        return <>{children}</>;
      }

      const isAfter = flexReverse ? !iconAfter : iconAfter;
      const baseClassName = cnb(
        {
          [beforeClassName]: !stacked && !isAfter,
          [afterClassName]: !stacked && isAfter,
          [aboveClassName]: stacked && !isAfter,
          [belowClassName]: stacked && isAfter,
        },
        className
      );

      let iconEl = propIcon;
      let content = children;
      if (!forceIconWrap && isValidElement<{ className?: string }>(propIcon)) {
        const icon = Children.only(propIcon);
        iconEl = cloneElement(icon, {
          className: cnb(baseClassName, icon.props.className),
        });
      } else if (propIcon) {
        iconEl = (
          <span className={cnb("rmd-text-icon-spacing", baseClassName)}>
            {propIcon}
          </span>
        );
      }

      if (iconEl) {
        content = (
          <>
            {!iconAfter && iconEl}
            {children}
            {iconAfter && iconEl}
          </>
        );
      }

      return <>{content}</>;
    }