mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
refactor(onboarding): decompose fluent onboarding into progressive steps
Breaking down the Fluent UI onboarding process into smaller, focused steps for better user guidance and comprehension: - Split monolithic Fluent components step into 6 progressive steps - Add individual step components for each UI section (Overview, Workspace Selector, Main Navigation, Projects, Other Views, Top Navigation) - Enhance preview components with interactive selection and focus/dim modes - Improve visual hierarchy with targeted highlighting for each step - Add keyboard support and accessibility attributes to preview components - Fix transition timing to show config check only at appropriate steps This change provides a more guided and digestible onboarding experience by presenting UI concepts progressively rather than all at once.
This commit is contained in:
parent
962623b3e8
commit
1814877969
15 changed files with 498 additions and 168 deletions
BIN
media/fluent-layout.png
Normal file
BIN
media/fluent-layout.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
|
|
@ -3,14 +3,20 @@ import { OnboardingConfig, OnboardingConfigMode } from "@/managers/onboarding-ma
|
|||
export enum OnboardingStep {
|
||||
INTRO = 0,
|
||||
MODE_SELECT = 1,
|
||||
FLUENT_PLACEMENT = 2,
|
||||
FLUENT_COMPONENTS = 3,
|
||||
SETTINGS_CHECK = 4,
|
||||
USER_LEVEL_SELECT = 5,
|
||||
FILE_FILTER = 6,
|
||||
CONFIG_PREVIEW = 7,
|
||||
TASK_CREATION_GUIDE = 8,
|
||||
COMPLETE = 9,
|
||||
// Fluent progressive steps
|
||||
FLUENT_OVERVIEW = 2,
|
||||
FLUENT_WS_SELECTOR = 3,
|
||||
FLUENT_MAIN_NAV = 4,
|
||||
FLUENT_PROJECTS = 5,
|
||||
FLUENT_OTHER_VIEWS = 6,
|
||||
FLUENT_TOPNAV = 7,
|
||||
// Config & rest
|
||||
SETTINGS_CHECK = 8,
|
||||
USER_LEVEL_SELECT = 9,
|
||||
FILE_FILTER = 10,
|
||||
CONFIG_PREVIEW = 11,
|
||||
TASK_CREATION_GUIDE = 12,
|
||||
COMPLETE = 13,
|
||||
}
|
||||
|
||||
export interface OnboardingState {
|
||||
|
|
@ -132,47 +138,37 @@ export class OnboardingController {
|
|||
// Determine next step based on current step and state
|
||||
switch (currentStep) {
|
||||
case OnboardingStep.INTRO:
|
||||
// Mode selection is now inline in INTRO step
|
||||
// So we skip MODE_SELECT and go directly based on selected mode
|
||||
if (this.state.uiMode === 'fluent') {
|
||||
nextStep = OnboardingStep.FLUENT_PLACEMENT;
|
||||
} else {
|
||||
// Legacy mode: check for existing changes
|
||||
if (this.state.userHasChanges) {
|
||||
nextStep = OnboardingStep.SETTINGS_CHECK;
|
||||
} else {
|
||||
nextStep = OnboardingStep.USER_LEVEL_SELECT;
|
||||
}
|
||||
}
|
||||
// Always go to mode selection
|
||||
nextStep = OnboardingStep.MODE_SELECT;
|
||||
break;
|
||||
|
||||
case OnboardingStep.MODE_SELECT:
|
||||
// This step is now integrated into INTRO, but keep for backward compatibility
|
||||
if (this.state.uiMode === 'fluent') {
|
||||
nextStep = OnboardingStep.FLUENT_PLACEMENT;
|
||||
nextStep = OnboardingStep.FLUENT_OVERVIEW;
|
||||
} else {
|
||||
// Legacy mode: check for existing changes
|
||||
if (this.state.userHasChanges) {
|
||||
nextStep = OnboardingStep.SETTINGS_CHECK;
|
||||
} else {
|
||||
nextStep = OnboardingStep.USER_LEVEL_SELECT;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OnboardingStep.FLUENT_PLACEMENT:
|
||||
nextStep = OnboardingStep.FLUENT_COMPONENTS;
|
||||
break;
|
||||
|
||||
case OnboardingStep.FLUENT_COMPONENTS:
|
||||
// Check if user has changes
|
||||
if (this.state.userHasChanges) {
|
||||
nextStep = OnboardingStep.SETTINGS_CHECK;
|
||||
} else {
|
||||
nextStep = OnboardingStep.USER_LEVEL_SELECT;
|
||||
}
|
||||
break;
|
||||
|
||||
case OnboardingStep.FLUENT_OVERVIEW:
|
||||
nextStep = OnboardingStep.FLUENT_WS_SELECTOR;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_WS_SELECTOR:
|
||||
nextStep = OnboardingStep.FLUENT_MAIN_NAV;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_MAIN_NAV:
|
||||
nextStep = OnboardingStep.FLUENT_PROJECTS;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_PROJECTS:
|
||||
nextStep = OnboardingStep.FLUENT_OTHER_VIEWS;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_OTHER_VIEWS:
|
||||
nextStep = OnboardingStep.FLUENT_TOPNAV;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_TOPNAV:
|
||||
nextStep = OnboardingStep.SETTINGS_CHECK;
|
||||
break;
|
||||
|
||||
case OnboardingStep.SETTINGS_CHECK:
|
||||
// User decided to continue wizard
|
||||
if (this.state.settingsCheckAction === 'wizard') {
|
||||
|
|
@ -238,18 +234,29 @@ export class OnboardingController {
|
|||
prevStep = OnboardingStep.INTRO;
|
||||
break;
|
||||
|
||||
case OnboardingStep.FLUENT_PLACEMENT:
|
||||
case OnboardingStep.FLUENT_OVERVIEW:
|
||||
prevStep = OnboardingStep.MODE_SELECT;
|
||||
break;
|
||||
|
||||
case OnboardingStep.FLUENT_COMPONENTS:
|
||||
prevStep = OnboardingStep.MODE_SELECT;
|
||||
case OnboardingStep.FLUENT_WS_SELECTOR:
|
||||
prevStep = OnboardingStep.FLUENT_OVERVIEW;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_MAIN_NAV:
|
||||
prevStep = OnboardingStep.FLUENT_WS_SELECTOR;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_PROJECTS:
|
||||
prevStep = OnboardingStep.FLUENT_MAIN_NAV;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_OTHER_VIEWS:
|
||||
prevStep = OnboardingStep.FLUENT_PROJECTS;
|
||||
break;
|
||||
case OnboardingStep.FLUENT_TOPNAV:
|
||||
prevStep = OnboardingStep.FLUENT_OTHER_VIEWS;
|
||||
break;
|
||||
|
||||
case OnboardingStep.SETTINGS_CHECK:
|
||||
// Go back to components or mode select based on UI mode
|
||||
// Go back to last fluent step or mode select based on UI mode
|
||||
if (this.state.uiMode === 'fluent') {
|
||||
prevStep = OnboardingStep.FLUENT_COMPONENTS;
|
||||
prevStep = OnboardingStep.FLUENT_TOPNAV;
|
||||
} else {
|
||||
prevStep = OnboardingStep.MODE_SELECT;
|
||||
}
|
||||
|
|
@ -260,7 +267,7 @@ export class OnboardingController {
|
|||
if (this.state.userHasChanges && this.state.settingsCheckAction === 'wizard') {
|
||||
prevStep = OnboardingStep.SETTINGS_CHECK;
|
||||
} else if (this.state.uiMode === 'fluent') {
|
||||
prevStep = OnboardingStep.FLUENT_COMPONENTS;
|
||||
prevStep = OnboardingStep.FLUENT_TOPNAV;
|
||||
} else {
|
||||
prevStep = OnboardingStep.MODE_SELECT;
|
||||
}
|
||||
|
|
@ -333,8 +340,12 @@ export class OnboardingController {
|
|||
// Must have a config selected
|
||||
return !!this.state.selectedConfig;
|
||||
|
||||
case OnboardingStep.FLUENT_PLACEMENT:
|
||||
case OnboardingStep.FLUENT_COMPONENTS:
|
||||
case OnboardingStep.FLUENT_OVERVIEW:
|
||||
case OnboardingStep.FLUENT_WS_SELECTOR:
|
||||
case OnboardingStep.FLUENT_MAIN_NAV:
|
||||
case OnboardingStep.FLUENT_PROJECTS:
|
||||
case OnboardingStep.FLUENT_OTHER_VIEWS:
|
||||
case OnboardingStep.FLUENT_TOPNAV:
|
||||
case OnboardingStep.FILE_FILTER:
|
||||
case OnboardingStep.CONFIG_PREVIEW:
|
||||
case OnboardingStep.TASK_CREATION_GUIDE:
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ import { OnboardingLayout } from "./OnboardingLayout";
|
|||
// Import step components
|
||||
import { IntroStep } from "./steps/IntroStep";
|
||||
import { ModeSelectionStep } from "./steps/ModeSelectionStep";
|
||||
import { PlacementStep } from "./steps/PlacementStep";
|
||||
import { FluentComponentsStep } from "./steps/FluentComponentsStep";
|
||||
import { FluentOverviewStep } from "./steps/FluentOverviewStep";
|
||||
import { FluentWorkspaceSelectorStep } from "./steps/FluentWorkspaceSelectorStep";
|
||||
import { FluentMainNavigationStep } from "./steps/FluentMainNavigationStep";
|
||||
import { FluentProjectSectionStep } from "./steps/FluentProjectSectionStep";
|
||||
import { FluentOtherViewsStep } from "./steps/FluentOtherViewsStep";
|
||||
import { FluentTopNavigationStep } from "./steps/FluentTopNavigationStep";
|
||||
import { UserLevelStep } from "./steps/UserLevelStep";
|
||||
import { FileFilterStep } from "./steps/FileFilterStep";
|
||||
import { ConfigPreviewStep } from "./steps/ConfigPreviewStep";
|
||||
|
|
@ -147,12 +151,23 @@ export class OnboardingView extends ItemView {
|
|||
ModeSelectionStep.render(headerEl, contentEl, this.controller);
|
||||
break;
|
||||
|
||||
case OnboardingStep.FLUENT_PLACEMENT:
|
||||
PlacementStep.render(headerEl, contentEl, this.controller);
|
||||
case OnboardingStep.FLUENT_OVERVIEW:
|
||||
FluentOverviewStep.render(headerEl, contentEl, this.controller);
|
||||
break;
|
||||
|
||||
case OnboardingStep.FLUENT_COMPONENTS:
|
||||
FluentComponentsStep.render(headerEl, contentEl, this.controller);
|
||||
case OnboardingStep.FLUENT_WS_SELECTOR:
|
||||
FluentWorkspaceSelectorStep.render(headerEl, contentEl, this.controller);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_MAIN_NAV:
|
||||
FluentMainNavigationStep.render(headerEl, contentEl, this.controller);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_PROJECTS:
|
||||
FluentProjectSectionStep.render(headerEl, contentEl, this.controller);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_OTHER_VIEWS:
|
||||
FluentOtherViewsStep.render(headerEl, contentEl, this.controller);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_TOPNAV:
|
||||
FluentTopNavigationStep.render(headerEl, contentEl, this.controller);
|
||||
break;
|
||||
|
||||
case OnboardingStep.SETTINGS_CHECK:
|
||||
|
|
@ -217,11 +232,20 @@ export class OnboardingView extends ItemView {
|
|||
console.log("handleNext - UI Mode:", state.uiMode);
|
||||
console.log("handleNext - User has changes:", state.userHasChanges);
|
||||
|
||||
// Special handling for INTRO step - show config check transition
|
||||
if (step === OnboardingStep.INTRO && state.uiMode !== "fluent") {
|
||||
// If user has changes, show checking animation before settings check
|
||||
// Show config check transition only when entering Settings Check from:
|
||||
// - Mode Select with Legacy
|
||||
// - The last Fluent step (Top Navigation)
|
||||
if (step === OnboardingStep.MODE_SELECT) {
|
||||
if (state.uiMode === "legacy" && state.userHasChanges) {
|
||||
// Clear header before showing transition to avoid residual UI
|
||||
this.layout.clearHeader();
|
||||
await this.showConfigCheckTransition();
|
||||
}
|
||||
}
|
||||
if (step === OnboardingStep.FLUENT_TOPNAV) {
|
||||
if (state.userHasChanges) {
|
||||
console.log("Showing config check transition from INTRO");
|
||||
// Clear header before showing transition to avoid residual UI
|
||||
this.layout.clearHeader();
|
||||
await this.showConfigCheckTransition();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export class ComponentPreviewFactory {
|
|||
const content = sidebar.createDiv({ cls: "v2-sidebar-content" });
|
||||
|
||||
// Primary navigation section
|
||||
const primarySection = content.createDiv({ cls: "v2-sidebar-section" });
|
||||
const primarySection = content.createDiv({ cls: "v2-sidebar-section v2-sidebar-section-primary" });
|
||||
const primaryList = primarySection.createDiv({ cls: "v2-navigation-list" });
|
||||
|
||||
const primaryItems = [
|
||||
|
|
@ -55,7 +55,7 @@ export class ComponentPreviewFactory {
|
|||
primaryItems.forEach((item, index) => {
|
||||
const navItem = primaryList.createDiv({
|
||||
cls: "v2-navigation-item",
|
||||
attr: { "data-view-id": item.id }
|
||||
attr: { "data-view-id": item.id, tabindex: "0", role: "button" }
|
||||
});
|
||||
if (index === 0) navItem.addClass("is-active");
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ export class ComponentPreviewFactory {
|
|||
});
|
||||
|
||||
// Projects section
|
||||
const projectsSection = content.createDiv({ cls: "v2-sidebar-section" });
|
||||
const projectsSection = content.createDiv({ cls: "v2-sidebar-section v2-sidebar-section-projects" });
|
||||
const projectsHeader = projectsSection.createDiv({ cls: "v2-section-header" });
|
||||
projectsHeader.createSpan({ text: t("Projects") });
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ export class ComponentPreviewFactory {
|
|||
projects.forEach(project => {
|
||||
const projectItem = scrollArea.createDiv({
|
||||
cls: "v2-project-item",
|
||||
attr: { "data-project-id": project.id, "data-level": "0" }
|
||||
attr: { "data-project-id": project.id, "data-level": "0", tabindex: "0", role: "button" }
|
||||
});
|
||||
const colorDot = projectItem.createDiv({ cls: "v2-project-color" });
|
||||
colorDot.style.backgroundColor = project.color;
|
||||
|
|
@ -109,7 +109,7 @@ export class ComponentPreviewFactory {
|
|||
});
|
||||
|
||||
// Other views section
|
||||
const otherSection = content.createDiv({ cls: "v2-sidebar-section" });
|
||||
const otherSection = content.createDiv({ cls: "v2-sidebar-section v2-sidebar-section-other" });
|
||||
const otherHeader = otherSection.createDiv({ cls: "v2-section-header" });
|
||||
otherHeader.createSpan({ text: t("Other Views") });
|
||||
|
||||
|
|
@ -123,12 +123,36 @@ export class ComponentPreviewFactory {
|
|||
otherItems.forEach(item => {
|
||||
const navItem = otherList.createDiv({
|
||||
cls: "v2-navigation-item",
|
||||
attr: { "data-view-id": item.id }
|
||||
attr: { "data-view-id": item.id, tabindex: "0", role: "button" }
|
||||
});
|
||||
const icon = navItem.createDiv({ cls: "v2-navigation-icon" });
|
||||
setIcon(icon, item.icon);
|
||||
navItem.createSpan({ text: item.label, cls: "v2-navigation-label" });
|
||||
});
|
||||
|
||||
// Interactive preview: simple selection toggle (visual only)
|
||||
const root = container.querySelector(".v2-sidebar")!;
|
||||
const handleActivate = (target: HTMLElement) => {
|
||||
const item = target.closest<HTMLElement>(".v2-navigation-item, .v2-project-item");
|
||||
if (!item) return;
|
||||
const parentList = item.parentElement;
|
||||
if (!parentList) return;
|
||||
// Clear previous active in the same group
|
||||
Array.from(parentList.children).forEach((el) => el.classList.remove("is-active"));
|
||||
item.classList.add("is-active");
|
||||
};
|
||||
|
||||
root.addEventListener("click", (e) => {
|
||||
handleActivate(e.target as HTMLElement);
|
||||
});
|
||||
root.addEventListener("keydown", (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
const target = e.target as HTMLElement;
|
||||
handleActivate(target);
|
||||
// Prevent page scroll on Space
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import { t } from "@/translations/helper";
|
||||
import { OnboardingController } from "../OnboardingController";
|
||||
import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
||||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentMainNavigationStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Main Navigation") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Access Inbox, Today, Upcoming and more from the primary section"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview focus-mode" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
|
||||
const primary = preview.querySelector<HTMLElement>(".v2-sidebar-section-primary");
|
||||
primary?.classList.add("is-focused");
|
||||
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-projects, .v2-sidebar-section-other"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
|
||||
desc.createEl("h3", { text: t("Navigate core views") });
|
||||
desc.createEl("p", {
|
||||
text: t("Quickly jump to core views like Inbox, Today, Upcoming and Flagged."),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Unread counts and indicators keep you informed"),
|
||||
t("Keyboard-ready with clear selection states"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { t } from "@/translations/helper";
|
||||
import { OnboardingController } from "../OnboardingController";
|
||||
import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
||||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentOtherViewsStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Other Views") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Access Calendar, Gantt and Tags from the other views section"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview focus-mode" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
|
||||
const other = preview.querySelector<HTMLElement>(".v2-sidebar-section-other");
|
||||
other?.classList.add("is-focused");
|
||||
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-primary, .v2-sidebar-section-projects"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
|
||||
desc.createEl("h3", { text: t("Specialized views") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Quickly reach Calendar scheduling, Gantt timelines and tag-based filtering."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Compact list with clear icons"),
|
||||
t("Consistent selection feedback"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { t } from "@/translations/helper";
|
||||
import { OnboardingController } from "../OnboardingController";
|
||||
import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
||||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentOverviewStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Discover Fluent Interface") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("A quick overview of the Fluent layout and its key areas"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
|
||||
// Build a compact overview: Sidebar + Top Navigation
|
||||
const sidebarWrap = preview.createDiv();
|
||||
ComponentPreviewFactory.createSidebarPreview(sidebarWrap);
|
||||
|
||||
const topNavWrap = preview.createDiv();
|
||||
(topNavWrap as HTMLElement).style.marginTop = "16px";
|
||||
ComponentPreviewFactory.createTopNavigationPreview(topNavWrap);
|
||||
|
||||
desc.createEl("h3", { text: t("Fluent Layout Overview") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Fluent groups navigation on the left and global tools at the top, keeping your content area clean and focused."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Sidebar for switching views and managing projects"),
|
||||
t("Top navigation for search, view modes and quick settings"),
|
||||
t("Consistent styling with clear hierarchy and feedback"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { t } from "@/translations/helper";
|
||||
import { OnboardingController } from "../OnboardingController";
|
||||
import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
||||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentProjectSectionStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Projects Section") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Organize your work with projects and hierarchies"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview focus-mode" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
|
||||
const projects = preview.querySelector<HTMLElement>(".v2-sidebar-section-projects");
|
||||
projects?.classList.add("is-focused");
|
||||
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-primary, .v2-sidebar-section-other"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
|
||||
desc.createEl("h3", { text: t("Project organization") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Group related tasks under projects. Build nested hierarchies and get quick stats."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Color-coded projects with counts"),
|
||||
t("Supports nested structures for complex work"),
|
||||
t("Quick actions via project popover (preview)"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { t } from "@/translations/helper";
|
||||
import { OnboardingController } from "../OnboardingController";
|
||||
import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
||||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentTopNavigationStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Top Navigation") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Search, switch views, and access quick settings from the top bar"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview focus-mode" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
|
||||
ComponentPreviewFactory.createTopNavigationPreview(preview);
|
||||
|
||||
const topNav = preview.querySelector<HTMLElement>(".v2-top-navigation");
|
||||
topNav?.classList.add("is-focused");
|
||||
|
||||
desc.createEl("h3", { text: t("Global controls") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Use the top bar to search across everything, switch view modes, and open notifications or settings."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Quick view mode switching"),
|
||||
t("Accessible controls with clear icons"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { t } from "@/translations/helper";
|
||||
import { OnboardingController } from "../OnboardingController";
|
||||
import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
||||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentWorkspaceSelectorStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Workspace Selector") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Switch and manage workspaces from the top of the sidebar"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview focus-mode" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
|
||||
// Focus workspace selector, dim other parts
|
||||
const wsBtn = preview.querySelector<HTMLElement>(".workspace-selector-button");
|
||||
wsBtn?.classList.add("is-focused");
|
||||
|
||||
const contentSections = preview.querySelectorAll<HTMLElement>(".v2-sidebar-content, .v2-navigation-list, .v2-project-list, .v2-section-header, .v2-top-navigation");
|
||||
contentSections.forEach((el) => {
|
||||
if (!wsBtn || !el.contains(wsBtn)) el.classList.add("is-dimmed");
|
||||
});
|
||||
|
||||
desc.createEl("h3", { text: t("Manage your spaces") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Use the workspace selector to switch between personal, work, or any custom workspace."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Quickly toggle between multiple workspaces"),
|
||||
t("Create and organize workspaces for different contexts"),
|
||||
t("Consistent placement at the top of the sidebar"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { t } from "@/translations/helper";
|
||||
import { OnboardingController } from "../OnboardingController";
|
||||
import { OnboardingController, OnboardingStep } from "../OnboardingController";
|
||||
import { TypingAnimation } from "./intro/TypingAnimation";
|
||||
import { TransitionMessage } from "./intro/TransitionMessage";
|
||||
import { ModeSelectionStep, UIMode } from "./ModeSelectionStep";
|
||||
|
||||
/**
|
||||
* Intro Step - Welcome message with typing animation + mode selection
|
||||
|
|
@ -73,21 +72,9 @@ export class IntroStep {
|
|||
|
||||
// Start typing animation
|
||||
new TypingAnimation(typingContainer, messages, () => {
|
||||
// After typing completes, show mode selection in same container
|
||||
const modeContainer = introWrapper.createDiv({
|
||||
cls: "intro-mode-selection-container"
|
||||
});
|
||||
|
||||
// Render mode selection inline (without clearing intro-line-4)
|
||||
ModeSelectionStep.renderInline(
|
||||
modeContainer,
|
||||
controller,
|
||||
(mode: UIMode) => {
|
||||
// User selected a mode, show footer with Next button
|
||||
controller.setUIMode(mode);
|
||||
footerEl.style.display = "";
|
||||
}
|
||||
);
|
||||
// Typing completed: show footer and move to Mode Selection step
|
||||
footerEl.style.display = "";
|
||||
controller.setStep(OnboardingStep.MODE_SELECT);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,15 @@ export class ModeSelectionStep {
|
|||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
|
||||
// Intro guidance text (same as intro-line-4)
|
||||
headerEl.createEl("p", {
|
||||
cls: "intro-line intro-line-4",
|
||||
text: t(
|
||||
"In the current version, Task Genius provides a brand new visual and interactive experience: Fluent; while also providing the option to return to the previous interface. Which one do you prefer?"
|
||||
),
|
||||
});
|
||||
|
||||
// Get current state
|
||||
const currentMode = controller.getState().uiMode;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@
|
|||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.component-showcase-description {
|
||||
|
|
@ -223,4 +228,39 @@
|
|||
|
||||
.component-showcase-preview.tg-v2-container.component-preview-sidebar .v2-sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Preview interactivity (scoped to preview only) */
|
||||
.component-showcase-preview .component-preview-sidebar .v2-navigation-item,
|
||||
.component-showcase-preview .component-preview-sidebar .v2-project-item {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.component-showcase-preview .component-preview-sidebar .v2-navigation-item.is-active,
|
||||
.component-showcase-preview .component-preview-sidebar .v2-project-item.is-active {
|
||||
background: var(--background-modifier-hover);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.component-showcase-preview .component-preview-sidebar .v2-navigation-item:focus-visible,
|
||||
.component-showcase-preview .component-preview-sidebar .v2-project-item:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--interactive-accent);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
/* Focus/Dim mode for step highlights */
|
||||
.component-showcase-preview.focus-mode .is-dimmed {
|
||||
opacity: 0.5;
|
||||
filter: grayscale(15%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.component-showcase-preview.focus-mode .is-focused {
|
||||
position: relative;
|
||||
outline: 1px solid var(--color-accent);
|
||||
outline-offset: -1px;
|
||||
sborder-radius: var(--radius-s);
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1499,6 +1499,7 @@
|
|||
color: var(--text-muted);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--size-4-5);
|
||||
text-align: left;s
|
||||
}
|
||||
|
||||
/* AI-style streaming characters */
|
||||
|
|
|
|||
119
styles.css
119
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue