Reaktionsfähige und anpassungsfähige Entwicklung
Auf der Suche nach Designrichtlinien? Siehe Reaktionsfähiges und anpassungsfähiges Design.
Bedingungen
Jutro-Komponenten können sein:
- Reaktionsfähig: Die Komponente passt sich mithilfe von Medienabfragen automatisch an die Bildschirmgröße an.
- Anpassungsfähig: Die Komponente passt sich nicht an die Bildschirmgröße an. Stattdessen sollten Sie sie mithilfe der Messpunkt-Eigenschaften konfigurieren. Die Komponente wird je nach Gerät unterschiedlich gerendert.
- Fixiert: Die Komponente passt sich nicht an die Bildschirmgröße an.
In Storybook ist jede Komponente in der oberen Leiste als reaktionsfähig, anpassungsfähig oder fixiert gekennzeichnet.
Die meisten Komponenten sind zwar für alle Gerätetypen reaktionsfähig, aber einige lassen sich auf kleinen Geräten möglicherweise nicht so gut verwenden wie auf großen. Wir empfehlen Ihnen, diese Komponenten mit Messpunkt-Eigenschaften zu konfigurieren.
Zusammenarbeit mit Designern
Ein Designer kann Ihnen App-Entwürfe für verschiedene Messpunkte zur Verfügung stellen. Fragen Sie ihn nach den erwarteten Verhaltensweisen und so weiter, um sicherzustellen, dass Sie verstehen, wie der Benutzer das Beste aus der Anwendung herausholen kann.
Im Folgenden finden Sie allgemeine Tipps für die Zusammenarbeit mit Designern:
- Achten Sie auf Designs desselben Bildes auf verschiedenen Bildschirmen.
- Designer können den Begriff „Formfaktor“ verwenden, um etwas Ähnliches wie den Messpunkt zu meinen, z. B. Desktop, Tablet und so weiter.
- Verwenden Sie die „Inspektions“-Tools in der Design-App (z. B. Figma), um die gewünschten Abstände und Größen zu erfahren. Stellen Sie sicher, dass Sie Abstands- und Größenvariablen von Jutro verwenden (siehe Theming).
- Zeigen Sie Ihre App so bald wie möglich einem Designer, um Feedback zu erhalten und weitere Funktionen iterativ zu implementieren.
- Holen Sie sich Klarheit über Dinge, die Sie nicht verstehen oder bei denen Sie sich nicht sicher sind, wie sie funktionieren sollen.
- Verwenden Sie die Entwicklertools in Ihrem Browser, um das Layout an verschiedenen Messpunkten zu testen.
- Wenn die Anwendung auf Telefonen funktionieren soll, entwickeln Sie sie zuerst für Telefone und gehen Sie später zu größeren Formaten über.
Machbarkeit
Besprechen Sie die Entwürfe mit dem Designer und arbeiten Sie an der Machbarkeit. Dieser Prozess wird auch als „UI QA“ (Qualitätssicherung) bezeichnet:
- Sind alle Komponenten verfügbar? Sie werden beispielsweise aufgefordert, ein Bildkarussell bereitzustellen, das jedoch keine Jutro-Standardkomponente ist. Das bedeutet, dass Sie Ihr eigenes erstellen müssen.
- Funktionieren alle Komponenten wie vorgesehen? Möglicherweise müssen Sie an dieser Stelle eine POC-App erstellen.
- Wenn komplexe Funktionen oder Interaktionen auftreten, prüfen Sie, ob diese in Jutro erlaubt und kostengünstig zu implementieren sind. Ist zum Beispiel das Karussell der beste Weg, um den Anwendungsfall zu erfüllen? Es wird eine Option benötigt, damit an Kinetose leidende Benutzer den Bildwechsel stoppen können. Ist es machbar, diese Funktionalität im Rahmen Ihres Projekts zu erstellen?
Messpunkt-Eigenschaften
Die Messpunkt-Eigenschaften sind:
| Name der Eigenschaft | Breite |
|---|---|
phone | < 580px |
phoneWide (phone in horizontal mode) | 580px to 768px |
tablet | 768px to 1024px |
desktop | > 1024px |
Use these props to override the behavior of your component on different devices.
Override children
For example, you want your action title bar to not include an action when the user is on a mobile device. In that case, you can use the phone prop to override the children of the component.
<ActionTitleBar
phone={{
children: (
<TitleElement>
{/* On a phone, render only a heading */}
<h4>Just a title</h4>
</TitleElement>
),
}}>
{/* On large screens, render an icon with a tooltip and a button */}
<TitleElement>
<h4>Title</h4>
<TooltipIcon {...tooltipIconProps} />
<Button {...buttonProps}>Edit</Button>
</TitleElement>
</ActionTitleBar>
You can make most components render differently on different devices using breakpoint props.
Override props
These props override component props when rendering on a particular device. For example:
<RadioButtonField
availableValues={[
{
displayName: 'Perform payment on checkout',
},
{
displayName: 'Defer payment to a later date',
},
]}
phone={{
availableValues: [
{
displayName: 'Pay now',
},
{
displayName: 'Pay later',
},
],
}}
/>
This is a very simplistic example of displaying different labels for checkboxes on small screens. Normally, you would use intl messages to render text for a component, but this example illustrates how you would pass a copy of the availableValues prop and override the items it contains.
Override styles
Another example is overriding styles for different devices:
<Card
className={styles.largeBanner}
phone={{
className: styles.smallBanner,
}}
tablet={{
className: styles.mediumBanner,
}}
/>
In this example, we have a card that is styled differently depending on the device size.
The useBreakpoint hook
useBreakpoint hook. Breakpoint props like phone, tablet, and phoneWide are not available on new components out of the box.The useBreakpoint hook is exported from @jutro/layout and can be imported like import { useBreakpoint } from '@jutro/layout'. It returns an object with the following properties:
breakpointProps- the props you passed into the component merged with the values you overrode in one of the following props:phone,phoneWide,tabletapplyBreakpoint- a function that takespropsas an argument and returnsbreakpointProps. You can use it in any nested code within your component where hooks are not allowed.breakpoint- the name of the current breakpoint. Any ofphone,phoneWide,tablet, ordesktop.
There are four ways you can use the hook to get the expected result.
1. Get props from the hook for a certain breakpoint
The first way to use the hook is to pass the props and apply the output to the component.
In the following example, the phone props object is passed to useBreakpoint. The props are then returned as textInputPhoneProps on the phone breakpoint. Then they are assigned to TextInput and the previously added props are overridden. The label prop is overridden, then placeholder and className are added when the hook detects that the breakpoint changes to phone.
const { breakpointProps: textInputPhoneProps } = useBreakpoint({
phone: {
placeholder: 'Phone placeholder',
label: 'Phone label',
className: 'somePhoneClass',
},
});
return (
<TextInput
label="Text input"
{...textInputPhoneProps}
/>
);
2. Only get breakpoint status from hook
The second way to use the hook is to only get the breakpoint status and use it to change that prop based on the returned breakpoint. In the following example, breakpoint is obtained from the hook and it changes based on the viewport. A different value for the label prop can be assigned if the breakpoint is set to phone.
const { breakpoint } = useBreakpoint({});
return (
<TextInput label={breakpoint === 'phone' ? 'Phone label' : 'Text input'} />
);
3. Get the callback that returns breakpoint props
The third way to use the hook is to use applyBreakpoint - it is very similar to the first option but can be better if there are different components on the same page that would require many useBreakPoints instances. In the following example, the callback that is returning the props is obtained and then applyBreakpoint is assigned to every TextInput and different props are passed to every component.
const { applyBreakpoint } = useBreakpoint({});
return (
<React.Fragment>
<TextInput
label="Text input"
{...applyBreakpoint({
phone: {
placeholder: 'Phone placeholder',
label: 'Phone label',
className: 'somePhoneClass',
},
})}
/>
<TextInput
label="Text input instance 2"
{...applyBreakpoint({
phone: {
placeholder: 'Another Phone placeholder',
label: 'Another Phone label',
className: 'someOtherPhoneClass',
},
})}
/>
</React.Fragment>
);
4. All at once
It is possible to get all three things that are returned from useBreakpoint and use them all at once. For example:
const {
breakpointProps: textInputPhoneProps,
breakpoint,
applyBreakpoint,
} = useBreakpoint({
phone: {
placeholder: 'Phone placeholder',
label: 'Phone label',
className: 'somePhoneClass',
},
});
return (
<React.Fragment>
<TextInput
label="Text input"
{...textInputPhoneProps}
/>
<TextInput
label={breakpoint === 'phone' ? 'Phone label 2' : 'Text input'}
/>
<TextInput
label="Text input"
{...applyBreakpoint({
phone: {
placeholder: 'Phone placeholder 3',
label: 'Phone label 3',
className: 'someOtherPhoneClass',
},
})}
/>
</React.Fragment>
);
Different props for a breakpoint
A typical use case for the hook is when you want to pass custom props to a component based on the breakpoint. When using the component, you do the following:
<BigBanner
phone={{
message: 'Call us!',
}}
message="Get in touch"
/>
That means you want your users to see the message "Get in touch" in most cases. But, when they are on the phone, you want them to see a message that says "Call us!"
Inside the functional component, you use the useBreakpoint hook, and you pass the props as the only argument. Then, you extract the value of the message property.
On desktop, it returns "Get in touch", and on phone, it returns "Call us!"
import { useBreakpoint } from '@jutro/layout';
function BigBanner(props) {
const { breakpointProps } = useBreakpoint(props);
return <div>{breakpointProps.message}</div>;
}
Different styles for a breakpoint
Another use case for the hook is to apply different styling to a component based on the current breakpoint.
You create two classes in your styles module:
.defaultColor {
color: magenta;
}
.phoneColor {
color: blue;
}
To use the styles, you get the current breakpoint using the hook. Then, you select a class based on the value of the hook.
import React from 'react';
import { useBreakpoint } from '@jutro/layout';
import styles from './BigBanner.module.scss';
function BigBanner(props) {
const { breakpoint } = useBreakpoint(props);
const myClass =
breakpoint === 'phone' ? styles.phoneColor : styles.defaultColor;
return <div className={myClass}>Welcome to my banner!</div>;
}
Or:
import React from 'react';
import cx from 'classnames';
import styles from './BigBanner.module.scss';
import { useBreakpoint } from '@jutro/layout';
function BigBanner(props) {
const { breakpointProps } = useBreakpoint({
...props,
className: cx(props.className, styles.defaultColor),
phone: {
className: cx(props.className, styles.phoneColor),
},
});
const { className } = breakpointProps;
return <div className={className}>{breakpointProps.message}</div>;
}
The BreakpointTracker context
You can also use BreakpointTrackerContext and BreakpointTracker directly. You can use them within class components or other places where using hooks is impossible.
The entire app is wrapped in this context provider, so you can use it within a class component, for example inside the render block. You can also use it within functions that are not nested inside functional components.
BreakpointTracker.applyBreakpointOverrides returns only a breakpointProps object.
It takes two arguments: props and a breakpoint.
Breakpoint context in a functional component
import React, { useContext } from 'react';
import { BreakpointTracker, BreakpointTrackerContext } from '@jutro/layout';
function BigBanner(props) {
const breakpoint = useContext(BreakpointTrackerContext);
const someRenderFunction = () => {
const breakpointProps = BreakpointTracker.applyBreakpointOverrides(
props,
breakpoint
);
return (
<pre>
<code>{JSON.stringify(breakpointProps, null, 2)}</code>
</pre>
);
};
return <div>{someRenderFunction()}</div>;
}
Breakpoint context in a class component
import React from 'react';
import { BreakpointTracker, BreakpointTrackerContext } from '@jutro/layout';
class ClassBanner extends React.Component {
static contextType = BreakpointTrackerContext;
render() {
const breakpointProps = BreakpointTracker.applyBreakpointOverrides(
this.props,
this.context
);
return (
<pre>
<code>{JSON.stringify(breakpointProps, null, 2)}</code>
</pre>
);
}
}
export default ClassBanner;
Breakpoints in unit tests
To test breakpoints, you can use the following example as a guide. It tests the BigBanner component which we created in other sections of this page.
import React from 'react';
import { render } from '@testing-library/react';
import { BreakpointTrackerContext } from '@jutro/layout';
import { BigBanner } from '../BigBanner';
import styles from '../BigBanner.module.scss';
function renderBigBannerWithBreakpoints(props = {}, breakpoint = 'desktop') {
return render(
<BreakpointTrackerContext.Provider value={breakpoint}>
<BigBanner {...props} />
</BreakpointTrackerContext.Provider>
);
}
describe('BigBanner', () => {
it('renders with default class on desktop', () => {
const { container } = renderBigBannerWithBreakpoints();
expect(container.firstChild).toHaveClass(styles.defaultColor);
});
it('renders with mobile class on phone', () => {
const { container } = renderBigBannerWithBreakpoints({}, 'phone');
expect(container.firstChild).toHaveClass(styles.phoneColor);
});
});