Skip to content

Commit

Permalink
[IOAPPFD0-144] Add the new IconContained component (#62)
Browse files Browse the repository at this point in the history
## Short description
This PR adds the new `IconContained`, a component designed to render an
icon inside a circle. Inspired by [Google Material Icon Button's various
contained styles](https://m3.material.io/components/icon-buttons/specs),
the component takes two props besides `icon`: `variant` and `color`. The
default and unique values, for now, are `tonal` (variant) and `neutral`
(color).

> [!WARNING]
> `IconContained` is just a special wrapper for the `Icon` component.
It's also not an interactive component, unlike the Google Material's
`IconButton`. When adding new styles, you should be aware of this
context and be careful not to add variants that look like interactive
counterparts.

## List of changes proposed in this pull request
- Add the new `IconContained` component
- **[EXTRA]** Add the new `transactions` icon

### Preview
<img
src="https://github.com/pagopa/io-app-design-system/assets/1255491/40c1ae7f-8ee0-47da-b10c-386ad77d773b"
width="300" />

## How to test
Go to the **Icons (Foundation)** in the example app.

---------

Co-authored-by: Cristiano Tofani <cri.tofani@gmail.com>
  • Loading branch information
dmnplb and CrisTofani authored Sep 15, 2023
1 parent 634e97e commit 7b5ea7e
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 4 deletions.
23 changes: 22 additions & 1 deletion example/src/pages/Icons.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import {
H2,
H3,
H4,
HSpacer,
IOBiometricIcons,
IOCategoryIcons,
IOColors,
IOIconSizeScale,
IOIcons,
IONavIcons,
IOProductIcons,
IOStyles,
IOSystemIcons,
IOThemeContext,
IOVisualCostants,
Icon,
IconContained,
SVGIconProps
} from "@pagopa/io-app-design-system";
import React, { useContext } from "react";
import { StyleSheet, View } from "react-native";
import { IconViewerBox, iconItemGutter } from "../components/IconViewerBox";
import { Screen } from "../components/Screen";
import { ComponentViewerBox } from "../components/ComponentViewerBox";

// Filter the main object, removing already displayed icons in the other sets
type IconSubsetObject = Record<
Expand Down Expand Up @@ -208,7 +213,23 @@ export const Icons = () => {
/>
))}
</View>
<H3 color={theme["textHeading-default"]} style={{ marginBottom: 12 }}>

<H4 color={theme["textHeading-default"]} style={{ marginBottom: 12 }}>
IconContained
</H4>
<ComponentViewerBox name={"IconContained, default variant"}>
<View style={IOStyles.row}>
<IconContained icon="device" variant="tonal" color="neutral" />
<HSpacer size={8} />
<IconContained icon="institution" variant="tonal" color="neutral" />
</View>
</ComponentViewerBox>

<H3
color={theme["textHeading-default"]}
weight={"Bold"}
style={{ marginBottom: 12 }}
>
Sizes
</H3>
<View style={styles.itemsWrapper}>
Expand Down
4 changes: 3 additions & 1 deletion src/components/icons/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ import IconSystemSettingsAndroid from "./svg/IconSystemSettingsAndroid";
import IconSystemSettingsiOS from "./svg/IconSystemSettingsiOS";
import IconSystemToggleInstructions from "./svg/IconSystemToggleInstructions";
import IconTag from "./svg/IconTag";
import IconTransactionsBoxed from "./svg/IconTransactionsBoxed";
import IconTransactions from "./svg/IconTransactions";
import IconTrashcan from "./svg/IconTrashcan";
import IconWarningFilled from "./svg/IconWarningFilled";
Expand Down Expand Up @@ -166,6 +167,7 @@ export const IOIcons = {
copy: IconCopy,
selfCert: IconSelfCertification,
institution: IconInstitution,
merchant: IconMerchant,
hourglass: IconHourglass,
shareiOs: IconShareiOs,
shareAndroid: IconShareAndroid,
Expand Down Expand Up @@ -203,6 +205,7 @@ export const IOIcons = {
fiscalCodeIndividual: IconFiscalCodeIndividual,
creditCard: IconCreditCard,
bonus: IconBonus,
transactionsBoxed: IconTransactionsBoxed,
transactions: IconTransactions,
amount: IconAmount,
psp: IconPSP,
Expand Down Expand Up @@ -296,7 +299,6 @@ export const IOIcons = {
productIOAppBlueBg: IconProductIOAppBlueBg,
checkTick: IconCheckTick,
checkTickBig: IconCheckTickBig,
merchant: IconMerchant,
light: IconLight,
lightFilled: IconLightFilled,
systemSettingsAndroid: IconSystemSettingsAndroid,
Expand Down
76 changes: 76 additions & 0 deletions src/components/icons/IconContained.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from "react";
import { useMemo } from "react";
import { StyleSheet, View } from "react-native";
import { IOVisualCostants, IOColors } from "../../core";
import { IOIcons, Icon } from ".";

type IconContained = {
variant: "tonal";
color: "neutral";
icon: IOIcons;
};

type IconContainedVisualAttrs = {
background: IOColors;
foreground: IOColors;
};

type IconContainedColorVariants = Record<
IconContained["color"],
IconContainedVisualAttrs
>;

const tonalColorMap: IconContainedColorVariants = {
neutral: {
background: "blueIO-50",
foreground: "grey-450"
}
};

const variantMap: Record<IconContained["variant"], IconContainedColorVariants> =
{
tonal: tonalColorMap
};

const IconContainedDefaultSize = IOVisualCostants.iconContainedSizeDefault;

const styles = StyleSheet.create({
iconContainedWrapper: {
overflow: "hidden",
display: "flex",
alignItems: "center",
justifyContent: "center",
width: IconContainedDefaultSize,
height: IconContainedDefaultSize,
borderRadius: IconContainedDefaultSize / 2
}
});

/*
`IconContained` is just a special wrapper for the `Icon` component.
It's also not an interactive component, unlike the `IconButton`.
When adding new styles, you should be aware of this context and be careful
not to add variants that look like interactive counterparts.
*/
export const IconContained = ({ variant, color, icon }: IconContained) => {
const backgroundColor = useMemo(
() => variantMap[variant][color].background,
[variant, color]
);

const foregroundColor = useMemo(
() => variantMap[variant][color].foreground,
[variant, color]
);

return (
<View
style={[
styles.iconContainedWrapper,
{ backgroundColor: IOColors[backgroundColor] }
]}
>
<Icon name={icon} color={foregroundColor} />
</View>
);
};
1 change: 1 addition & 0 deletions src/components/icons/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./Icon";
export * from "./IconContained";
2 changes: 1 addition & 1 deletion src/components/icons/svg/IconTransactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const IconTransactions = ({ size, style, ...props }: SVGIconProps) => (
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M5 0C2.23858 0 0 2.23858 0 5v14c0 2.7614 2.23858 5 5 5h14c2.7614 0 5-2.2386 5-5V5c0-2.76142-2.2386-5-5-5H5ZM2 5c0-1.65685 1.34315-3 3-3h14c1.6569 0 3 1.34315 3 3v14c0 1.6569-1.3431 3-3 3H5c-1.65685 0-3-1.3431-3-3V5Zm7.70714.29289c.39056.39053.39056 1.02369 0 1.41422L7.41424 9H19c.5523 0 1 .44772 1 1 0 .5523-.4477 1-1 1H5.00003c-.40446 0-.7691-.2436-.92388-.6173-.15478-.3737-.06923-.80381.21677-1.08981l4-4c.39053-.39052 1.02369-.39052 1.41422 0ZM14.2929 18.7071c-.3906-.3905-.3906-1.0237 0-1.4142L16.5858 15H4.99997c-.55228 0-1-.4477-1-1s.44772-1 1-1H19c.4044 0 .7691.2436.9239.6173.1547.3737.0692.8038-.2168 1.0898l-4 4c-.3905.3905-1.0237.3905-1.4142 0Z"
d="M8.70716 2.29289c.39053.39053.39053 1.02369 0 1.41422L3.41427 9H23.0001c.5522 0 1 .44772 1 1 0 .5523-.4478 1-1 1H1.00006c-.40446 0-.7691-.2436-.92388-.6173-.15478-.3737-.06923-.80381.21677-1.08981l7-7c.39053-.39052 1.02369-.39052 1.41421 0ZM15.2929 21.7071c-.3905-.3905-.3905-1.0237 0-1.4142L20.5858 15H1c-.55229 0-1-.4477-1-1s.44771-1 1-1h22c.4045 0 .7691.2436.9239.6173.1548.3737.0692.8038-.2168 1.0898l-7 7c-.3905.3905-1.0237.3905-1.4142 0Z"
fill="currentColor"
/>
</Svg>
Expand Down
16 changes: 16 additions & 0 deletions src/components/icons/svg/IconTransactionsBoxed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";
import { Svg, Path } from "react-native-svg";
import { SVGIconProps } from "../Icon";

const IconTransactionsBoxed = ({ size, style, ...props }: SVGIconProps) => (
<Svg width={size} height={size} viewBox="0 0 24 24" style={style} {...props}>
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M5 0C2.23858 0 0 2.23858 0 5v14c0 2.7614 2.23858 5 5 5h14c2.7614 0 5-2.2386 5-5V5c0-2.76142-2.2386-5-5-5H5ZM2 5c0-1.65685 1.34315-3 3-3h14c1.6569 0 3 1.34315 3 3v14c0 1.6569-1.3431 3-3 3H5c-1.65685 0-3-1.3431-3-3V5Zm7.70714.29289c.39056.39053.39056 1.02369 0 1.41422L7.41424 9H19c.5523 0 1 .44772 1 1 0 .5523-.4477 1-1 1H5.00003c-.40446 0-.7691-.2436-.92388-.6173-.15478-.3737-.06923-.80381.21677-1.08981l4-4c.39053-.39052 1.02369-.39052 1.41422 0ZM14.2929 18.7071c-.3906-.3905-.3906-1.0237 0-1.4142L16.5858 15H4.99997c-.55228 0-1-.4477-1-1s.44772-1 1-1H19c.4044 0 .7691.2436.9239.6173.1547.3737.0692.8038-.2168 1.0898l-4 4c-.3905.3905-1.0237.3905-1.4142 0Z"
fill="currentColor"
/>
</Svg>
);

export default IconTransactionsBoxed;
8 changes: 8 additions & 0 deletions src/components/icons/svg/originals/IconTransactions.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/components/icons/svg/originals/IconTransactionsBoxed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/core/IOStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ interface IOVisualCostants {
// Dimensions
avatarSizeSmall: number;
avatarSizeMedium: number;
iconContainedSizeDefault: number;
}

export const IOVisualCostants: IOVisualCostants = {
appMarginDefault: 24,
headerHeight: 56,
avatarSizeSmall: 44,
avatarSizeMedium: 66
avatarSizeMedium: 66,
iconContainedSizeDefault: 44
};

export const IOStyles = StyleSheet.create({
Expand Down

0 comments on commit 7b5ea7e

Please sign in to comment.