diff --git a/.storybook/theme.ts b/.storybook/theme.ts index be10cf1b92..f8403eb342 100644 --- a/.storybook/theme.ts +++ b/.storybook/theme.ts @@ -53,15 +53,15 @@ export const darkTheme = create({ barSelectedColor: tokens["color-base-blue--100"], // UI - appBg: '#ddffff', - appContentBg: 'var(--color-surface)', - appPreviewBg: 'var(--color-surface)', - appBorderColor: '#585C6D', - appBorderRadius: 4, + appBg: tokens["color-base-blue--1000"], + appContentBg: tokens["color-base-blue--900"], + appPreviewBg: tokens["color-base-blue--900"], + appBorderColor: tokens["color-base-blue--700"], + appBorderRadius: tokens["radius-base"], /** * Brand Identity */ brandTitle: "πŸ”± Atlantis", -}) \ No newline at end of file +}) diff --git a/docs/components/Chip/Chip.stories.mdx b/docs/components/Chip/Chip.stories.mdx index b8d1383ab2..72ebb596f4 100644 --- a/docs/components/Chip/Chip.stories.mdx +++ b/docs/components/Chip/Chip.stories.mdx @@ -8,31 +8,37 @@ import { Flex } from "@jobber/components/Flex"; # Chip -Chip is a flexible component that can be used for +Chip is a flexible component that can be used for + - inline single- or multi-selection of items -- triggering filtering and selection components like [Combobox](../?path=/docs/components-selections-combobox--docs) +- triggering filtering and selection components like + [Combobox](../?path=/docs/components-selections-combobox--docs) - presenting grouped items that can be added or removed - + - + ## Usage guidelines -See the [Comparison story](../?path=/story/components-selections-chip-web-comparisons--all) +See the +[Comparison story](../?path=/story/components-selections-chip-web-comparisons--all) for a full overview of potential Chip variants. ### Variations -The base variation of Chip should be used in most cases. When a lighter-weight approach -is desired, use the subtle variation. +The base variation of Chip should be used in most cases. When a lighter-weight +approach is desired, use the subtle variation. @@ -43,9 +49,9 @@ is desired, use the subtle variation. ### Selection -Chip allows users to make selections in scenarios where space -is at a premium. It has three high-level usages: single-select, multi-select, -and add/dismiss selection. +Chip allows users to make selections in scenarios where space is at a premium. +It has three high-level usages: single-select, multi-select, and add/dismiss +selection. #### Single-select @@ -61,12 +67,16 @@ selected single-select Chip can be de-selected by the user, leaving all selections blank. - + - + @@ -88,22 +98,26 @@ Similar to Checkbox, a selected multi-select Chip can be de-selected by the user, leaving all selections blank. - + - + - + - + - + @@ -111,8 +125,8 @@ user, leaving all selections blank. #### Add/dismiss selection When the user will be selecting one or more items by inputting their own Chip -options, use a dismissible Chip. Think of a case like team assignment, where -the user can add multiple users represented as Chips, and click on the dismiss +options, use a dismissible Chip. Think of a case like team assignment, where the +user can add multiple users represented as Chips, and click on the dismiss suffix of the Chip to remove a user. The dismissible Chip allows them to remove previous selections from the Chips. @@ -122,56 +136,72 @@ would be far too many Chip options to present in one group, and would be overwhelming for the user to interpret. - + - + - + - + - + - + - + - + ### Invalid -If something goes awry with a selection or you otherwise need to convey to the user -that something's gone wrong in relation to the Chip, you can use the invalid state. +If something goes awry with a selection or you otherwise need to convey to the +user that something's gone wrong in relation to the Chip, you can use the +invalid state. - + +## Sub Components + +### Chip.Prefix + +When `Chip.Prefix` is provided an Icon or Avatar as the immediate child or +children, some extra markup and styles will automatically be added. If these +styles and markup are not desired, you may wrap the Avatar(s) or Icon(s) in any +other element and provide your own layout. + ## Related components -- [Chips](../?path=/docs/components-selections-chips--docs) is a convenience wrapper - that offers the single-select, multi-select, and add/dismiss functionality "out of the box" -- [Combobox](../?path=/docs/components-selections-combobox--docs) is most commonly - triggered by a Chip, but is a separate component -- [Select](../?path=/docs/components-selections-select--docs) is a simpler single-select - "dropdown" that presents as a form element and should be preferred in forms +- [Chips](../?path=/docs/components-selections-chips--docs) is a convenience + wrapper that offers the single-select, multi-select, and add/dismiss + functionality "out of the box" +- [Combobox](../?path=/docs/components-selections-combobox--docs) is most + commonly triggered by a Chip, but is a separate component +- [Select](../?path=/docs/components-selections-select--docs) is a simpler + single-select "dropdown" that presents as a form element and should be + preferred in forms - [RadioGroup](../?path=/docs/components-forms-and-inputs-radiogroup--docs) should be used to allow the user to select "one-of-many" items (single-select) and the labels for the items are longer than 1 or 2 words. @@ -179,18 +209,18 @@ that something's gone wrong in relation to the Chip, you can use the invalid sta to allow the user to select "one-or-more-of-many" items (multi-select) and the labels for the items are longer than 1 or 2 words. - [InlineLabel](../?path=/docs/components-status-and-feedback-inlinelabel--docs) - should be used when you just need a rounded-rectangular element that displays + should be used when you just need a rounded-rectangular element that displays metadata about an element ## Content guidelines -Chip headings and labels for single- or multi-select should be succinct - ideally 1–2 words. -If any of the options in the group may have longer labels, consider Checkbox or Radio as necessary -for your selection type. +Chip headings and labels for single- or multi-select should be succinct - +ideally 1–2 words. If any of the options in the group may have longer labels, +consider Checkbox or Radio as necessary for your selection type. -In cases where a Chip displays name of its selections, such as when used to trigger -a Combobox or a date range selector, use the heading to identify the "category" and -the label to identify the selected items. +In cases where a Chip displays name of its selections, such as when used to +trigger a Combobox or a date range selector, use the heading to identify the +"category" and the label to identify the selected items. ## Accessibility @@ -202,7 +232,6 @@ dealing with a checkbox or radio button. If Chips is set for add/dismiss selections, the dismiss button should notify the user that they will "dismiss \{label name\}" upon press. - ## Responsiveness The Chips themselves will take up as much space as their container allows, and @@ -212,9 +241,9 @@ out of view in a single row, depending on your use case. Chip can truncate if its' container is limited in space, but does not inherently cap its own width and will default to "hug" its contents. - ## Notes -Chip is in the process of being applied to the more opinionated Chips convenience wrapper, -but by design does not carry the same level of "out of the box" functionality as it is a more -"atomic" element that can be used outside of those more complex selection flows. +Chip is in the process of being applied to the more opinionated Chips +convenience wrapper, but by design does not carry the same level of "out of the +box" functionality as it is a more "atomic" element that can be used outside of +those more complex selection flows. diff --git a/docs/components/Chips/Chips.stories.mdx b/docs/components/Chips/Chips.stories.mdx index 74162eb8fc..d74ba6f8ee 100644 --- a/docs/components/Chips/Chips.stories.mdx +++ b/docs/components/Chips/Chips.stories.mdx @@ -83,8 +83,9 @@ In cases where the Chip is on an area whose background is ## Related components -- [Chip](../?path=/docs/components-selections-chip--docs) is the building block that Chips is built on top of, and has more - flexibility to be used in isolation. +- [Chip](../?path=/docs/components-selections-chip--docs) is the building block + that Chips is built on top of, and has more flexibility to be used in + isolation. - [RadioGroup](../?path=/docs/components-forms-and-inputs-radiogroup--docs) should be used to allow the user to select "one-of-many" items (single-select) and the labels for the items are longer than 1 or 2 words. @@ -143,34 +144,3 @@ colors for parity with Jobber's mobile platform, but we will not be implementing this is for the web components as of this first iteration of our web Chip. The mobile use-case is very specific to a filtering use case that we as of now do not anticipate will be required for the web. - -### Chips Composition with Standalone Chip (Web) - -Standalone Chip's `role`, ``, `` `heading` and -`variation` will not work specifically when used within a `Chips`. - -If you wish to use a prefix, please use `Chip` from `Chips/Chip`. - -eg. - -``` -import { Chip, Chips } from "@jobber/components/Chips"; -... - - - - -``` - -Rather than using the standalone implementation. - -``` -import { Chip } from "@jobber/components"; - -... - - - ... - - -``` diff --git a/docs/components/Chips/Web.stories.tsx b/docs/components/Chips/Web.stories.tsx index d2b10e3800..40f8528a6c 100644 --- a/docs/components/Chips/Web.stories.tsx +++ b/docs/components/Chips/Web.stories.tsx @@ -2,7 +2,9 @@ import React, { useState } from "react"; import { ComponentMeta, ComponentStory } from "@storybook/react"; import { Content } from "@jobber/components/Content"; import { Chip, Chips } from "@jobber/components/Chips"; +import { Icon } from "@jobber/components/Icon"; import { Text } from "@jobber/components/Text"; +import { Avatar } from "@jobber/components/Avatar"; import { useFakeOptionQuery } from "./utils/storyUtils"; export default { @@ -30,15 +32,22 @@ const BasicTemplate: ComponentStory = args => { You are {selected ? selected : "_______"} - - - - + + } + label="Amazing" + value="Amazing" + /> + } + label="Wonderful" + value="Wonderful" + /> + } + label="Brilliant" + value="Brilliant" + /> @@ -88,7 +97,6 @@ const SelectionTemplate: ComponentStory = args => { return ( = args => { buttonLabel="Base Dropzone Uploader" getUploadParams={fetchUploadParams} /> + Promise.resolve({ url: "https://httpbin.org/post" }), }; + +export const WithDescription = StatefulTemplate.bind({}); +WithDescription.args = { + allowMultiple: true, + getUploadParams: () => Promise.resolve({ url: "https://httpbin.org/post" }), + description: "JPEG, HEIC, PNG up to 5MB each", +}; diff --git a/docs/components/Page/Web.stories.tsx b/docs/components/Page/Web.stories.tsx index d485d13b30..21273aab4c 100644 --- a/docs/components/Page/Web.stories.tsx +++ b/docs/components/Page/Web.stories.tsx @@ -1,9 +1,10 @@ -import React from "react"; +import React, { useRef, useState } from "react"; import { ComponentMeta, ComponentStory } from "@storybook/react"; import { StatusLabel } from "@jobber/components"; import { Page } from "@jobber/components/Page"; import { Content } from "@jobber/components/Content"; import { Text } from "@jobber/components/Text"; +import { Popover } from "@jobber/components/Popover"; export default { title: "Components/Layouts and Structure/Page/Web", @@ -22,6 +23,52 @@ const BasicTemplate: ComponentStory = args => ( ); +const PopoverTemplate: ComponentStory = args => { + const primaryDivRef = useRef(null); + const [showPrimaryPopover, setShowPrimaryPopover] = useState(false); + + const secondaryDivRef = useRef(null); + const [showSecondaryPopover, setShowSecondaryPopover] = useState(false); + + return ( + <> + setShowPrimaryPopover(true), + ref: primaryDivRef, + }} + secondaryAction={{ + label: "Trigger Drink Popover", + onClick: () => setShowSecondaryPopover(true), + ref: secondaryDivRef, + }} + {...args} + > + + Page content here + + + setShowPrimaryPopover(false)} + preferredPlacement="bottom" + > + Your food order: πŸ₯¨ + + setShowSecondaryPopover(false)} + preferredPlacement="bottom" + > + Your drink order: 🍹 + + + ); +}; + const titleMetaData = ( ); @@ -33,12 +80,10 @@ Basic.args = { "Improve job completion rates, stop chasing payments, and boost your customer service by automatically communicating with your clients at key points before, during, and after a job. Read more about Notifications by visiting our [Help Center](https://help.getjobber.com/hc/en-us).", }; -export const WithActions = BasicTemplate.bind({}); +export const WithActions = PopoverTemplate.bind({}); WithActions.args = { title: "Notification Settings", intro: "This isn't just talk. Get into action with some buttons and menus.", - primaryAction: { label: "Send Food Alert", onClick: () => alert("πŸ₯¨") }, - secondaryAction: { label: "Send Drink Alert", onClick: () => alert("🍹") }, moreActionsMenu: [ { actions: [ diff --git a/docs/design/Colors.stories.mdx b/docs/design/Colors.stories.mdx index 893d2be142..b4089c223a 100644 --- a/docs/design/Colors.stories.mdx +++ b/docs/design/Colors.stories.mdx @@ -1,5 +1,7 @@ import { Meta } from "@storybook/addon-docs"; import { ColorSwatches as Swatch } from "mdxUtils/ColorSwatches"; +import { Disclosure } from "@jobber/components/Disclosure"; +import { Text } from "@jobber/components/Text"; import { ColorSwatches } from "@jobber/docx"; import colors from "@jobber/design/colors"; @@ -203,6 +205,18 @@ state. A slightly darker surface gives a receded appearance relative to main surfaces. +#### Surface--Background--Subtle + + + +Use when you need a surface that's distinct from the main `surface` without +appearing _too_ receded. + #### Surface--Reverse @@ -271,114 +285,119 @@ caution, it’s _bright!_ ## Base colors -This is the underlying color palette that powers our semantic system. - -Direct use of these colors should be avoided, as they do not respond to theming -or systemic changes. - - + + This is the underlying color palette that powers our semantic system. + Direct use of base colors should be avoided, as they do not respond to + theming or systemic changes. + + } +> + + diff --git a/package-lock.json b/package-lock.json index 2d6d7e0dba..5d67b05b61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44549,7 +44549,7 @@ }, "packages/components": { "name": "@jobber/components", - "version": "5.29.0", + "version": "5.33.0", "license": "MIT", "dependencies": { "@jobber/formatters": "^0.3.0", @@ -44628,7 +44628,7 @@ }, "packages/components-native": { "name": "@jobber/components-native", - "version": "0.71.1", + "version": "0.72.1", "license": "MIT", "dependencies": { "@react-native-clipboard/clipboard": "^1.11.2", @@ -45540,7 +45540,7 @@ }, "packages/design": { "name": "@jobber/design", - "version": "0.65.0", + "version": "0.65.1", "license": "MIT", "devDependencies": { "@rollup/plugin-commonjs": "^26.0.1", diff --git a/packages/components-native/CHANGELOG.md b/packages/components-native/CHANGELOG.md index 415f9afd3b..786622d075 100644 --- a/packages/components-native/CHANGELOG.md +++ b/packages/components-native/CHANGELOG.md @@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.72.1](https://github.com/GetJobber/atlantis/compare/@jobber/components-native@0.72.0...@jobber/components-native@0.72.1) (2024-09-17) + + +### Bug Fixes + +* **design:** Update critical color for dark mode ([#2006](https://github.com/GetJobber/atlantis/issues/2006)) ([88254f2](https://github.com/GetJobber/atlantis/commit/88254f22708ca8c7ef60283187e8a9c136210f89)) + + + + + +# [0.72.0](https://github.com/GetJobber/atlantis/compare/@jobber/components-native@0.71.2...@jobber/components-native@0.72.0) (2024-09-12) + + +### Features + +* **components-native:** Add underline support to Text/Typography components for mobile (JOB-104505) ([#2022](https://github.com/GetJobber/atlantis/issues/2022)) ([f831aa1](https://github.com/GetJobber/atlantis/commit/f831aa1db5deb9d355353e814637fe60bbe2072a)) + + + + + +## [0.71.2](https://github.com/GetJobber/atlantis/compare/@jobber/components-native@0.71.1...@jobber/components-native@0.71.2) (2024-09-06) + + +### Bug Fixes + +* **components:** match Glimmer mobile styles to web ([#2004](https://github.com/GetJobber/atlantis/issues/2004)) ([013553d](https://github.com/GetJobber/atlantis/commit/013553dc8151796e3e4d3ec514666274830bc6cd)) + + + + + ## [0.71.1](https://github.com/GetJobber/atlantis/compare/@jobber/components-native@0.71.0...@jobber/components-native@0.71.1) (2024-08-22) diff --git a/packages/components-native/package.json b/packages/components-native/package.json index d9cd9fefac..b1835834df 100644 --- a/packages/components-native/package.json +++ b/packages/components-native/package.json @@ -1,6 +1,6 @@ { "name": "@jobber/components-native", - "version": "0.71.1", + "version": "0.72.1", "license": "MIT", "description": "React Native implementation of Atlantis", "repository": { diff --git a/packages/components-native/src/BottomSheet/components/BottomSheetOption/BottomSheetOption.tsx b/packages/components-native/src/BottomSheet/components/BottomSheetOption/BottomSheetOption.tsx index 963a9bc32b..9da14e8db2 100644 --- a/packages/components-native/src/BottomSheet/components/BottomSheetOption/BottomSheetOption.tsx +++ b/packages/components-native/src/BottomSheet/components/BottomSheetOption/BottomSheetOption.tsx @@ -26,7 +26,7 @@ export function BottomSheetOption({ textTransform = "capitalize", onPress, }: BottomSheetOptionProps): JSX.Element { - const destructiveColor = "critical"; + const destructiveColor = "destructive"; const textVariation = destructive ? destructiveColor : "subdued"; return ( diff --git a/packages/components-native/src/Glimmer/Glimmer.style.ts b/packages/components-native/src/Glimmer/Glimmer.style.ts index 3c43db2b21..e545afa8a4 100644 --- a/packages/components-native/src/Glimmer/Glimmer.style.ts +++ b/packages/components-native/src/Glimmer/Glimmer.style.ts @@ -5,7 +5,7 @@ export const shineWidth = tokens["space-largest"]; export const styles = StyleSheet.create({ container: { - backgroundColor: tokens["color-surface--background"], + backgroundColor: "rgba(0, 0, 0, 0.05)", overflow: "hidden", position: "relative", width: "100%", diff --git a/packages/components-native/src/Glimmer/Glimmer.tsx b/packages/components-native/src/Glimmer/Glimmer.tsx index cdd4a6be33..d896ae4d2a 100644 --- a/packages/components-native/src/Glimmer/Glimmer.tsx +++ b/packages/components-native/src/Glimmer/Glimmer.tsx @@ -85,12 +85,18 @@ export function Glimmer({ + - diff --git a/packages/components-native/src/Menu/components/MenuOption/MenuOption.tsx b/packages/components-native/src/Menu/components/MenuOption/MenuOption.tsx index 99208855af..9c27c8d2c9 100644 --- a/packages/components-native/src/Menu/components/MenuOption/MenuOption.tsx +++ b/packages/components-native/src/Menu/components/MenuOption/MenuOption.tsx @@ -18,7 +18,7 @@ export function MenuOption({ onPress, setOpen, }: MenuOptionInternalProps): JSX.Element { - const destructiveColor = "critical"; + const destructiveColor = "destructive"; const textVariation = destructive ? destructiveColor : "heading"; return ( diff --git a/packages/components-native/src/Text/Text.test.tsx b/packages/components-native/src/Text/Text.test.tsx index e28412eeb6..dbf07df132 100644 --- a/packages/components-native/src/Text/Text.test.tsx +++ b/packages/components-native/src/Text/Text.test.tsx @@ -142,3 +142,9 @@ it("renders text that is inaccessible", () => { }), ); }); + +it("renders text with underline styling", () => { + const text = render(Test Text); + + expect(text.toJSON()).toMatchSnapshot(); +}); diff --git a/packages/components-native/src/Text/Text.tsx b/packages/components-native/src/Text/Text.tsx index a2b6348977..dccca3ab57 100644 --- a/packages/components-native/src/Text/Text.tsx +++ b/packages/components-native/src/Text/Text.tsx @@ -70,6 +70,13 @@ interface TextProps */ readonly italic?: boolean; + /** + * Underline style to use for the text. The non-solid style is only supported + * on iOS, as per React Native's Text component's limitations. + * https://reactnative.dev/docs/text-style-props#textdecorationstyle-ios + */ + readonly underline?: "solid" | "dotted"; + /** * This will make the text inaccessible to the screen reader. * This should be avoided unless there is a good reason. @@ -118,6 +125,7 @@ export function Text({ italic = false, hideFromScreenReader = false, maxFontScaleSize, + underline, selectable, }: TextProps): JSX.Element { const accessibilityRole: TextAccessibilityRole = "text"; @@ -130,6 +138,7 @@ export function Text({ fontWeight={getFontWeight({ level, emphasis })} maxFontScaleSize={maxFontScaleSize || TEXT_MAX_SCALED_FONT_SIZES[level]} selectable={selectable} + underline={underline} {...{ ...levelStyles[level], allowFontScaling, diff --git a/packages/components-native/src/Text/__snapshots__/Text.test.tsx.snap b/packages/components-native/src/Text/__snapshots__/Text.test.tsx.snap index c4646ba9a4..9895c31d40 100644 --- a/packages/components-native/src/Text/__snapshots__/Text.test.tsx.snap +++ b/packages/components-native/src/Text/__snapshots__/Text.test.tsx.snap @@ -546,6 +546,47 @@ exports[`renders text with success variation reverseTheme true 1`] = ` `; +exports[`renders text with underline styling 1`] = ` + + Test Text + +`; + exports[`renders text with warn variation 1`] = ` { }), ); }); + +describe("underline", () => { + it.each(["solid", "double", "dotted", "dashed"] as const)( + "renders text with %s underline", + underlineStyle => { + const typography = render( + Test Text, + ); + + expect(typography.toJSON()).toMatchSnapshot(); + }, + ); +}); diff --git a/packages/components-native/src/Typography/Typography.tsx b/packages/components-native/src/Typography/Typography.tsx index 5e6fb7dcda..a5781611a1 100644 --- a/packages/components-native/src/Typography/Typography.tsx +++ b/packages/components-native/src/Typography/Typography.tsx @@ -7,6 +7,7 @@ import { // eslint-disable-next-line no-restricted-imports Text, TextProps, + TextStyle, ViewStyle, } from "react-native"; import { TypographyGestureDetector } from "./TypographyGestureDetector"; @@ -83,6 +84,13 @@ export interface TypographyProps */ readonly fontStyle?: T extends "base" ? BaseStyle : DisplayStyle; + /** + * Underline style to use for the text. The non-solid style is only supported + * on iOS, as per React Native's Text component's limitations. + * https://reactnative.dev/docs/text-style-props#textdecorationstyle-ios + */ + readonly underline?: "solid" | "double" | "dotted" | "dashed" | undefined; + /** * Font weight */ @@ -124,6 +132,7 @@ const maxNumberOfLines = { export const Typography = React.memo(InternalTypography); +// eslint-disable-next-line max-statements function InternalTypography({ fontFamily, fontStyle, @@ -143,6 +152,7 @@ function InternalTypography({ hideFromScreenReader = false, accessibilityRole = "text", strikeThrough = false, + underline, selectable = true, }: TypographyProps): JSX.Element { const sizeAndHeight = getSizeAndHeightStyle(size, lineHeight); @@ -162,6 +172,11 @@ function InternalTypography({ style.push(styles.italic); } + if (underline) { + const underlineTextStyle: TextStyle = { textDecorationStyle: underline }; + style.push(underlineTextStyle, styles.underline); + } + const numberOfLinesForNativeText = maxNumberOfLines[maxLines]; const text = getTransformedText(children, transform); @@ -326,6 +341,7 @@ export type TextColor = | "textReverse" | "textReverseSecondary" | "interactive" + | "interactiveSubtle" | "destructive" | "learning" | "subtle" @@ -334,12 +350,14 @@ export type TextColor = export type TextVariation = | "success" | "interactive" + | "interactiveSubtle" | "error" | "base" | "subdued" | "warn" | "info" | "disabled" + | "destructive" | "critical"; export type TextTransform = "uppercase" | "lowercase" | "capitalize" | "none"; diff --git a/packages/components-native/src/Typography/__snapshots__/Typography.test.tsx.snap b/packages/components-native/src/Typography/__snapshots__/Typography.test.tsx.snap index 0ceeef4711..4c3992fa2d 100644 --- a/packages/components-native/src/Typography/__snapshots__/Typography.test.tsx.snap +++ b/packages/components-native/src/Typography/__snapshots__/Typography.test.tsx.snap @@ -868,3 +868,163 @@ exports[`renders text with white color 1`] = ` Test Text `; + +exports[`underline renders text with dashed underline 1`] = ` + + Test Text + +`; + +exports[`underline renders text with dotted underline 1`] = ` + + Test Text + +`; + +exports[`underline renders text with double underline 1`] = ` + + Test Text + +`; + +exports[`underline renders text with solid underline 1`] = ` + + Test Text + +`; diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 171ca546cd..805642fe7c 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -3,6 +3,103 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.33.0](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.32.0...@jobber/components@5.33.0) (2024-09-18) + + +### Features + +* **components:** update Chip.Prefix to accept more than Icon and Avatar ([#2020](https://github.com/GetJobber/atlantis/issues/2020)) ([1a6c72c](https://github.com/GetJobber/atlantis/commit/1a6c72ca9eedcefc77d251da360fc426fbd35858)) + + + + + +# [5.32.0](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.31.1...@jobber/components@5.32.0) (2024-09-17) + + +### Features + +* **components:** small improvement to focus state of DataList headers ([#2014](https://github.com/GetJobber/atlantis/issues/2014)) ([fb4885d](https://github.com/GetJobber/atlantis/commit/fb4885d7b38f32a769ad0781b3b462824374c185)) + + + + + +## [5.31.1](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.31.0...@jobber/components@5.31.1) (2024-09-17) + + +### Bug Fixes + +* **design:** Update critical color for dark mode ([#2006](https://github.com/GetJobber/atlantis/issues/2006)) ([88254f2](https://github.com/GetJobber/atlantis/commit/88254f22708ca8c7ef60283187e8a9c136210f89)) + + + + + +# [5.31.0](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.30.1...@jobber/components@5.31.0) (2024-09-16) + + +### Features + +* **components:** add elevation to DataList item actions ([#2000](https://github.com/GetJobber/atlantis/issues/2000)) ([e969f96](https://github.com/GetJobber/atlantis/commit/e969f96578bef3260bc3962a9c12c9b9f2ea5630)) +* **components:** Add underline support to Typography component for web (JOB-104497) ([#2021](https://github.com/GetJobber/atlantis/issues/2021)) ([4a30070](https://github.com/GetJobber/atlantis/commit/4a30070734f2faa05a57615d5296855eac11aa25)) + + + + + +## [5.30.1](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.30.0...@jobber/components@5.30.1) (2024-09-13) + + +### Bug Fixes + +* **components:** Display Datalist checkbox based on device type instead of breakpoint ([#2013](https://github.com/GetJobber/atlantis/issues/2013)) ([bb59990](https://github.com/GetJobber/atlantis/commit/bb599904cab94591fad13b64569436a8f70728d3)) + + + + + +# [5.30.0](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.29.3...@jobber/components@5.30.0) (2024-09-13) + + +### Features + +* **components:** Add description prop to InputFile ([#2019](https://github.com/GetJobber/atlantis/issues/2019)) ([ba5b262](https://github.com/GetJobber/atlantis/commit/ba5b262c123bca26226f86e14931d3a11398b3e1)) + + + + + +## [5.29.3](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.29.2...@jobber/components@5.29.3) (2024-09-13) + +**Note:** Version bump only for package @jobber/components + + + + + +## [5.29.2](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.29.1...@jobber/components@5.29.2) (2024-09-12) + + +### Reverts + +* "feat(components): Separate Chip from Chips Again" ([#2028](https://github.com/GetJobber/atlantis/issues/2028)) ([7f6c94f](https://github.com/GetJobber/atlantis/commit/7f6c94fce86702fc4f464884ca019823d9b4491b)), closes [#1995](https://github.com/GetJobber/atlantis/issues/1995) + + + + + +## [5.29.1](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.29.0...@jobber/components@5.29.1) (2024-09-04) + + +### Bug Fixes + +* **components:** Add ref prop to primary and secondary actions on Page component ([#2010](https://github.com/GetJobber/atlantis/issues/2010)) ([bdac7de](https://github.com/GetJobber/atlantis/commit/bdac7de3bf349ea924edfbfe968abccad4498465)) + + + + + # [5.29.0](https://github.com/GetJobber/atlantis/compare/@jobber/components@5.28.1...@jobber/components@5.29.0) (2024-08-29) diff --git a/packages/components/package.json b/packages/components/package.json index 1ef44cd948..38ab2d9402 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@jobber/components", - "version": "5.29.0", + "version": "5.33.0", "license": "MIT", "type": "module", "main": "dist/index.cjs", diff --git a/packages/components/src/Chip/Chip.css b/packages/components/src/Chip/Chip.css index b6eecc5347..fcb088d3e4 100644 --- a/packages/components/src/Chip/Chip.css +++ b/packages/components/src/Chip/Chip.css @@ -5,12 +5,11 @@ --chip-radius: 20px; --chip-base-bg: var(--color-surface--background); --chip-base--hover: var(--color-surface--background--hover); - display: inline-flex; + display: flex; position: relative; height: var(--chip-height); min-width: 0; - box-sizing: border-box; - padding: 0 var(--base-unit); + padding: var(--space-small) var(--base-unit); border: var(--border-base) solid transparent; border-radius: var(--chip-radius); color: var(--color-heading); @@ -89,7 +88,8 @@ --chip--gradient-color-variation: var(--chip-base-bg); } -.base:hover { +.base:hover, +.base:focus-visible { background-color: var(--chip-base--hover); --chip--gradient-color-variation--hover: var(--chip-base--hover); } diff --git a/packages/components/src/Chip/Chip.test.tsx b/packages/components/src/Chip/Chip.test.tsx index e5e7d6564a..8b05b28bdf 100644 --- a/packages/components/src/Chip/Chip.test.tsx +++ b/packages/components/src/Chip/Chip.test.tsx @@ -49,14 +49,13 @@ describe("Chip", () => { it("should have a role of button when role not provided", () => { const { getByRole } = render(); - expect(getByRole("button")).toBeInstanceOf(HTMLDivElement); + expect(getByRole("button")).toBeInstanceOf(HTMLButtonElement); }); it("should have a tabIndex of zero if one is not provided", () => { const { getByRole } = render(); expect(getByRole("button")).toHaveAttribute("tabIndex", "0"); - expect(getByRole("button")).toBeInstanceOf(HTMLDivElement); }); it("should set aria-label if one is provided", () => { @@ -66,6 +65,12 @@ describe("Chip", () => { expect(getByRole("button")).toHaveAttribute("aria-label", "Test Label"); }); + + it("should default aria-label to provided label if not provided", () => { + const { getByRole } = render(); + + expect(getByRole("button")).toHaveAttribute("aria-label", "Test Chip"); + }); }); describe("Chip Children", () => { diff --git a/packages/components/src/Chip/Chip.tsx b/packages/components/src/Chip/Chip.tsx index 05630d6f92..6e55418010 100644 --- a/packages/components/src/Chip/Chip.tsx +++ b/packages/components/src/Chip/Chip.tsx @@ -16,7 +16,6 @@ export const Chip = ({ invalid, label, value, - testID, onClick, onKeyDown, children, @@ -42,21 +41,19 @@ export const Chip = ({ label, heading, ); - const Tag = onClick ? "button" : "div"; return ( - - + ); }; diff --git a/packages/components/src/Chip/Chip.types.d.ts b/packages/components/src/Chip/Chip.types.d.ts index ac36469bc3..f6d0d66b00 100644 --- a/packages/components/src/Chip/Chip.types.d.ts +++ b/packages/components/src/Chip/Chip.types.d.ts @@ -6,11 +6,6 @@ export interface ChipProps extends PropsWithChildren { */ readonly ariaLabel?: string; - /** - * The testing id for the chip if necessary. - */ - testID?: string; - /** * Disables both mouse and keyboard functionality, and updates the visual style of the Chip to appear disabled. */ @@ -58,15 +53,13 @@ export interface ChipProps extends PropsWithChildren { */ readonly onClick?: ( value: string | number | undefined, - ev: React.MouseEvent, + ev: React.MouseEvent, ) => void; /** * Callback. Called when you keydown on Chip. Ships the event, so you can get the key pushed. */ - readonly onKeyDown?: ( - ev: React.KeyboardEvent, - ) => void; + readonly onKeyDown?: (ev: React.KeyboardEvent) => void; } export type ChipVariations = "subtle" | "base"; diff --git a/packages/components/src/Chip/components/ChipPrefix/Chip.Prefix.test.tsx b/packages/components/src/Chip/components/ChipPrefix/Chip.Prefix.test.tsx index 0c8e0aca0e..3ab6180479 100644 --- a/packages/components/src/Chip/components/ChipPrefix/Chip.Prefix.test.tsx +++ b/packages/components/src/Chip/components/ChipPrefix/Chip.Prefix.test.tsx @@ -1,32 +1,38 @@ import React from "react"; -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import { Chip } from "../../Chip"; import { Avatar } from "../../../Avatar"; import { Icon } from "../../../Icon"; describe("Chip.Prefix Component", () => { it("renders Prefix", () => { - const { getByText } = render( + render( , ); - expect(getByText("DT")).toBeInTheDocument(); + expect(screen.getByText("DT")).toBeInTheDocument(); }); - it("should hide prefix when passed bad child", () => { - const { queryByText } = render(Hello!); - expect(queryByText("Hello!")).not.toBeInTheDocument(); + it("should show all children when icon or avatar isn't provided", () => { + render( + +

First child

+

Second child

+
, + ); + expect(screen.getByText("First child")).toBeInTheDocument(); + expect(screen.getByText("Second child")).toBeInTheDocument(); }); it("prefix renders only one valid child", () => { - const { container } = render( + render( , ); - expect(container.querySelectorAll("svg")).toHaveLength(1); + expect(screen.getAllByTestId("cross")).toHaveLength(1); }); }); diff --git a/packages/components/src/Chip/components/ChipPrefix/Chip.Prefix.tsx b/packages/components/src/Chip/components/ChipPrefix/Chip.Prefix.tsx index 964152bf52..b78e2c99fb 100644 --- a/packages/components/src/Chip/components/ChipPrefix/Chip.Prefix.tsx +++ b/packages/components/src/Chip/components/ChipPrefix/Chip.Prefix.tsx @@ -6,14 +6,12 @@ import { useChildComponent } from "../../hooks/index"; import styles from "../../Chip.css"; export function ChipPrefix({ children }: PropsWithChildren) { - const singleChild = useChildComponent( + const avatarOrIcon = useChildComponent( children, d => d.type === Avatar || d.type === Icon, ); - return ( - - {singleChild} - - ); + if (!avatarOrIcon) return <>{children}; + + return {avatarOrIcon}; } diff --git a/packages/components/src/Chip/components/ChipSuffix/Chip.Suffix.tsx b/packages/components/src/Chip/components/ChipSuffix/Chip.Suffix.tsx index 0fd99587de..30888c00db 100644 --- a/packages/components/src/Chip/components/ChipSuffix/Chip.Suffix.tsx +++ b/packages/components/src/Chip/components/ChipSuffix/Chip.Suffix.tsx @@ -1,7 +1,6 @@ import React, { PropsWithChildren } from "react"; import classNames from "classnames"; import { Icon } from "@jobber/components/Icon"; -import { InternalChipButton } from "@jobber/components/Chips/InternalChipButton"; import { useChildComponent } from "../../hooks"; import styles from "../../Chip.css"; @@ -12,10 +11,7 @@ export function ChipSuffix({ testID, ariaLabel, }: ChipSuffixProps) { - let singleChild = useChildComponent( - children, - d => d.type === Icon || d.type == InternalChipButton, - ); + let singleChild = useChildComponent(children, d => d.type === Icon); const handleClick = ( event: @@ -30,9 +26,7 @@ export function ChipSuffix({ onClick(event); }; - const iconName = singleChild?.props?.name || singleChild?.props?.icon; - - if (!allowedSuffixIcons.includes(iconName)) { + if (!allowedSuffixIcons.includes(singleChild?.props?.name)) { singleChild = undefined; } @@ -66,10 +60,4 @@ export interface ChipSuffixProps extends PropsWithChildren { ) => void; } -export const allowedSuffixIcons = [ - "cross", - "add", - "checkmark", - "remove", - "arrowDown", -]; +export const allowedSuffixIcons = ["cross", "add", "checkmark", "arrowDown"]; diff --git a/packages/components/src/Chips/ChipsTypes.tsx b/packages/components/src/Chips/ChipsTypes.tsx index 4ee7254d1c..69cabc3fb0 100644 --- a/packages/components/src/Chips/ChipsTypes.tsx +++ b/packages/components/src/Chips/ChipsTypes.tsx @@ -1,4 +1,5 @@ import { MouseEvent, ReactElement } from "react"; +import { XOR } from "ts-xor"; import { ChipProps } from "./Chip"; interface ChipFoundationProps { @@ -95,6 +96,7 @@ export interface ChipDismissibleProps extends ChipFoundationProps { onLoadMore?(searchValue: string): void; } -export type ChipsProps = - | ChipSingleSelectProps - | (ChipMultiSelectProps | ChipDismissibleProps); +export type ChipsProps = XOR< + ChipSingleSelectProps, + XOR +>; diff --git a/packages/components/src/Chips/InternalChip.css b/packages/components/src/Chips/InternalChip.css index 1da040a5f0..fae9f28489 100644 --- a/packages/components/src/Chips/InternalChip.css +++ b/packages/components/src/Chips/InternalChip.css @@ -59,8 +59,7 @@ */ .chip:focus, -.input:focus-visible ~ div[role="option"], -.input:focus-visible ~ div[role="button"] { +.input:focus ~ .chip { box-shadow: var(--shadow-focus); outline: none; } diff --git a/packages/components/src/Chips/InternalChip.tsx b/packages/components/src/Chips/InternalChip.tsx index 3cd947ebe5..d4e93bc20f 100644 --- a/packages/components/src/Chips/InternalChip.tsx +++ b/packages/components/src/Chips/InternalChip.tsx @@ -1,9 +1,14 @@ -import React from "react"; +import React, { useState } from "react"; +import classnames from "classnames"; +import styles from "./InternalChip.css"; import { InternalChipProps } from "./ChipTypes"; -import { Chip } from "../Chip"; +import { InternalChipAffix } from "./InternalChipAffix"; +import { Typography } from "../Typography"; +import { Tooltip } from "../Tooltip"; export function InternalChip({ label, + active = false, disabled = false, invalid = false, prefix, @@ -13,20 +18,43 @@ export function InternalChip({ onClick, onKeyDown, }: InternalChipProps) { - return ( - (); + const isClickable = onClick && !disabled; + const classNames = classnames(styles.chip, { + [styles.clickable]: isClickable, + [styles.active]: active, + [styles.disabled]: disabled, + [styles.invalid]: invalid, + [styles.hasPrefix]: prefix, + [styles.hasSuffix]: suffix, + }); + const affixProps = { active, invalid, disabled }; + const Tag = onClick ? "button" : "div"; + + const chip = ( + onClick(ev) : undefined} - label={label} > - {prefix && {prefix}} - {suffix && {suffix}} - + + + {label} + + + ); + + return isTruncated() ? {chip} : chip; + + function isTruncated() { + const truncateParentHeight = truncateRef?.parentElement?.offsetHeight || 0; + const truncateChildHeight = truncateRef?.offsetHeight || 0; + + return truncateChildHeight >= truncateParentHeight * 2; + } } diff --git a/packages/components/src/Chips/InternalChipButton.tsx b/packages/components/src/Chips/InternalChipButton.tsx index 7339a1f5bb..ec14caeb37 100644 --- a/packages/components/src/Chips/InternalChipButton.tsx +++ b/packages/components/src/Chips/InternalChipButton.tsx @@ -39,7 +39,7 @@ export function InternalChipButton({ aria-disabled={disabled} data-testid="remove-chip-button" > - + ); @@ -64,7 +64,7 @@ export function InternalChipButton({ if (disabled) return "disabled"; if (invalid) return "critical"; - return "interactiveSubtle"; + return "greyBlue"; } function handleClick(event: MouseEvent) { diff --git a/packages/components/src/Chips/InternalChipDismissible/__tests__/InternalChipDismissible.test.tsx b/packages/components/src/Chips/InternalChipDismissible/__tests__/InternalChipDismissible.test.tsx index c5c4e63b79..ebb4e0355a 100644 --- a/packages/components/src/Chips/InternalChipDismissible/__tests__/InternalChipDismissible.test.tsx +++ b/packages/components/src/Chips/InternalChipDismissible/__tests__/InternalChipDismissible.test.tsx @@ -162,7 +162,7 @@ describe("Basic interaction", () => { describe("left and right arrow keys via keyboard", () => { it("should focus on the correct element when left or right arrow down", async () => { - const chipWrappers = screen.getAllByTestId("ATL-InternalChip"); + const chipWrappers = screen.getAllByTestId("chip-wrapper"); const first = chipWrappers[0]; await userEvent.tab(); diff --git a/packages/components/src/Chips/InternalChipMultiSelect.tsx b/packages/components/src/Chips/InternalChipMultiSelect.tsx index b6c9691661..4a73441546 100644 --- a/packages/components/src/Chips/InternalChipMultiSelect.tsx +++ b/packages/components/src/Chips/InternalChipMultiSelect.tsx @@ -33,18 +33,7 @@ export function InternalChipMultiSelect({ - ), - } - : {})} - tabIndex={-1} + suffix={checkmarkIcon(isChipActive)} /> ); @@ -80,4 +69,15 @@ export function InternalChipMultiSelect({ const newVal = values.filter(val => val !== value); onChange(newVal); } + + function checkmarkIcon(show: boolean) { + // Ideally, we should be returning a fragment `<>` when a function + // returns a component / element. However, this one needs to return nothing + // to prevent it from randomly rendering a suffix. + // + // DO NOT COPY! + if (!show) return; + + return ; + } } diff --git a/packages/components/src/Chips/InternalChipSingleSelect.tsx b/packages/components/src/Chips/InternalChipSingleSelect.tsx index b38fd66b21..2d29667748 100644 --- a/packages/components/src/Chips/InternalChipSingleSelect.tsx +++ b/packages/components/src/Chips/InternalChipSingleSelect.tsx @@ -2,7 +2,6 @@ import React, { KeyboardEvent, MouseEvent, useId } from "react"; import styles from "./InternalChip.css"; import { InternalChip } from "./InternalChip"; import { ChipSingleSelectProps } from "./ChipsTypes"; -import { Icon } from "../Icon"; type InternalChipChoiceProps = Pick< ChipSingleSelectProps, @@ -35,22 +34,7 @@ export function InternalChipSingleSelect({ }} disabled={child.props.disabled} /> - - ), - } - : {})} - active={isSelected} - tabIndex={-1} - /> + ); })} diff --git a/packages/components/src/Chips/tests/InternalChip.test.tsx b/packages/components/src/Chips/tests/InternalChip.test.tsx index 8087c587e0..0db9dc67cf 100644 --- a/packages/components/src/Chips/tests/InternalChip.test.tsx +++ b/packages/components/src/Chips/tests/InternalChip.test.tsx @@ -6,27 +6,49 @@ import { Icon } from "../../Icon"; it("should render a div chip when onClick is not present", () => { render(); - expect(screen.getByTestId("ATL-InternalChip")).toBeInstanceOf(HTMLDivElement); + expect(screen.getByTestId("chip-wrapper")).toBeInstanceOf(HTMLDivElement); }); it("should render a button chip when onClick is not present", () => { render(); - expect(screen.getByTestId("ATL-InternalChip")).toBeInstanceOf( - HTMLButtonElement, - ); + expect(screen.getByTestId("chip-wrapper")).toBeInstanceOf(HTMLButtonElement); }); it("should fire the callback when it's clicked", async () => { const handleClick = jest.fn(); render(); - await userEvent.click(screen.getByTestId("ATL-InternalChip")); + await userEvent.click(screen.getByTestId("chip-wrapper")); expect(handleClick).toHaveBeenCalledTimes(1); }); describe("Chip icon colors depending on state", () => { - it("should be green when it's active", () => { + it("should be red when it's invalid", () => { + expect(mockChip({ invalid: true })).toHaveStyle({ + fill: "var(--color-critical)", + }); + }); + + it("should be red when it's invalid and active", () => { + expect(mockChip({ invalid: true, active: true })).toHaveStyle({ + fill: "var(--color-critical)", + }); + }); + + it("should be grey when it's disabled", () => { + expect(mockChip({ disabled: true })).toHaveStyle({ + fill: "var(--color-disabled)", + }); + }); + + it("should be grey when it's disabled and invalid", () => { + expect(mockChip({ disabled: true, invalid: true })).toHaveStyle({ + fill: "var(--color-disabled)", + }); + }); + + it("should be white when it's active", () => { expect(mockChip({ active: true })).toHaveStyle({ - fill: "var(--color-success)", + fill: "var(--color-white)", }); }); @@ -41,16 +63,37 @@ describe("Chip icon colors depending on state", () => { disabled = false, active = false, }: MockChipProps) { - const { getByTestId } = render( + render( } + prefix={} label="Yo!" />, ); - return getByTestId("checkmark").querySelector("path"); + return screen.getByTestId("checkbox").querySelector("path"); } }); + +// TODO: Figure out why this is always passing + +describe.skip("When the chip is disabled and invalid", () => { + it("should still look disabled but have a border of red", () => { + render(); + expect(screen.getByTestId("chip-wrapper")).toHaveStyle({ + borderColor: "var(--color-critical)", + backgroundColor: "var(--color-disabled--secondary)", + }); + }); +}); + +describe.skip("When the chip is disabled and active", () => { + it("should be a darker chip but still grey", async () => { + render(); + expect(screen.getByTestId("chip-wrapper")).toHaveStyle(` + background-color: var(--color-disabled); + `); + }); +}); diff --git a/packages/components/src/DataList/DataList.css b/packages/components/src/DataList/DataList.css index 2ec4d16498..3f074875d1 100644 --- a/packages/components/src/DataList/DataList.css +++ b/packages/components/src/DataList/DataList.css @@ -175,7 +175,7 @@ transition: opacity var(--transition-properties); --transition-properties: var(--timing-quick) ease-in-out; - @media (--medium-screens-and-up) { + @media (any-pointer: fine) { padding-top: 0; opacity: 0; } diff --git a/packages/components/src/DataList/components/DataListAction/DataListAction.test.tsx b/packages/components/src/DataList/components/DataListAction/DataListAction.test.tsx index dc5aac0bbe..238bf2d3bf 100644 --- a/packages/components/src/DataList/components/DataListAction/DataListAction.test.tsx +++ b/packages/components/src/DataList/components/DataListAction/DataListAction.test.tsx @@ -38,7 +38,7 @@ describe("DataListAction", () => { mockDestructiveValue.mockReturnValue(true); const { button } = renderComponent(); - expect(button.querySelector("p")).toHaveClass("critical"); + expect(button.querySelector("p")).toHaveClass("destructive"); }); it("should not fire the onClick when the item from the context is undefined", async () => { diff --git a/packages/components/src/DataList/components/DataListAction/DataListAction.tsx b/packages/components/src/DataList/components/DataListAction/DataListAction.tsx index 9441234b20..65fe0f1c42 100644 --- a/packages/components/src/DataList/components/DataListAction/DataListAction.tsx +++ b/packages/components/src/DataList/components/DataListAction/DataListAction.tsx @@ -23,7 +23,7 @@ export function DataListAction({ return null; } - const color = destructive ? "critical" : "heading"; + const color = destructive ? "destructive" : "heading"; function getActionLabel() { if (typeof label === "string") { diff --git a/packages/components/src/DataList/components/DataListHeaderTile/DataListHeaderTile.css b/packages/components/src/DataList/components/DataListHeaderTile/DataListHeaderTile.css index 2324336ea7..a8f8b07cff 100644 --- a/packages/components/src/DataList/components/DataListHeaderTile/DataListHeaderTile.css +++ b/packages/components/src/DataList/components/DataListHeaderTile/DataListHeaderTile.css @@ -2,14 +2,17 @@ display: flex; position: relative; align-items: center; - padding: 0; + margin: 0 calc(-1 * var(--space-smallest)); + padding: 0 var(--space-smallest); border: none; + border-radius: var(--radius-small); background-color: transparent; + transition: all var(--timing-base) ease-out; } .headerLabel:focus, .headerLabel:focus-visible { - outline: none; + outline: transparent; } .headerLabel:focus-visible { diff --git a/packages/components/src/DataList/components/DataListHeaderTile/DataListSortingArrows.css b/packages/components/src/DataList/components/DataListHeaderTile/DataListSortingArrows.css index 1897a20abf..8657d7662b 100644 --- a/packages/components/src/DataList/components/DataListHeaderTile/DataListSortingArrows.css +++ b/packages/components/src/DataList/components/DataListHeaderTile/DataListSortingArrows.css @@ -1,7 +1,6 @@ .sortIcon { - width: var(--space-large); - height: var(--space-large); - margin-right: var(--space-small); + width: 18px; + height: 24px; } .sortIcon path { diff --git a/packages/components/src/DataList/components/DataListItemActions/DataListItemActions.tsx b/packages/components/src/DataList/components/DataListItemActions/DataListItemActions.tsx index 549deca484..d9361e3400 100644 --- a/packages/components/src/DataList/components/DataListItemActions/DataListItemActions.tsx +++ b/packages/components/src/DataList/components/DataListItemActions/DataListItemActions.tsx @@ -43,6 +43,7 @@ export function InternalDataListItemActions({ delay: TRANSITION_DELAY_IN_SECONDS, }} className={styles.menu} + data-elevation={"elevated"} onContextMenu={handleContextMenu} > {actions} diff --git a/packages/components/src/InputFile/InputFile.tsx b/packages/components/src/InputFile/InputFile.tsx index eb33350881..f9fa7b4833 100644 --- a/packages/components/src/InputFile/InputFile.tsx +++ b/packages/components/src/InputFile/InputFile.tsx @@ -123,6 +123,11 @@ interface InputFileProps { */ readonly allowMultiple?: boolean; + /** + * Further description of the input. + */ + readonly description?: string; + /** * A callback that receives a file object and returns a `UploadParams` needed * to upload the file. @@ -181,6 +186,7 @@ export function InputFile({ buttonLabel: providedButtonLabel, allowMultiple = false, allowedTypes = "all", + description, getUploadParams, onUploadStart, onUploadProgress, @@ -239,9 +245,14 @@ export function InputFile({

- or drag a file here to upload + Select or drag a file here to upload

@@ -72,9 +72,9 @@ exports[`Post Requests renders an InputFile with multiple images allowed 1`] = `

- or drag images here to upload + Select or drag images here to upload

@@ -113,9 +113,9 @@ exports[`Post Requests renders an InputFile with multiple uploads 1`] = `

- or drag files here to upload + Select or drag files here to upload

@@ -154,9 +154,9 @@ exports[`Post Requests renders an InputFile with only images allowed 1`] = `

- or drag an image here to upload + Select or drag an image here to upload

@@ -194,9 +194,9 @@ exports[`Post Requests renders an InputFile with proper variations 1`] = `

- or drag a file here to upload + Select or drag a file here to upload

diff --git a/packages/components/src/Page/Page.css b/packages/components/src/Page/Page.css index d5ef582bc2..bc98fcd875 100644 --- a/packages/components/src/Page/Page.css +++ b/packages/components/src/Page/Page.css @@ -39,6 +39,7 @@ .titleBar > .actionGroup { flex: 1 0 auto; + align-self: baseline; } .titleBar.large, diff --git a/packages/components/src/Page/Page.test.tsx b/packages/components/src/Page/Page.test.tsx index a1eef46281..cdad1bd485 100644 --- a/packages/components/src/Page/Page.test.tsx +++ b/packages/components/src/Page/Page.test.tsx @@ -2,6 +2,7 @@ import React from "react"; import { fireEvent, render } from "@testing-library/react"; import { StatusLabel } from "@jobber/components/StatusLabel"; import { Page } from "."; +import { getActionProps } from "./Page"; import { SectionProps } from "../Menu"; jest.mock("@jobber/hooks", () => { @@ -106,6 +107,23 @@ describe("When actions are provided", () => { fireEvent.click(getByText("Second Do")); expect(handleSecondaryAction).toHaveBeenCalledTimes(1); }); + + describe("getActionProps", () => { + it("given action props, it returns the correct props", () => { + const buttonActionProps = { + label: "Label", + }; + const buttonActionWithRefProps = { + label: "Label", + ref: { current: null }, + }; + + expect(getActionProps(buttonActionProps)).toEqual(buttonActionProps); + expect(getActionProps(buttonActionWithRefProps)).toEqual( + buttonActionProps, + ); + }); + }); }); describe("When moreActions are provided", () => { diff --git a/packages/components/src/Page/Page.tsx b/packages/components/src/Page/Page.tsx index 4e256db54d..5a6ba28a67 100644 --- a/packages/components/src/Page/Page.tsx +++ b/packages/components/src/Page/Page.tsx @@ -14,6 +14,10 @@ import { Button, ButtonProps } from "../Button"; import { Menu, SectionProps } from "../Menu"; import { Emphasis } from "../Emphasis"; +export type ButtonActionProps = ButtonProps & { + ref?: React.RefObject; +}; + interface PageFoundationProps { readonly children: ReactNode | ReactNode[]; @@ -49,13 +53,13 @@ interface PageFoundationProps { /** * Page title primary action button settings. */ - readonly primaryAction?: ButtonProps; + readonly primaryAction?: ButtonActionProps; /** * Page title secondary action button settings. * Only shown if there is a primaryAction. */ - readonly secondaryAction?: ButtonProps; + readonly secondaryAction?: ButtonActionProps; /** * Page title Action menu. @@ -154,13 +158,16 @@ export function Page({ {showActionGroup && (
{primaryAction && ( -
-
)} {secondaryAction && ( -
-
)} {showMenu && ( @@ -186,3 +193,10 @@ export function Page({
); } + +export const getActionProps = (actionProps: ButtonActionProps): ButtonProps => { + const buttonProps = { ...actionProps }; + if (actionProps.ref) delete buttonProps.ref; + + return buttonProps; +}; diff --git a/packages/components/src/Tooltip/Tooltip.css b/packages/components/src/Tooltip/Tooltip.css index 3406d0025f..45e98a2184 100644 --- a/packages/components/src/Tooltip/Tooltip.css +++ b/packages/components/src/Tooltip/Tooltip.css @@ -17,7 +17,7 @@ display: inline-block; position: relative; max-width: 250px; - padding: calc(var(--space-base) - var(--space-smallest)) var(--space-base); + padding: var(--space-small) calc(var(--space-small) + var(--space-smaller)); border-radius: var(--radius-base); background-color: var(--tooltip--surface); } @@ -88,7 +88,7 @@ .tooltipMessage { margin: 0; color: var(--color-text--reverse); - font-size: var(--typography--fontSize-base); - font-weight: 600; + font-size: var(--typography--fontSize-small); + font-weight: 500; line-height: var(--typography--lineHeight-base); } diff --git a/packages/components/src/Tooltip/Tooltip.tsx b/packages/components/src/Tooltip/Tooltip.tsx index e4c6e1e6d9..83840fe34e 100644 --- a/packages/components/src/Tooltip/Tooltip.tsx +++ b/packages/components/src/Tooltip/Tooltip.tsx @@ -9,8 +9,8 @@ import { useTooltipPositioning } from "./useTooltipPositioning"; import { Placement } from "./Tooltip.types"; const variation = { - startOrStop: { scale: 0.6, opacity: 0 }, - done: { scale: 1, opacity: 1 }, + startOrStop: { opacity: 0 }, + done: { opacity: 1 }, }; interface TooltipProps { @@ -24,15 +24,12 @@ interface TooltipProps { * @default 'top' */ readonly preferredPlacement?: Placement; - - readonly setTabIndex?: boolean; } export function Tooltip({ message, children, preferredPlacement = "top", - setTabIndex = true, }: TooltipProps) { const [show, setShow] = useState(false); @@ -75,8 +72,9 @@ export function Tooltip({ animate="done" exit="startOrStop" transition={{ - damping: 50, - stiffness: 500, + ease: "easeOut", + duration: 0.15, + delay: 0.3, }} >

{message}

@@ -109,10 +107,7 @@ export function Tooltip({ // This is to avoid having to add those attribute as a prop on every // component we have. activator.setAttribute("aria-description", message); - - if (setTabIndex) { - activator.setAttribute("tabindex", "0"); // enable focus - } + activator.setAttribute("tabindex", "0"); // enable focus } }; diff --git a/packages/components/src/Typography/Typography.test.tsx b/packages/components/src/Typography/Typography.test.tsx index 48fd27a797..aba21d05b1 100644 --- a/packages/components/src/Typography/Typography.test.tsx +++ b/packages/components/src/Typography/Typography.test.tsx @@ -255,3 +255,43 @@ it("renders a center-aligned text", () => { `); }); + +describe("underlining", () => { + it("renders an underline when a style is specified", () => { + const { container } = render( + Underline me, + ); + + const snapshot = ` +
+

+ Underline me +

+
+ `; + + expect(container).toMatchInlineSnapshot(snapshot); + }); + + it("renders an underline with a customizable color", () => { + const { container } = render( + Underline me, + ); + + const snapshot = ` +
+

+ Underline me +

+
+ `; + + expect(container).toMatchInlineSnapshot(snapshot); + }); +}); diff --git a/packages/components/src/Typography/Typography.tsx b/packages/components/src/Typography/Typography.tsx index f785ac8c44..5634e4b237 100644 --- a/packages/components/src/Typography/Typography.tsx +++ b/packages/components/src/Typography/Typography.tsx @@ -10,6 +10,8 @@ import emphasis from "./css/Emphasis.css"; import truncate from "./css/Truncate.css"; import alignment from "./css/TextAlignment.css"; import fontFamilies from "./css/FontFamilies.css"; +import underlineStyles from "./css/Underline.css"; +import { UnderlineStyle, UnderlineStyleWithColor } from "./types"; interface TypographyProps { readonly id?: string; @@ -50,6 +52,16 @@ interface TypographyProps { readonly fontFamily?: keyof typeof fontFamilies; readonly children: ReactNode; readonly numberOfLines?: number; + + /** + * The style (and optionally a color) of underline the text is decorated with. + * All semantic color tokens (other than the base values) defined in tokens.web + * are valid values. If omitted, no underline is applied. + * + * @example "solid" for a non-dashed underline of the same color as `textColor` + * @example "double color-invoice" for a double underline in the specified color + */ + readonly underline?: UnderlineStyle | UnderlineStyleWithColor | undefined; } export type TypographyOptions = Omit; @@ -65,6 +77,7 @@ export function Typography({ emphasisType, numberOfLines, fontFamily, + underline, }: TypographyProps) { const shouldTruncateText = numberOfLines && numberOfLines > 0; const className = classnames( @@ -76,23 +89,49 @@ export function Typography({ emphasisType && emphasis[emphasisType], fontFamily && fontFamilies[fontFamily], shouldTruncateText && truncate.textTruncate, + underline && underlineStyles.basicUnderline, { ...(align && { [alignment[align]]: align !== `start` }), }, ); - let truncateLines: CSSProperties | undefined; + let stylesOverrides: CSSProperties = {}; if (shouldTruncateText) { - truncateLines = { + stylesOverrides = { WebkitLineClamp: numberOfLines, WebkitBoxOrient: "vertical", }; } + if (underline) { + const [underlineStyle, underlineColor] = underline.split(" "); + + stylesOverrides.textDecorationStyle = underlineStyle as UnderlineStyle; + stylesOverrides.textDecorationColor = computeUnderlineColor( + underlineColor, + textColor, + ); + } + return ( - + {children} ); } + +function computeUnderlineColor( + textDecorationColor: string, + textColor?: keyof typeof textColors, +): string | undefined { + // Use the specified underline color if one is provided. If no underline color + // is specified, fall back to the text color for the underline. + if (textDecorationColor) { + return `var(--${textDecorationColor})`; + } + + if (textColor) { + return textColors[textColor]; + } +} diff --git a/packages/components/src/Typography/css/Underline.css b/packages/components/src/Typography/css/Underline.css new file mode 100644 index 0000000000..311580a75c --- /dev/null +++ b/packages/components/src/Typography/css/Underline.css @@ -0,0 +1,5 @@ +.basicUnderline { + text-decoration-line: underline; + text-decoration-thickness: 1px; + text-underline-offset: var(--space-smaller); +} diff --git a/packages/components/src/Typography/css/Underline.css.d.ts b/packages/components/src/Typography/css/Underline.css.d.ts new file mode 100644 index 0000000000..cfe1d093a0 --- /dev/null +++ b/packages/components/src/Typography/css/Underline.css.d.ts @@ -0,0 +1,5 @@ +declare const styles: { + readonly "basicUnderline": string; +}; +export = styles; + diff --git a/packages/components/src/Typography/types.ts b/packages/components/src/Typography/types.ts new file mode 100644 index 0000000000..2a452cb088 --- /dev/null +++ b/packages/components/src/Typography/types.ts @@ -0,0 +1,15 @@ +import { tokens } from "@jobber/design"; + +type Color = { + // Omit non-color properties from the token map + [K in keyof typeof tokens]: K extends `color-${string}` + ? // Omit the base colors as they shouldn't be used + K extends `color-base-${string}` + ? never + : K + : never; +}[keyof typeof tokens]; + +export type UnderlineStyle = "solid" | "double" | "dotted" | "dashed"; + +export type UnderlineStyleWithColor = `${UnderlineStyle} ${Color}`; diff --git a/packages/design/CHANGELOG.md b/packages/design/CHANGELOG.md index 4329d03ffb..7a8d6f41db 100644 --- a/packages/design/CHANGELOG.md +++ b/packages/design/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.65.1](https://github.com/GetJobber/atlantis/compare/@jobber/design@0.65.0...@jobber/design@0.65.1) (2024-09-17) + + +### Bug Fixes + +* **design:** Update critical color for dark mode ([#2006](https://github.com/GetJobber/atlantis/issues/2006)) ([88254f2](https://github.com/GetJobber/atlantis/commit/88254f22708ca8c7ef60283187e8a9c136210f89)) + + + + + # [0.65.0](https://github.com/GetJobber/atlantis/compare/@jobber/design@0.64.0...@jobber/design@0.65.0) (2024-08-22) diff --git a/packages/design/package.json b/packages/design/package.json index 0d102c6b44..066215967c 100644 --- a/packages/design/package.json +++ b/packages/design/package.json @@ -1,6 +1,6 @@ { "name": "@jobber/design", - "version": "0.65.0", + "version": "0.65.1", "description": "Design foundation for the Jobber Atlantis Design System", "license": "MIT", "type": "module", diff --git a/packages/design/src/tokens/dark.tokens.json b/packages/design/src/tokens/dark.tokens.json index 2662113e0b..ca600f9509 100644 --- a/packages/design/src/tokens/dark.tokens.json +++ b/packages/design/src/tokens/dark.tokens.json @@ -51,7 +51,7 @@ } }, "critical": { - "$value": "{color.base.red-.600}" + "$value": "{color.base.red-.500}" }, "critical-": { "surface": {