Skip to content

Commit

Permalink
docs: create a Patterns docs section and add 3 patterns (#2332)
Browse files Browse the repository at this point in the history
* add file for disabled state docs

* add patterns nav infra and one docs page

* add to home page and create thumbs

* Get page loading

* flesh out the doc

* add interactive patterns

* Add empty states - wip

* more refinements

* remove console log

* clean up formattting of NN principlies

* format interactions like the other patterns

* ready to go empty states

* update disabled state feedback

* revise empty states content

* use thumbnail in Home

---------

Co-authored-by: Aiden-Brine <[email protected]>
  • Loading branch information
chris-at-jobber and Aiden-Brine authored Jan 29, 2025
1 parent cfa7f46 commit b48f362
Show file tree
Hide file tree
Showing 19 changed files with 544 additions and 0 deletions.
99 changes: 99 additions & 0 deletions docs/patterns/disabled-states.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Meta } from "@storybook/addon-docs";
import { Figma } from "@storybook/addon-designs/blocks";
import { InlineLabel, Tabs, Tab } from "@jobber/components";

<Meta title="Patterns/Disabled states" />

# Disabled states

| **Platform** | **Status** |
| :--------------------------------------------------------------- | :--------------------------------------------- |
| <InlineLabel>Web</InlineLabel> <InlineLabel>Mobile</InlineLabel> | <InlineLabel color="green">Ready</InlineLabel> |

This pattern involves how we disable, show, or hide actions based on different
criteria. It becomes a balance in explaining the interface and not overwhelming
users with choice. Below are a few of the common examples and accompanying
guidelines for handling those situations.

## Goal

Avoid placing the user in a frustrating state where actions are unavailable and
they do not know how to enable.

## Use when…

There's absolutely no other way to avoid a disabled state. This should be a last
resort. This flowchart can help you find alternatives:

<Figma url="https://www.figma.com/design/HXWXusJPZLmaJNGlKEpiyhXW/Product%2FBase?m=auto&node-id=21011-642&t=SHOjoSApnGE6QQeB-1" />

## Solution

### Avoid disabled states

Your first goal should be to avoid disabled states entirely. If there is a
condition required for the user to take an action, present the opportunity to
set the required condition before the user is blocked by a disabled state. An
example of this is on our “work objects” which all require a client to be saved.
If the user makes it to the bottom of the form and has not added a client, the
primary CTA is “Select Client“.

<Figma url="https://www.figma.com/design/HXWXusJPZLmaJNGlKEpiyhXW/Product%2FBase?m=auto&node-id=21013-643&t=SHOjoSApnGE6QQeB-1" />

#### Permissions-related unavailability

When functionality or a feature isn't available because of a user's permissions,
hide the action altogether.

#### Account-tier-related unavailability

If functionality is unavailable because of a user’s subscription tier, but we
want to introduce the functionality to encourage them to upgrade, use an inline
element near where the unavailable UI would exist, or a
`Button variation="learning"` in place of the UI to nudge the user to learn
more.

### Why

Avoiding disabled states is an accessibility best practice and also generally
helpful from a usability perspective. The more time a user spends trying to
understand why they can’t take an action, the less time they spend getting sh\*t
done.

Disabling elements is often the easiest path for a development team to manage
conditional states of an interface to design and build, but results in a
less-friendly interface for the user.

### Implementation

While you should avoid disabled states, many Atlantis components do offer a
`disabled` boolean property for absolutely necessary cases. If you use the
disabled state, you must ensure that the user can understand _why_ the element
is disabled.

<Tabs>
<Tab label="Web">
A [Tooltip](../components/Tooltip) may help explain to the user why they
can’t select an element. Make sure the Tooltip can be triggered both by
focus and hover.
<Figma url="https://www.figma.com/design/HXWXusJPZLmaJNGlKEpiyhXW/Product%2FBase?m=auto&node-id=21049-582&t=SHOjoSApnGE6QQeB-1" />
</Tab>
<Tab label="Mobile">
[Text](../component/Text) (level="supportingText") is more helpful for users
in the mobile app as it does not require an additional interaction to
uncover. If an input element (such as a Select or InputText) has “assistive
text” built in, do not use the assistive text in a disabled state, but
provide the supporting Text as a separate element.
<Figma url="https://www.figma.com/design/HXWXusJPZLmaJNGlKEpiyhXW/Product%2FBase?m=auto&node-id=21049-645&t=SHOjoSApnGE6QQeB-1" />
</Tab>
</Tabs>

## Related

- [Interaction](../patterns/interaction)
- [Empty states](../patterns/empty-states)

## Principles

- [Visibility of system status](https://www.nngroup.com/articles/visibility-system-status/)
- [Recognition over recall](https://www.nngroup.com/articles/recognition-and-recall/)
144 changes: 144 additions & 0 deletions docs/patterns/empty-states.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Meta, Canvas } from "@storybook/addon-docs";
import { Figma } from "@storybook/addon-designs/blocks";
import {
InlineLabel,
Box,
Icon,
Heading,
Text,
Button,
Tabs,
Tab,
} from "@jobber/components";

<Meta title="Patterns/Empty states" />

# Empty states

| **Platform** | **Status** |
| :--------------------------------------------------------------- | :--------------------------------------------- |
| <InlineLabel>Web</InlineLabel> <InlineLabel>Mobile</InlineLabel> | <InlineLabel color="green">Ready</InlineLabel> |

## Goal

Ensure the user does not hit a dead end when there is no content to display.

## Use when...

Use when there _could_ be content in a view, but there is none. Possible reasons
for the “emptiness” may include, but are not limited to…

- A notifications panel when the user hasn't received any notifications
- A list of user-generated content before user has created any content (for
example, a list of clients when the user doesn’t yet have any clients)
- Dashboards or other data-centric views when the user has not generated any
data (ie Home)
- Search results with no match
- An error has resulted in the user not being able to access content they
otherwise could

## Solution

A successful empty state should:

- communicate system status
- increase learnability of the system
- deliver direct pathways for key tasks

[Designing Empty States in Complex Applications: 3 Guidelines](https://www.nngroup.com/articles/empty-state-interface-design/#:~:text=Empty%20states%20that%20are%20intentionally,getting%20started%20with%20key%20tasks)

<Figma url="https://www.figma.com/design/HXWXusJPZLmaJNGlKEpiyhXW/Product%2FBase?m=auto&node-id=21162-662&t=SHOjoSApnGE6QQeB-1" />

#### When _not_ to add a CTA

You might not use a direct CTA in the empty state if:

- The action to populate the empty state has dependencies on another action
being taken
- Example: Quotes must be approved by clients before they appear in a list of
"approved quotes"
- Example: A payment requires creation and delivery of an invoice, and the
client to make a payment, before it exists

### In lists

Adjust your context and actions if the user has no content because of a search
or filter, vs when they have no content at all.

<Figma url="https://www.figma.com/design/HXWXusJPZLmaJNGlKEpiyhXW/Product%2FBase?m=auto&node-id=21189-604&t=nhEwO89McEzvF5TV-1" />

<Figma url="https://www.figma.com/design/HXWXusJPZLmaJNGlKEpiyhXW/Product%2FBase?m=auto&node-id=21162-768&t=SHOjoSApnGE6QQeB-1" />

### Error states

A full-screen "empty" error state can help the user make sense of what's going
on, and how to get back on track. If an entire view goes blank due to an error,
a single Banner can look out of place.

<Canvas>
<Box direction="column" alignItems="center" gap="base" width="100%">
<Icon size="large" name="alert" color="critical" />
<Heading level={4}>Something went wrong</Heading>
<Text>Couldn't load content. Refresh to try again.</Text>
<Button label="Refresh" />
</Box>
</Canvas>

### When the user can't add content

When a card is empty, and there is no way for the user to add content, you
should still show some form of empty state, but without a CTA.

Do not hide the card, as this state-based hiding and showing may not be
intuitive for the user. For example, see the ”Payments” card here, where the
user needs to create an invoice and interact with their client before they can
create a payment against a job.

<Figma url="https://www.figma.com/design/HXWXusJPZLmaJNGlKEpiyhXW/Product%2FBase?m=auto&node-id=21341-20062&t=nhEwO89McEzvF5TV-1" />

## Why

By considering and accounting for these often “un-happy” paths in the user’s
journey, we can allow the user to learn how to use Jobber more effectively,
guide them out of troublesome scenarios, and ensure that they never feel like
they’ve hit a dead-end in the product.

## Implementation

<Tabs>
<Tab label="Web">
There's no component but these are some common patterns:

<Canvas>
<Box direction="column" alignItems="center" gap="base" width="100%">
<Icon size="large" name="alert" />
<Heading level={4}>No results found</Heading>
<Text>Try adjusting your search criteria or clearing filters</Text>
<Button variation="subtle" label="Clear Filters" />
</Box>
</Canvas>
</Tab>
<Tab label="Mobile">
[EmptyState](../component/EmptyState?mobile) gives you the boilerplate
`Icon` + `Heading` + `Text` + `Button` out of the box but you can also compose it
similar to the web implementation if more flexibility is needed.
</Tab>
</Tabs>

## Not obvious details

In some cases, illustrations may be useful in an empty state to bring some
personality to the scenario. If this fits your use case, work with the design
team to find or create an appropriate illustration.

## Related

- [Empty State](../component/EmptyState?mobile) mobile component
- [Disabled states](../patterns/disabled-states)

## Principles

- [Visibility of system status](https://www.nngroup.com/articles/visibility-system-status/)
- [Consistency and standards](https://www.nngroup.com/articles/consistency-and-standards/)
- [Aesthetics and minimalist design](https://www.nngroup.com/videos/aesthetic-and-minimalist-design/)
- [Help users recognize, diagnose and recover from errors](https://www.nngroup.com/articles/ten-usability-heuristics/#toc-9-help-users-recognize-diagnose-and-recover-from-errors-9)
128 changes: 128 additions & 0 deletions docs/patterns/interaction.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Canvas, Meta } from "@storybook/addon-docs";
import { Figma } from "@storybook/addon-designs/blocks";
import {
Card,
Content,
Disclosure,
InlineLabel,
InputText,
Text,
Tabs,
Tab,
} from "@jobber/components";

<Meta title="Patterns/Interaction" />

# Interaction

| **Platform** | **Status** |
| :--------------------------------------------------------------- | :--------------------------------------------- |
| <InlineLabel>Web</InlineLabel> <InlineLabel>Mobile</InlineLabel> | <InlineLabel color="green">Ready</InlineLabel> |

## Goal

Provide feedback (or feedforward) to the user in a consistent way to help them
predict what will happen when they engage with Jobber.

## Use when...

An interactive element is... being interacted with.

## Solution

<Tabs>
<Tab label="Web">
<Canvas>
<Card header="Interactive element example" url="#">
<Content>
<Text>This card has examples of hover and focus states for the web</Text>
<InputText placeholder="Inputs change too" />
</Content>
</Card>
</Canvas>

#### Hover
- background-color increases contrast with the surface
- cursor changes to a pointer

#### Focused with keyboard (:focus-visible)
- background-color increases contrast with the surface
- a custom focus ring is applied

#### Timing
Most state transitions use `var(--timing-base) ease-in` for a pleasant
transition.

#### CSS implementation
Atlantis components come with these states handled, but if you're extending or
creating custom elements, this is the general pattern used to achieve an "Atlantis"
interactive state.

```css
.myElement {
background-color: var(--color-surface);
/* apply consistent transitions to background and box-shadow */
transition: all var(--timing-base) ease-in;
cursor: pointer;
/* hide the default focus ring, but still support "high contrast" modes */
outline: transparent;
}

.myElement:hover,
/* use :focus-visible instead of :focus to reduce visual noise for mouse users */
.myElement:focus-visible {
background-color: var(--color-surface--hover);
}

.myElement:focus-visible {
box-shadow: var(--shadow-focus);
}
```

</Tab>

<Tab label="Mobile">
<Figma
collapsable
url="https://www.figma.com/proto/avvgu5SkbBvS8lGVePBsqO/Product%2FMobile?node-id=63935-11952&t=9XDlpJmD0lnq3e97-1"
/>

#### Pressed
- Most elements will reduce in opacity using the `opacity-pressed` token

#### Focused inputs
- While there is no system-wide "focus ring" in mobile, input borders change color when focused


#### React Native implementation

```.ts
// as a classname in styles.ts files

pressed {
opacity: tokens["opacity-pressed"]
}
```

```.ts
// as a property on react-native core components

<TouchableOpacity
activeOpacity={tokens["opacity-pressed"]}
onPress={yourActionHere}
>
```
</Tab>

</Tabs>

## Related

- [Disabled states](../patterns/disabled-states)
- [Color](../design/color)
- [Animation](../design/animation)

## Principles

- [Visibility of system status](https://www.nngroup.com/articles/visibility-system-status/)
- [Consistency and standards](https://www.nngroup.com/articles/consistency-and-standards/)
Binary file added packages/site/public/DisabledStates.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/site/public/DisabledStates.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/site/public/EmptyStates.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions packages/site/public/EmptyStates.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/site/public/Interaction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b48f362

Please sign in to comment.