mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Improve Guided lessons (#6446)
* Lessons have been slightly reworked overall * Most languages are now supported * A new option allows to fill a value automatically with a button, speeding up the typing part
This commit is contained in:
@@ -341,8 +341,9 @@
|
||||
"description": {
|
||||
"messageByLocale": {
|
||||
"en": "Let's choose an **object** from the asset store",
|
||||
"fr": "Nous allons choisir un objet dans le **magasin de ressources**.",
|
||||
"fr": "Nous allons choisir un **objet** dans le magasin de ressources.",
|
||||
"es": "Vamos a elegir un **objeto** de la tienda de recursos.",
|
||||
"pt": "Vamos escolher um objeto da loja de recursos.",
|
||||
"th": "เลือก **วัตถุ** จาก ร้านค้า asset",
|
||||
"ar": "هيّا نقوم باختيار **كائن** من متجر العناصر"
|
||||
}
|
||||
@@ -2180,6 +2181,7 @@
|
||||
"en": "Add a few $(obstacle) to the **scene** to protect $(target).",
|
||||
"fr": "Ajoutez quelques $(obstacle) à la **scène** pour protéger $(target).",
|
||||
"es": "Agrega algunos $(obstacle) a la **escena** para proteger a $(target).",
|
||||
"pt": "Adicione alguns $(obstacle) à **cena** para proteger $(target).",
|
||||
"th": "เพิ่ม $(obstacle) ปริมาณนิดหน่อยลงใน **scene** เพื่อป้องกัน $(target)",
|
||||
"ar": "إضافة بضعة $(obstacle) إلى **المشهد** لحماية $(target)."
|
||||
}
|
||||
|
@@ -2,7 +2,24 @@
|
||||
{
|
||||
"id": "cameraParallax",
|
||||
"contentUrl": "https://resources.gdevelop-app.com/in-app-tutorials/cameraParallax.json",
|
||||
"availableLocales": ["en", "fr", "es", "pt", "th", "ar"],
|
||||
"availableLocales": [
|
||||
"en",
|
||||
"fr",
|
||||
"ar",
|
||||
"de",
|
||||
"es",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt",
|
||||
"th",
|
||||
"ru",
|
||||
"sl",
|
||||
"sq",
|
||||
"uk",
|
||||
"zh"
|
||||
],
|
||||
"initialTemplateUrl": "https://resources.gdevelop-app.com/in-app-tutorials/templates/cameraParallax/game.json",
|
||||
"initialProjectData": {
|
||||
"cameraScene": "CameraScene",
|
||||
@@ -19,14 +36,48 @@
|
||||
{
|
||||
"id": "healthBar",
|
||||
"contentUrl": "https://resources.gdevelop-app.com/in-app-tutorials/healthBar.json",
|
||||
"availableLocales": ["en", "fr", "es", "pt", "th", "ar"],
|
||||
"availableLocales": [
|
||||
"en",
|
||||
"fr",
|
||||
"ar",
|
||||
"de",
|
||||
"es",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt",
|
||||
"th",
|
||||
"ru",
|
||||
"sl",
|
||||
"sq",
|
||||
"uk",
|
||||
"zh"
|
||||
],
|
||||
"initialTemplateUrl": "https://resources.gdevelop-app.com/in-app-tutorials/templates/healthBar/game.json",
|
||||
"initialProjectData": { "level": "Level", "player": "Player" }
|
||||
},
|
||||
{
|
||||
"id": "joystick",
|
||||
"contentUrl": "https://resources.gdevelop-app.com/in-app-tutorials/joystick.json",
|
||||
"availableLocales": ["en", "fr", "es", "pt", "th", "ar", "sq"],
|
||||
"availableLocales": [
|
||||
"en",
|
||||
"fr",
|
||||
"ar",
|
||||
"de",
|
||||
"es",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt",
|
||||
"th",
|
||||
"ru",
|
||||
"sl",
|
||||
"sq",
|
||||
"uk",
|
||||
"zh"
|
||||
],
|
||||
"initialTemplateUrl": "https://resources.gdevelop-app.com/in-app-tutorials/templates/joystick/game.json",
|
||||
"initialProjectData": {
|
||||
"gameScene": "GameScene",
|
||||
@@ -36,14 +87,48 @@
|
||||
{
|
||||
"id": "object3d",
|
||||
"contentUrl": "https://resources.gdevelop-app.com/in-app-tutorials/object3d.json",
|
||||
"availableLocales": ["en", "fr", "es", "pt", "th", "ar"],
|
||||
"availableLocales": [
|
||||
"en",
|
||||
"fr",
|
||||
"ar",
|
||||
"de",
|
||||
"es",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt",
|
||||
"th",
|
||||
"ru",
|
||||
"sl",
|
||||
"sq",
|
||||
"uk",
|
||||
"zh"
|
||||
],
|
||||
"initialTemplateUrl": "https://resources.gdevelop-app.com/in-app-tutorials/templates/object3d/game.json",
|
||||
"initialProjectData": { "gameScene": "GameScene" }
|
||||
"initialProjectData": { "gameScene": "GameScene", "platform": "Platform" }
|
||||
},
|
||||
{
|
||||
"id": "plinkoMultiplier",
|
||||
"contentUrl": "https://resources.gdevelop-app.com/in-app-tutorials/plinkoMultiplier.json",
|
||||
"availableLocales": ["en", "fr", "es", "pt", "th", "ar"],
|
||||
"availableLocales": [
|
||||
"en",
|
||||
"fr",
|
||||
"ar",
|
||||
"de",
|
||||
"es",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt",
|
||||
"th",
|
||||
"ru",
|
||||
"sl",
|
||||
"sq",
|
||||
"uk",
|
||||
"zh"
|
||||
],
|
||||
"initialTemplateUrl": "https://resources.gdevelop-app.com/in-app-tutorials/templates/plinkoMultiplier/game.json",
|
||||
"initialProjectData": {
|
||||
"gameScene": "GameScene",
|
||||
@@ -56,7 +141,24 @@
|
||||
{
|
||||
"id": "timer",
|
||||
"contentUrl": "https://resources.gdevelop-app.com/in-app-tutorials/timer.json",
|
||||
"availableLocales": ["en", "fr", "es", "pt", "th", "ar"],
|
||||
"availableLocales": [
|
||||
"en",
|
||||
"fr",
|
||||
"ar",
|
||||
"de",
|
||||
"es",
|
||||
"it",
|
||||
"ja",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt",
|
||||
"th",
|
||||
"ru",
|
||||
"sl",
|
||||
"sq",
|
||||
"uk",
|
||||
"zh"
|
||||
],
|
||||
"initialTemplateUrl": "https://resources.gdevelop-app.com/in-app-tutorials/templates/timer/game.json",
|
||||
"initialProjectData": { "gameScene": "GameScene" }
|
||||
}
|
||||
|
@@ -93,7 +93,6 @@ export const CustomObjectPackResults = ({
|
||||
<TextButton
|
||||
icon={<ChevronArrowLeft />}
|
||||
label={<Trans>Back</Trans>}
|
||||
primary={false}
|
||||
onClick={onBack}
|
||||
disabled={isAssetBeingInstalled}
|
||||
/>
|
||||
|
@@ -652,7 +652,6 @@ export const AssetStore = React.forwardRef<Props, AssetStoreInterface>(
|
||||
<TextButton
|
||||
icon={<ChevronArrowLeft />}
|
||||
label={<Trans>Back</Trans>}
|
||||
primary={false}
|
||||
onClick={async () => {
|
||||
const page = shopNavigationState.backToPreviousPage();
|
||||
const isUpdatingSearchtext = reApplySearchTextIfNeeded(
|
||||
|
@@ -43,6 +43,7 @@ import useAlertDialog from '../UI/Alert/useAlertDialog';
|
||||
import PasteIcon from '../UI/CustomSvgIcons/Clipboard';
|
||||
import CopyIcon from '../UI/CustomSvgIcons/Copy';
|
||||
import ResponsiveFlatButton from '../UI/ResponsiveFlatButton';
|
||||
import { useResponsiveWindowSize } from '../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -281,6 +282,7 @@ type Props = {|
|
||||
|};
|
||||
|
||||
const BehaviorsEditor = (props: Props) => {
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const scrollView = React.useRef<?ScrollViewInterface>(null);
|
||||
const [
|
||||
justAddedBehaviorName,
|
||||
@@ -598,7 +600,9 @@ const BehaviorsEditor = (props: Props) => {
|
||||
helpPagePath="/behaviors"
|
||||
tutorialId="intro-behaviors-and-functions"
|
||||
actionButtonId="add-behavior-button"
|
||||
actionLabel={<Trans>Add a behavior</Trans>}
|
||||
actionLabel={
|
||||
isMobile ? <Trans>Add</Trans> : <Trans>Add a behavior</Trans>
|
||||
}
|
||||
onAction={() => setNewBehaviorDialogOpen(true)}
|
||||
secondaryActionIcon={<PasteIcon />}
|
||||
secondaryActionLabel={
|
||||
@@ -645,7 +649,13 @@ const BehaviorsEditor = (props: Props) => {
|
||||
<ResponsiveFlatButton
|
||||
key={'copy-all-behaviors'}
|
||||
leftIcon={<CopyIcon />}
|
||||
label={<Trans>Copy all behaviors</Trans>}
|
||||
label={
|
||||
isMobile ? (
|
||||
<Trans>Copy all</Trans>
|
||||
) : (
|
||||
<Trans>Copy all behaviors</Trans>
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
copyAllBehaviors();
|
||||
}}
|
||||
@@ -663,7 +673,13 @@ const BehaviorsEditor = (props: Props) => {
|
||||
<LineStackLayout justifyContent="flex-end" expand>
|
||||
<RaisedButton
|
||||
key="add-behavior-line"
|
||||
label={<Trans>Add a behavior</Trans>}
|
||||
label={
|
||||
isMobile ? (
|
||||
<Trans>Add</Trans>
|
||||
) : (
|
||||
<Trans>Add a behavior</Trans>
|
||||
)
|
||||
}
|
||||
primary
|
||||
onClick={() => setNewBehaviorDialogOpen(true)}
|
||||
icon={<Add />}
|
||||
|
@@ -370,7 +370,6 @@ const PublishHome = ({
|
||||
<TextButton
|
||||
icon={<ChevronArrowLeft />}
|
||||
label={<Trans>Back</Trans>}
|
||||
primary={false}
|
||||
onClick={onBack}
|
||||
disabled={isNavigationDisabled}
|
||||
/>
|
||||
|
@@ -1039,20 +1039,20 @@ const InAppTutorialOrchestrator = React.forwardRef<
|
||||
|
||||
let formattedStepTrigger;
|
||||
const stepTrigger = currentStep.nextStepTrigger;
|
||||
if (stepTrigger) {
|
||||
if (stepTrigger.clickOnTooltipButton) {
|
||||
const formattedButtonLabel = translateAndInterpolateText({
|
||||
text: stepTrigger.clickOnTooltipButton,
|
||||
data,
|
||||
i18n,
|
||||
project,
|
||||
});
|
||||
formattedStepTrigger = formattedButtonLabel
|
||||
? {
|
||||
clickOnTooltipButton: formattedButtonLabel,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
if (stepTrigger && stepTrigger.clickOnTooltipButton) {
|
||||
const formattedButtonLabel = translateAndInterpolateText({
|
||||
text: stepTrigger.clickOnTooltipButton,
|
||||
data,
|
||||
i18n,
|
||||
project,
|
||||
});
|
||||
formattedStepTrigger = formattedButtonLabel
|
||||
? {
|
||||
clickOnTooltipButton: formattedButtonLabel,
|
||||
}
|
||||
: undefined;
|
||||
} else {
|
||||
formattedStepTrigger = stepTrigger;
|
||||
}
|
||||
const formattedStep: InAppTutorialFlowFormattedStep = {
|
||||
...currentStep,
|
||||
|
@@ -100,7 +100,10 @@ const getWrongEditorTooltip = (
|
||||
|
||||
export const queryElementOrItsMostVisuallySignificantParent = (
|
||||
elementToHighlightId: string
|
||||
) => {
|
||||
): {|
|
||||
elementToHighlight: ?HTMLElement,
|
||||
elementWithId: ?HTMLElement,
|
||||
|} => {
|
||||
let foundElement = document.querySelector(elementToHighlightId);
|
||||
if (foundElement instanceof HTMLTextAreaElement) {
|
||||
// In this case, the element to highlight is a Material UI multiline text field
|
||||
@@ -108,7 +111,7 @@ export const queryElementOrItsMostVisuallySignificantParent = (
|
||||
// to highlight the parent div.
|
||||
const parentDiv = foundElement.closest('div');
|
||||
if (parentDiv instanceof HTMLElement && isElementAMuiInput(parentDiv)) {
|
||||
foundElement = parentDiv;
|
||||
return { elementToHighlight: parentDiv, elementWithId: foundElement };
|
||||
}
|
||||
} else if (
|
||||
foundElement instanceof HTMLInputElement &&
|
||||
@@ -116,10 +119,13 @@ export const queryElementOrItsMostVisuallySignificantParent = (
|
||||
) {
|
||||
const containerDiv = foundElement.closest('div[data-search-bar-container]');
|
||||
if (containerDiv instanceof HTMLElement) {
|
||||
foundElement = containerDiv;
|
||||
return { elementToHighlight: containerDiv, elementWithId: foundElement };
|
||||
}
|
||||
}
|
||||
return foundElement;
|
||||
return {
|
||||
elementToHighlight: foundElement,
|
||||
elementWithId: foundElement,
|
||||
};
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
@@ -149,6 +155,7 @@ function InAppTutorialStepDisplayer({
|
||||
elementToHighlight,
|
||||
setElementToHighlight,
|
||||
] = React.useState<?HTMLElement>(null);
|
||||
const [elementWithId, setElementWithId] = React.useState<?HTMLElement>(null);
|
||||
const [
|
||||
hideBehindOtherDialog,
|
||||
setHideBehindOtherDialog,
|
||||
@@ -169,9 +176,12 @@ function InAppTutorialStepDisplayer({
|
||||
() => {
|
||||
if (!elementToHighlightId) return;
|
||||
|
||||
setElementToHighlight(
|
||||
queryElementOrItsMostVisuallySignificantParent(elementToHighlightId)
|
||||
);
|
||||
const {
|
||||
elementToHighlight,
|
||||
elementWithId,
|
||||
} = queryElementOrItsMostVisuallySignificantParent(elementToHighlightId);
|
||||
setElementToHighlight(elementToHighlight);
|
||||
setElementWithId(elementWithId);
|
||||
},
|
||||
[elementToHighlightId]
|
||||
);
|
||||
@@ -262,6 +272,36 @@ function InAppTutorialStepDisplayer({
|
||||
);
|
||||
};
|
||||
|
||||
const getFillAutomaticallyFunction = React.useCallback(
|
||||
() => {
|
||||
if (!nextStepTrigger || !nextStepTrigger.valueEquals) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
!(elementWithId instanceof HTMLInputElement) &&
|
||||
!(elementWithId instanceof HTMLTextAreaElement)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const valuePropertyDescriptor = Object.getOwnPropertyDescriptor(
|
||||
elementWithId.constructor.prototype,
|
||||
'value'
|
||||
);
|
||||
if (!valuePropertyDescriptor) return undefined;
|
||||
const valueSetter = valuePropertyDescriptor.set;
|
||||
if (!valueSetter) return undefined;
|
||||
return () => {
|
||||
valueSetter.call(elementWithId, nextStepTrigger.valueEquals);
|
||||
// Trigger blur to make sure the value is taken into account
|
||||
// by the React input.
|
||||
elementWithId.dispatchEvent(new Event('blur', { bubbles: true }));
|
||||
};
|
||||
},
|
||||
[nextStepTrigger, elementWithId]
|
||||
);
|
||||
|
||||
const renderTooltip = (i18n: I18nType) => {
|
||||
if (tooltip && !expectedEditor) {
|
||||
const anchorElement = tooltip.standalone
|
||||
@@ -281,6 +321,7 @@ function InAppTutorialStepDisplayer({
|
||||
? nextStepTrigger.clickOnTooltipButton
|
||||
: undefined
|
||||
}
|
||||
fillAutomatically={getFillAutomaticallyFunction()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -290,7 +331,7 @@ function InAppTutorialStepDisplayer({
|
||||
return (
|
||||
<InAppTutorialTooltipDisplayer
|
||||
endTutorial={endTutorial}
|
||||
showQuitButton={!isOnClosableDialog}
|
||||
showQuitButton // Always show the quit button when the user is on the wrong editor
|
||||
anchorElement={assistantImage}
|
||||
tooltip={wrongEditorTooltip}
|
||||
progress={progress}
|
||||
|
@@ -21,6 +21,7 @@ import { LineStackLayout } from '../UI/Layout';
|
||||
import ChevronArrowTop from '../UI/CustomSvgIcons/ChevronArrowTop';
|
||||
import { textEllipsisStyle } from '../UI/TextEllipsis';
|
||||
import { useResponsiveWindowSize } from '../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import TextButton from '../UI/TextButton';
|
||||
|
||||
const themeColors = {
|
||||
grey10: '#EBEBED',
|
||||
@@ -129,12 +130,14 @@ type TooltipBodyProps = {|
|
||||
tooltip: InAppTutorialFormattedTooltip,
|
||||
buttonLabel?: string,
|
||||
goToNextStep: () => void,
|
||||
fillAutomatically?: () => void,
|
||||
|};
|
||||
|
||||
const TooltipBody = ({
|
||||
tooltip,
|
||||
buttonLabel,
|
||||
goToNextStep,
|
||||
fillAutomatically,
|
||||
}: TooltipBodyProps) => {
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const titleAndDescription = (
|
||||
@@ -182,10 +185,18 @@ const TooltipBody = ({
|
||||
</Column>
|
||||
);
|
||||
|
||||
const fillAutomaticallyButton = fillAutomatically && (
|
||||
<TextButton
|
||||
onClick={fillAutomatically}
|
||||
label={<Trans>Fill automatically</Trans>}
|
||||
primary
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Column noMargin>
|
||||
{titleAndDescription}
|
||||
{imageAndButton}
|
||||
{fillAutomaticallyButton}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -287,6 +298,7 @@ type Props = {|
|
||||
progress: number,
|
||||
endTutorial: () => void,
|
||||
goToNextStep: () => void,
|
||||
fillAutomatically?: () => void,
|
||||
|};
|
||||
|
||||
const InAppTutorialTooltipDisplayer = ({
|
||||
@@ -297,6 +309,7 @@ const InAppTutorialTooltipDisplayer = ({
|
||||
progress,
|
||||
endTutorial,
|
||||
goToNextStep,
|
||||
fillAutomatically,
|
||||
}: Props) => {
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const {
|
||||
@@ -388,6 +401,7 @@ const InAppTutorialTooltipDisplayer = ({
|
||||
tooltip={tooltip}
|
||||
buttonLabel={buttonLabel}
|
||||
goToNextStep={goToNextStep}
|
||||
fillAutomatically={fillAutomatically}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
|
@@ -500,7 +500,7 @@ const BuildSection = ({
|
||||
</Text>
|
||||
<Spacer />
|
||||
<TextButton
|
||||
primary
|
||||
secondary
|
||||
label={
|
||||
isMobile ? (
|
||||
<Trans>Open</Trans>
|
||||
|
@@ -99,85 +99,63 @@ const GuidedLessons = ({ selectInAppTutorial, lessonsIds }: Props) => {
|
||||
return tutorialProgress.progress[0]; // guided lessons only have one part.
|
||||
};
|
||||
|
||||
const lessonsCompleted = guidedLessonsIds.reduce((acc, tutorialId) => {
|
||||
const tutorialProgress = getTutorialPartProgress({ tutorialId }) || 0;
|
||||
return tutorialProgress === 100 ? acc + 1 : acc;
|
||||
}, 0);
|
||||
const displayedGuidedLessonsIds = lessonsIds || guidedLessonsIds;
|
||||
|
||||
const lessonsCompleted = displayedGuidedLessonsIds.reduce(
|
||||
(acc, tutorialId) => {
|
||||
const tutorialProgress = getTutorialPartProgress({ tutorialId }) || 0;
|
||||
return tutorialProgress === 100 ? acc + 1 : acc;
|
||||
},
|
||||
0
|
||||
);
|
||||
const lessonsProgress = Math.round(
|
||||
(lessonsCompleted / guidedLessonsIds.length) * 100
|
||||
(lessonsCompleted / displayedGuidedLessonsIds.length) * 100
|
||||
);
|
||||
|
||||
const guidedLessonCards = [
|
||||
{
|
||||
id: JOYSTICK_IN_APP_TUTORIAL_ID,
|
||||
title: t`Add Joystick controls`,
|
||||
title: t`Joystick controls`,
|
||||
description: t`Learn how to add a joystick to control the player.`,
|
||||
keyPoints: [
|
||||
t`Add a layer`,
|
||||
t`Download and use a prefab`,
|
||||
t`Use a behavior`,
|
||||
],
|
||||
durationInMinutes: 1,
|
||||
renderImage: props => <Joystick {...props} />,
|
||||
},
|
||||
{
|
||||
id: HEALTH_BAR_IN_APP_TUTORIAL_ID,
|
||||
title: t`Display a Health bar for the player`,
|
||||
title: t`Health bar`,
|
||||
description: t`Learn how to display the health of a player on the foreground.`,
|
||||
keyPoints: [t`Add a layer`, t`Download and use a prefab`],
|
||||
durationInMinutes: 2,
|
||||
renderImage: props => <HealthBar {...props} />,
|
||||
},
|
||||
{
|
||||
id: OBJECT_3D_IN_APP_TUTORIAL_ID,
|
||||
title: t`Add a 3D object`,
|
||||
description: t`Learn how to add a 3D object to your game.`,
|
||||
keyPoints: [
|
||||
t`Add a 3D box`,
|
||||
t`Add a behavior`,
|
||||
t`Update the elevation of a 3D box`,
|
||||
],
|
||||
title: t`3D box`,
|
||||
description: t`Learn how to add a 3D box to your game.`,
|
||||
durationInMinutes: 2,
|
||||
renderImage: props => <Object3D {...props} />,
|
||||
},
|
||||
{
|
||||
id: CAMERA_PARALLAX_IN_APP_TUTORIAL_ID,
|
||||
title: t`Improve background and camera`,
|
||||
title: t`Background`,
|
||||
description: t`Learn how to create a parallax background as well as a camera that follows the player.`,
|
||||
keyPoints: [
|
||||
t`Add an extension`,
|
||||
t`Add a layer`,
|
||||
t`Use a tiled sprite`,
|
||||
t`Control the camera`,
|
||||
],
|
||||
durationInMinutes: 2,
|
||||
renderImage: props => <Parallax {...props} />,
|
||||
},
|
||||
{
|
||||
id: TIMER_IN_APP_TUTORIAL_ID,
|
||||
title: t`Use a timer`,
|
||||
title: t`Timer`,
|
||||
description: t`Learn how to use a timer to count a score.`,
|
||||
keyPoints: [
|
||||
t`Create and use a timer`,
|
||||
t`Create and modify a text`,
|
||||
t`Build an expression`,
|
||||
],
|
||||
durationInMinutes: 2,
|
||||
renderImage: props => <Timer {...props} />,
|
||||
},
|
||||
{
|
||||
id: PLINKO_MULTIPLIER_IN_APP_TUTORIAL_ID,
|
||||
title: t`Add score multiplier`,
|
||||
title: t`Score multiplier`,
|
||||
description: t`Learn how to manipulate a score by adding collectibles.`,
|
||||
keyPoints: [
|
||||
t`Create a variable`,
|
||||
t`Use & manipulate a variable`,
|
||||
t`Build an expression`,
|
||||
],
|
||||
durationInMinutes: 3,
|
||||
renderImage: props => <MultiplierScore {...props} />,
|
||||
},
|
||||
].filter(item => (lessonsIds ? lessonsIds.includes(item.id) : true));
|
||||
].filter(item => displayedGuidedLessonsIds.includes(item.id));
|
||||
|
||||
return (
|
||||
<Line>
|
||||
@@ -191,20 +169,18 @@ const GuidedLessons = ({ selectInAppTutorial, lessonsIds }: Props) => {
|
||||
</PlaceholderError>
|
||||
) : (
|
||||
<ColumnStackLayout noMargin>
|
||||
{!lessonsIds && (
|
||||
<Column>
|
||||
<LineStackLayout alignItems="center">
|
||||
{lessonsProgress !== 100 ? (
|
||||
<Text displayInlineAsSpan noMargin size="body2">
|
||||
{lessonsProgress}%
|
||||
</Text>
|
||||
) : (
|
||||
<Trophy />
|
||||
)}
|
||||
<ColoredLinearProgress value={lessonsProgress} />
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
)}
|
||||
<Column>
|
||||
<LineStackLayout alignItems="center">
|
||||
{lessonsProgress !== 100 ? (
|
||||
<Text displayInlineAsSpan noMargin size="body2">
|
||||
{lessonsProgress}%
|
||||
</Text>
|
||||
) : (
|
||||
<Trophy />
|
||||
)}
|
||||
<ColoredLinearProgress value={lessonsProgress} />
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
<GridList
|
||||
cols={getColumnsFromWindowSize(windowSize, isLandscape)}
|
||||
style={styles.grid}
|
||||
@@ -217,7 +193,6 @@ const GuidedLessons = ({ selectInAppTutorial, lessonsIds }: Props) => {
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
durationInMinutes={item.durationInMinutes}
|
||||
keyPoints={item.keyPoints}
|
||||
renderImage={item.renderImage}
|
||||
progress={getTutorialPartProgress({ tutorialId: item.id })}
|
||||
onClick={() => selectInAppTutorial(item.id)}
|
||||
|
@@ -4,10 +4,8 @@ import { Trans } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import Dialog, { DialogPrimaryButton } from '../../../../UI/Dialog';
|
||||
import FlatButton from '../../../../UI/FlatButton';
|
||||
import { Line, Column } from '../../../../UI/Grid';
|
||||
import { Line } from '../../../../UI/Grid';
|
||||
import { ColumnStackLayout } from '../../../../UI/Layout';
|
||||
import InAppTutorialContext from '../../../../InAppTutorial/InAppTutorialContext';
|
||||
import { getLanguageLabelForLocale } from '../../../../Utils/i18n/MessageByLocale';
|
||||
import Text from '../../../../UI/Text';
|
||||
import {
|
||||
FLING_GAME_IN_APP_TUTORIAL_ID,
|
||||
@@ -26,15 +24,6 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
open: boolean,
|
||||
tutorialId: string,
|
||||
onClose: () => void,
|
||||
tutorialCompletionStatus: 'notStarted' | 'started' | 'complete',
|
||||
isProjectOpened?: boolean,
|
||||
startTutorial: (scenario: 'resume' | 'startOver' | 'start') => Promise<void>,
|
||||
|};
|
||||
|
||||
const getGuidedLessonContent = ({
|
||||
learningKeys,
|
||||
}: {
|
||||
@@ -44,12 +33,6 @@ const getGuidedLessonContent = ({
|
||||
<Text>
|
||||
<Trans>You're about to start this guided lesson.</Trans>
|
||||
</Text>
|
||||
<Text>
|
||||
<Trans>
|
||||
A new project will be opened, so before beginning please ensure you have
|
||||
closed and saved your current project.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text>
|
||||
<Trans>In this tutorial you will learn:</Trans>
|
||||
</Text>
|
||||
@@ -105,7 +88,6 @@ const titleAndContentByKey = {
|
||||
content: getGuidedLessonContent({
|
||||
learningKeys: [
|
||||
<Trans>Add a background with parallax effect</Trans>,
|
||||
<Trans>Add a new layer</Trans>,
|
||||
<Trans>Add an extension</Trans>,
|
||||
<Trans>Use basic camera movements to follow the player</Trans>,
|
||||
],
|
||||
@@ -117,8 +99,8 @@ const titleAndContentByKey = {
|
||||
),
|
||||
content: getGuidedLessonContent({
|
||||
learningKeys: [
|
||||
<Trans>Add a new layer</Trans>,
|
||||
<Trans>Use a prefab to display the player's health bar</Trans>,
|
||||
<Trans>Use a prefab for a health bar</Trans>,
|
||||
<Trans>Update the health bar based on the player's health</Trans>,
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -126,7 +108,6 @@ const titleAndContentByKey = {
|
||||
title: <Trans>Let's add mobile controls to our game</Trans>,
|
||||
content: getGuidedLessonContent({
|
||||
learningKeys: [
|
||||
<Trans>Add a new layer</Trans>,
|
||||
<Trans>Add a joystick prefab</Trans>,
|
||||
<Trans>Add a behavior</Trans>,
|
||||
],
|
||||
@@ -138,12 +119,21 @@ const titleAndContentByKey = {
|
||||
learningKeys: [
|
||||
<Trans>Add a 3D Box</Trans>,
|
||||
<Trans>Add a behavior</Trans>,
|
||||
<Trans>Update the elevation of a 3D box</Trans>,
|
||||
],
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
open: boolean,
|
||||
tutorialId: string,
|
||||
onClose: () => void,
|
||||
tutorialCompletionStatus: 'notStarted' | 'started' | 'complete',
|
||||
isProjectOpened?: boolean,
|
||||
isProjectOpening: boolean,
|
||||
startTutorial: (scenario: 'resume' | 'startOver' | 'start') => Promise<void>,
|
||||
|};
|
||||
|
||||
const StartInAppTutorialDialog = ({
|
||||
open,
|
||||
tutorialId,
|
||||
@@ -151,33 +141,16 @@ const StartInAppTutorialDialog = ({
|
||||
tutorialCompletionStatus,
|
||||
isProjectOpened,
|
||||
startTutorial,
|
||||
isProjectOpening,
|
||||
}: Props) => {
|
||||
const resumeTutorial = () => startTutorial('resume');
|
||||
const startOverTutorial = () => startTutorial('startOver');
|
||||
const startTutorialForFirstTime = () => startTutorial('start');
|
||||
|
||||
const { getInAppTutorialShortHeader } = React.useContext(
|
||||
InAppTutorialContext
|
||||
);
|
||||
|
||||
const selectedInAppTutorialShortHeader = getInAppTutorialShortHeader(
|
||||
tutorialId
|
||||
);
|
||||
|
||||
const availableLocales = selectedInAppTutorialShortHeader
|
||||
? selectedInAppTutorialShortHeader.availableLocales
|
||||
: null;
|
||||
|
||||
const dialogContentByCompletionStatus = {
|
||||
notStarted: {
|
||||
title: titleAndContentByKey[tutorialId].title,
|
||||
content: titleAndContentByKey[tutorialId].content,
|
||||
availableLocalesLabels: availableLocales
|
||||
? availableLocales.map(locale => [
|
||||
locale,
|
||||
getLanguageLabelForLocale(locale),
|
||||
])
|
||||
: null,
|
||||
primaryAction: {
|
||||
label: <Trans>Yes</Trans>,
|
||||
onClick: startTutorialForFirstTime,
|
||||
@@ -248,12 +221,14 @@ const StartInAppTutorialDialog = ({
|
||||
key="close"
|
||||
label={secondaryAction.label}
|
||||
onClick={secondaryAction.onClick}
|
||||
disabled={isProjectOpening}
|
||||
/>,
|
||||
<DialogPrimaryButton
|
||||
key="start"
|
||||
label={primaryAction.label}
|
||||
primary
|
||||
onClick={primaryAction.onClick}
|
||||
disabled={isProjectOpening}
|
||||
/>,
|
||||
];
|
||||
const secondaryActions = tertiaryAction
|
||||
@@ -262,6 +237,7 @@ const StartInAppTutorialDialog = ({
|
||||
key="other"
|
||||
label={tertiaryAction.label}
|
||||
onClick={tertiaryAction.onClick}
|
||||
disabled={isProjectOpening}
|
||||
/>,
|
||||
]
|
||||
: undefined;
|
||||
@@ -284,20 +260,6 @@ const StartInAppTutorialDialog = ({
|
||||
</div>
|
||||
</Line>
|
||||
{content}
|
||||
{dialogContent.availableLocalesLabels ? (
|
||||
<Column noMargin>
|
||||
<Text>
|
||||
<Trans>
|
||||
This tutorial is available in the following languages:
|
||||
</Trans>
|
||||
</Text>
|
||||
{dialogContent.availableLocalesLabels.map(([locale, label]) => (
|
||||
<Text displayAsListItem noMargin key={locale}>
|
||||
{label}
|
||||
</Text>
|
||||
))}
|
||||
</Column>
|
||||
) : null}
|
||||
<Text>
|
||||
<Trans>Are you ready?</Trans>
|
||||
</Text>
|
||||
|
@@ -3480,6 +3480,7 @@ const MainFrame = (props: Props) => {
|
||||
onClose={() => {
|
||||
setSelectedInAppTutorialInfo(null);
|
||||
}}
|
||||
isProjectOpening={isProjectOpening}
|
||||
/>
|
||||
)}
|
||||
{state.gdjsDevelopmentWatcherEnabled &&
|
||||
|
@@ -269,7 +269,6 @@ const EditProfileDialog = ({
|
||||
label={<Trans>Delete my account</Trans>}
|
||||
disabled={actionInProgress}
|
||||
key="delete"
|
||||
primary={false}
|
||||
onClick={onDeleteAccount}
|
||||
/>,
|
||||
];
|
||||
|
@@ -78,6 +78,12 @@ export class ExtensionTreeViewItemContent implements TreeViewItemContent {
|
||||
return `extension-item-${index}`;
|
||||
}
|
||||
|
||||
getDataSet(): { [string]: string } {
|
||||
return {
|
||||
extension: this.eventsFunctionsExtension.getName(),
|
||||
};
|
||||
}
|
||||
|
||||
getThumbnail(): ?string {
|
||||
return (
|
||||
this.eventsFunctionsExtension.getIconUrl() ||
|
||||
|
@@ -73,6 +73,12 @@ export class ExternalEventsTreeViewItemContent implements TreeViewItemContent {
|
||||
return `external-events-item-${index}`;
|
||||
}
|
||||
|
||||
getDataSet(): { [string]: string } {
|
||||
return {
|
||||
'external-events': this.externalEvents.getName(),
|
||||
};
|
||||
}
|
||||
|
||||
getThumbnail(): ?string {
|
||||
return 'res/icons_default/external_events_black.svg';
|
||||
}
|
||||
|
@@ -73,6 +73,12 @@ export class ExternalLayoutTreeViewItemContent implements TreeViewItemContent {
|
||||
return `external-layout-item-${index}`;
|
||||
}
|
||||
|
||||
getDataSet(): { [string]: string } {
|
||||
return {
|
||||
'external-layout': this.externalLayout.getName(),
|
||||
};
|
||||
}
|
||||
|
||||
getThumbnail(): ?string {
|
||||
return 'res/icons_default/external_layout_black.svg';
|
||||
}
|
||||
|
@@ -72,6 +72,12 @@ export class SceneTreeViewItemContent implements TreeViewItemContent {
|
||||
return `scene-item-${index}`;
|
||||
}
|
||||
|
||||
getDataSet(): { [string]: string } {
|
||||
return {
|
||||
scene: this.scene.getName(),
|
||||
};
|
||||
}
|
||||
|
||||
getThumbnail(): ?string {
|
||||
return 'res/icons_default/scene_black.svg';
|
||||
}
|
||||
|
@@ -116,6 +116,7 @@ export interface TreeViewItemContent {
|
||||
getName(): string | React.Node;
|
||||
getId(): string;
|
||||
getHtmlId(index: number): ?string;
|
||||
getDataSet(): { [string]: string };
|
||||
getThumbnail(): ?string;
|
||||
onClick(): void;
|
||||
buildMenuTemplate(i18n: I18nType, index: number): Array<MenuItemTemplate>;
|
||||
@@ -182,6 +183,7 @@ class PlaceHolderTreeViewItem implements TreeViewItem {
|
||||
class LabelTreeViewItemContent implements TreeViewItemContent {
|
||||
id: string;
|
||||
label: string | React.Node;
|
||||
dataSet: { [string]: string };
|
||||
buildMenuTemplateFunction: (
|
||||
i18n: I18nType,
|
||||
index: number
|
||||
@@ -199,6 +201,7 @@ class LabelTreeViewItemContent implements TreeViewItemContent {
|
||||
rightButton
|
||||
? [
|
||||
{
|
||||
id: rightButton.id,
|
||||
label: rightButton.label,
|
||||
click: rightButton.click,
|
||||
},
|
||||
@@ -223,6 +226,10 @@ class LabelTreeViewItemContent implements TreeViewItemContent {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getDataSet(): { [string]: string } {
|
||||
return {};
|
||||
}
|
||||
|
||||
getThumbnail(): ?string {
|
||||
return null;
|
||||
}
|
||||
@@ -307,6 +314,10 @@ class ActionTreeViewItemContent implements TreeViewItemContent {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getDataSet(): { [string]: string } {
|
||||
return {};
|
||||
}
|
||||
|
||||
getThumbnail(): ?string {
|
||||
return this.thumbnail;
|
||||
}
|
||||
@@ -358,7 +369,8 @@ const getTreeViewItemChildren = (i18n: I18nType) => (item: TreeViewItem) =>
|
||||
item.getChildren(i18n);
|
||||
const getTreeViewItemThumbnail = (item: TreeViewItem) =>
|
||||
item.content.getThumbnail();
|
||||
const getTreeViewItemData = (item: TreeViewItem) => null;
|
||||
const getTreeViewItemDataSet = (item: TreeViewItem) =>
|
||||
item.content.getDataSet();
|
||||
const buildMenuTemplate = (i18n: I18nType) => (
|
||||
item: TreeViewItem,
|
||||
index: number
|
||||
@@ -1016,6 +1028,7 @@ const ProjectManager = React.forwardRef<Props, ProjectManagerInterface>(
|
||||
const index = project.getLayoutsCount() - 1;
|
||||
addNewScene(index, i18n);
|
||||
},
|
||||
id: 'add-new-scene-button',
|
||||
}
|
||||
),
|
||||
getChildren(i18n: I18nType): ?Array<TreeViewItem> {
|
||||
@@ -1049,6 +1062,7 @@ const ProjectManager = React.forwardRef<Props, ProjectManagerInterface>(
|
||||
icon: <Add />,
|
||||
label: i18n._(t`Create or search for new extensions`),
|
||||
click: openSearchExtensionDialog,
|
||||
id: 'project-manager-extension-search-or-create',
|
||||
}
|
||||
),
|
||||
getChildren(i18n: I18nType): ?Array<TreeViewItem> {
|
||||
@@ -1086,6 +1100,7 @@ const ProjectManager = React.forwardRef<Props, ProjectManagerInterface>(
|
||||
const index = project.getExternalEventsCount() - 1;
|
||||
addExternalEvents(index, i18n);
|
||||
},
|
||||
id: 'add-new-external-events-button',
|
||||
}
|
||||
),
|
||||
getChildren(i18n: I18nType): ?Array<TreeViewItem> {
|
||||
@@ -1123,6 +1138,7 @@ const ProjectManager = React.forwardRef<Props, ProjectManagerInterface>(
|
||||
const index = project.getExternalLayoutsCount() - 1;
|
||||
addExternalLayout(index, i18n);
|
||||
},
|
||||
id: 'add-new-external-layout-button',
|
||||
}
|
||||
),
|
||||
getChildren(i18n: I18nType): ?Array<TreeViewItem> {
|
||||
@@ -1275,7 +1291,7 @@ const ProjectManager = React.forwardRef<Props, ProjectManagerInterface>(
|
||||
multiSelect={false}
|
||||
getItemId={getTreeViewItemId}
|
||||
getItemHtmlId={getTreeViewItemHtmlId}
|
||||
getItemDataset={getTreeViewItemData}
|
||||
getItemDataset={getTreeViewItemDataSet}
|
||||
onEditItem={editItem}
|
||||
onCollapseItem={onCollapseItem}
|
||||
selectedItems={selectedItems}
|
||||
|
@@ -9,6 +9,7 @@ import { type Node as ReactNode } from 'react';
|
||||
export type MenuItemTemplate =
|
||||
// "Classic" menu item
|
||||
| {|
|
||||
id?: string,
|
||||
label: string,
|
||||
visible?: boolean,
|
||||
enabled?: boolean,
|
||||
@@ -18,11 +19,13 @@ export type MenuItemTemplate =
|
||||
|}
|
||||
// Sub menu
|
||||
| {|
|
||||
id?: string,
|
||||
label: string,
|
||||
submenu: Array<MenuItemTemplate>,
|
||||
|}
|
||||
// Checkbox
|
||||
| {|
|
||||
id?: string,
|
||||
type: 'checkbox',
|
||||
label: string,
|
||||
visible?: boolean,
|
||||
|
@@ -109,7 +109,7 @@ const NotificationList = ({
|
||||
</List>
|
||||
{shouldShowLoadMoreButton && (
|
||||
<TextButton
|
||||
primary
|
||||
secondary
|
||||
label={<Trans>Load more...</Trans>}
|
||||
onClick={() => setShowAll(true)}
|
||||
/>
|
||||
|
@@ -8,6 +8,7 @@ type Props = {|
|
||||
label: React.Node,
|
||||
onClick: ?(ev: any) => void | Promise<void>,
|
||||
primary?: boolean,
|
||||
secondary?: boolean,
|
||||
allowBrowserAutoTranslate?: boolean,
|
||||
disabled?: boolean,
|
||||
keyboardFocused?: boolean,
|
||||
@@ -33,6 +34,7 @@ const TextButton = React.forwardRef<Props, ButtonInterface>(
|
||||
{
|
||||
label,
|
||||
primary,
|
||||
secondary,
|
||||
icon,
|
||||
keyboardFocused,
|
||||
disabled,
|
||||
@@ -53,7 +55,7 @@ const TextButton = React.forwardRef<Props, ButtonInterface>(
|
||||
variant="text"
|
||||
size="small"
|
||||
translate={allowBrowserAutoTranslate ? undefined : 'no'}
|
||||
color={primary ? 'secondary' : 'default'}
|
||||
color={primary ? 'primary' : secondary ? 'secondary' : 'default'}
|
||||
autoFocus={keyboardFocused}
|
||||
focusRipple={focusRipple}
|
||||
disabled={disabled}
|
||||
|
@@ -418,6 +418,7 @@ const TreeViewRow = <Item: ItemBaseAttributes>(props: Props<Item>) => {
|
||||
{node.rightComponent}
|
||||
{rightButton && (
|
||||
<IconButton
|
||||
id={rightButton.id}
|
||||
size="small"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
|
@@ -27,6 +27,7 @@ export type ItemBaseAttributes = {
|
||||
};
|
||||
|
||||
export type MenuButton = {|
|
||||
id?: string,
|
||||
icon: React.Node,
|
||||
label: string,
|
||||
click: ?() => void | Promise<void>,
|
||||
|
@@ -73,8 +73,9 @@ export type InAppTutorialFlowStepTrigger =
|
||||
|
||||
export type InAppTutorialFlowStepFormattedTrigger =
|
||||
| InAppTutorialFlowStepDOMChangeTrigger
|
||||
| {| valueEquals: string |}
|
||||
| {| editorIsActive: string |}
|
||||
| {| valueHasChanged: true |}
|
||||
| {| valueEquals: string |}
|
||||
| {| instanceAddedOnScene: string, instancesCount?: number |}
|
||||
| {| objectAddedInLayout: true |}
|
||||
| {| previewLaunched: true |}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import { Trans } from '@lingui/macro';
|
||||
export type MessageByLocale = { [string]: string };
|
||||
|
||||
export const selectMessageByLocale = (
|
||||
@@ -27,45 +25,3 @@ export const selectMessageByLocale = (
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getLanguageLabelForLocale = (locale: string): React.Node => {
|
||||
const languageCode = locale.split(/[-_]/)[0];
|
||||
switch (languageCode) {
|
||||
case 'en':
|
||||
return <Trans>English</Trans>;
|
||||
case 'fr':
|
||||
return <Trans>French</Trans>;
|
||||
case 'pt':
|
||||
return <Trans>Portuguese</Trans>;
|
||||
case 'es':
|
||||
return <Trans>Spanish</Trans>;
|
||||
case 'it':
|
||||
return <Trans>Italian</Trans>;
|
||||
case 'ar':
|
||||
return <Trans>Arabic</Trans>;
|
||||
case 'id':
|
||||
return <Trans>Indonesian</Trans>;
|
||||
case 'ja':
|
||||
return <Trans>Japanese</Trans>;
|
||||
case 'ko':
|
||||
return <Trans>Korean</Trans>;
|
||||
case 'zh':
|
||||
return <Trans>Chinese</Trans>;
|
||||
case 'pl':
|
||||
return <Trans>Polish</Trans>;
|
||||
case 'ru':
|
||||
return <Trans>Russian</Trans>;
|
||||
case 'sl':
|
||||
return <Trans>Slovene</Trans>;
|
||||
case 'si':
|
||||
return <Trans>Sinhala</Trans>;
|
||||
case 'th':
|
||||
return <Trans>Thai</Trans>;
|
||||
case 'tr':
|
||||
return <Trans>Turkish</Trans>;
|
||||
case 'uk':
|
||||
return <Trans>Ukrainian</Trans>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
@@ -21,6 +21,20 @@ export const Default = () => {
|
||||
tutorialCompletionStatus={'notStarted'}
|
||||
startTutorial={action('Start tutorial')}
|
||||
onClose={() => action('On close dialog')()}
|
||||
isProjectOpening={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Opening = () => {
|
||||
return (
|
||||
<StartInAppTutorialDialog
|
||||
open
|
||||
tutorialId="flingGame"
|
||||
tutorialCompletionStatus={'notStarted'}
|
||||
startTutorial={action('Start tutorial')}
|
||||
onClose={() => action('On close dialog')()}
|
||||
isProjectOpening
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -33,6 +47,7 @@ export const WithTutorialAlreadyStarted = () => {
|
||||
tutorialCompletionStatus={'started'}
|
||||
startTutorial={action('Start tutorial')}
|
||||
onClose={() => action('On close dialog')()}
|
||||
isProjectOpening={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -44,6 +59,7 @@ export const WithTutorialCompleted = () => {
|
||||
tutorialCompletionStatus={'complete'}
|
||||
startTutorial={action('Start tutorial')}
|
||||
onClose={() => action('On close dialog')()}
|
||||
isProjectOpening={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@@ -107,6 +107,17 @@ export const Default = () => (
|
||||
label="Primary Text button"
|
||||
onClick={action('onClick')()}
|
||||
/>
|
||||
<TextButton
|
||||
secondary
|
||||
label="Secondary Text button"
|
||||
onClick={action('onClick')()}
|
||||
/>
|
||||
<TextButton
|
||||
icon={<Download />}
|
||||
secondary
|
||||
label="Secondary Text button"
|
||||
onClick={action('onClick')()}
|
||||
/>
|
||||
</LineStackLayout>
|
||||
<LineStackLayout noMargin>
|
||||
<Text size="block-title">Buttons with split menus:</Text>
|
||||
|
Reference in New Issue
Block a user