-
-
+
+
+
Welcome to Axis Fluent Components
+
+ }
+ title="Components"
+ description={"Axis branded component"}
+ text={"Complement to fluent ui components"}
+ onClick={navigateToFirstComponent}
+ />
+ }
+ title="Theme"
+ description={"Axis branded themes"}
+ onClick={navigateToTheme}
+ />
+ }
+ title="Icons"
+ description={"Axis branded icons"}
+ onClick={navigateToIcon}
+ />
+ }
+ title="Styles"
+ description={"Utilities for existing components"}
+ onClick={navigateToFirstStyle}
+ />
+
+
+
);
};
+
+function useWelcomePage() {
+ const navigate = useNavigate();
+
+ const navigateToFirstComponent = useCallback(
+ () => {
+ const [firstComponent] = getRouteByCategory(RouteCategory.COMPONENT)[0];
+ navigate(firstComponent);
+ },
+ [navigate]
+ );
+
+ const navigateToFirstStyle = useCallback(
+ () => {
+ const [firstComponent] = getRouteByCategory(RouteCategory.STYLE)[0];
+ navigate(firstComponent);
+ },
+ [navigate]
+ );
+
+ const navigateToIcon = useCallback(
+ () => {
+ navigate(routes.IconCatalog);
+ },
+ [navigate]
+ );
+
+ const navigateToTheme = useCallback(
+ () => {
+ navigate(routes.Theme);
+ },
+ [navigate]
+ );
+
+ return {
+ navigateToFirstComponent,
+ navigateToFirstStyle,
+ navigateToIcon,
+ navigateToTheme,
+ };
+}
diff --git a/examples/src/landingpage/welcome-card.tsx b/examples/src/landingpage/welcome-card.tsx
new file mode 100644
index 00000000..9e0bb9fd
--- /dev/null
+++ b/examples/src/landingpage/welcome-card.tsx
@@ -0,0 +1,57 @@
+import {
+ Caption1,
+ Card,
+ CardHeader,
+ makeStyles,
+ shorthands,
+ Text,
+ tokens,
+} from "@fluentui/react-components";
+
+import React from "react";
+
+const useStyles = makeStyles({
+ root: {
+ minWidth: "200px",
+ maxWidth: "100%",
+ height: "fit-content",
+ },
+ caption: {
+ color: tokens.colorNeutralForeground3,
+ },
+ text: {
+ ...shorthands.margin(0),
+ },
+});
+
+type TCardExample = {
+ title: string;
+ description?: string;
+ text?: string;
+ icon: JSX.Element;
+ onClick: () => void;
+};
+
+export const WelcomeCard = (
+ { title, description, text, icon, onClick }: TCardExample
+) => {
+ const styles = useStyles();
+
+ return (
+
+ {title}}
+ image={icon}
+ description={
+ {description}
+ }
+ />
+
+ {text}
+
+
+ );
+};
diff --git a/examples/src/landingpage/welcome-image/welcome-image.component.tsx b/examples/src/landingpage/welcome-image/welcome-image.component.tsx
index 11fcfd5a..4dfecb22 100644
--- a/examples/src/landingpage/welcome-image/welcome-image.component.tsx
+++ b/examples/src/landingpage/welcome-image/welcome-image.component.tsx
@@ -1,25 +1,23 @@
// --------------------------------------------------------------------
// Copyright (c) Axis Communications AB, SWEDEN. All rights reserved.
// --------------------------------------------------------------------
+import { axisDarkTheme } from "@axiscommunications/fluent-theme";
+import { Image } from "@fluentui/react-components";
import React from "react";
+import { useAppContext } from "../../context/ApplicationStateProvider";
import welcomeImageDark from "./img/welcome-image-dark.svg";
import welcomeImageLight from "./img/welcome-image-light.svg";
import { useWelcomeImageStyles } from "./welcome-image.styles";
-import { axisDarkTheme } from "@axiscommunications/fluent-theme";
-import { Card, Image } from "@fluentui/react-components";
-import { useAppContext } from "../../context/ApplicationStateProvider";
export const WelcomeImage: React.FC = () => {
const theme = useAppContext((context) => context.theme);
const styles = useWelcomeImageStyles();
return (
-
-
-
+
);
};
diff --git a/examples/src/landingpage/welcome-message/welcome-message.component.tsx b/examples/src/landingpage/welcome-message/welcome-message.component.tsx
deleted file mode 100644
index e5e4e308..00000000
--- a/examples/src/landingpage/welcome-message/welcome-message.component.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-// --------------------------------------------------------------------
-// Copyright (c) Axis Communications AB, SWEDEN. All rights reserved.
-// --------------------------------------------------------------------
-import React from "react";
-import { Body1, Link, Title2 } from "@fluentui/react-components";
-import { useWelcomeMessageStyle } from "./welcome-message.styles";
-
-export const WelcomeMessage: React.FC = () => {
- const styles = useWelcomeMessageStyle();
-
- const bodyText = (
- <>
-
- Here you may find{" "}
-
- fluent
- {" "}
- customizations regarding:
-
-
- Components
- Themes
- Icons
-
- >
- );
- return (
-
- Welcome to Axis Fluent Components
- {bodyText}
-
- );
-};
diff --git a/examples/src/landingpage/welcome-message/welcome-message.styles.ts b/examples/src/landingpage/welcome-message/welcome-message.styles.ts
deleted file mode 100644
index dc5b79be..00000000
--- a/examples/src/landingpage/welcome-message/welcome-message.styles.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// --------------------------------------------------------------------
-// Copyright (c) Axis Communications AB, SWEDEN. All rights reserved.
-// --------------------------------------------------------------------
-
-import { makeStyles, shorthands } from "@fluentui/react-components";
-
-export const useWelcomeMessageStyle = makeStyles({
- content: {
- display: "flex",
- flexDirection: "column",
- ...shorthands.gap("50px"),
- ...shorthands.padding("100px"),
- },
- bodyText: {
- display: "flex",
- flexDirection: "column",
- ...shorthands.gap("10px"),
- },
-});
diff --git a/examples/src/layout.tsx b/examples/src/layout.tsx
index 265ba32a..9c2e1044 100644
--- a/examples/src/layout.tsx
+++ b/examples/src/layout.tsx
@@ -1,11 +1,6 @@
import React from "react";
-import {
- makeStyles,
- mergeClasses,
- shorthands,
- tokens,
-} from "@fluentui/react-components";
+import { makeStyles, shorthands, tokens } from "@fluentui/react-components";
type LayoutProps = {
readonly header: JSX.Element;
@@ -16,28 +11,26 @@ type LayoutProps = {
const useStyles = makeStyles({
root: {
backgroundColor: tokens.colorNeutralBackground4,
- display: "flex",
- flexDirection: "column",
- height: "100vh",
+ display: "grid",
+ gridTemplateColumns: "min-content 1fr",
+ gridTemplateRows: "min-content 1fr",
+ gridTemplateAreas: `
+ 'header header'
+ 'navigation outlet'`,
width: "100vw",
- },
- body: {
+ height: "100vh",
...shorthands.overflow("hidden"),
- display: "flex",
- flexGrow: 1,
- width: "100vw",
},
- content: {
- ...shorthands.border(
- tokens.strokeWidthThin,
- "solid",
- tokens.colorNeutralShadowKeyLighter
- ),
- ...shorthands.borderRadius(tokens.borderRadiusXLarge, 0, 0, 0),
+ header: {
+ ...shorthands.gridArea("header"),
+ },
+ navigation: {
+ ...shorthands.gridArea("navigation"),
+ },
+ outlet: {
+ backgroundColor: tokens.colorNeutralBackground3,
+ ...shorthands.gridArea("outlet"),
...shorthands.overflow("hidden"),
- backgroundColor: tokens.colorNeutralBackground2,
- boxShadow: tokens.shadow4,
- width: "100%",
},
});
@@ -45,14 +38,12 @@ export const Layout = ({ header, navigation, content }: LayoutProps) => {
const styles = useStyles();
return (
-
- {header}
-
+
+
{header}
+
{navigation}
-
- {content}
-
+
{content}
);
};
diff --git a/examples/src/main-page.tsx b/examples/src/main-page.tsx
index 5f377500..170b7b86 100644
--- a/examples/src/main-page.tsx
+++ b/examples/src/main-page.tsx
@@ -1,23 +1,11 @@
-/*! *****************************************************************************
-Copyright 2022 Axis Communications AB, SWEDEN. All rights reserved.
-***************************************************************************** */
-import {
- Divider,
- makeStyles,
- SelectTabData,
- SelectTabEvent,
- Tab,
- TabList,
- tokens,
-} from "@fluentui/react-components";
-import React, { useCallback, useMemo } from "react";
-import { Outlet, useLocation, useNavigate } from "react-router-dom";
+import { makeStyles, tokens } from "@fluentui/react-components";
+import React from "react";
+import { Outlet } from "react-router-dom";
import { Navbar } from "./components/top-bar";
import { Layout } from "./layout";
-import { getRouteByGroup, RouteGroup, routeMap } from "./routing/route-map";
-import { routes, TRoute } from "./routing/routes";
import { useStaticStyles } from "./styles/static";
-import { useUtilStyles } from "./styles/utils";
+import { NavigationMenu } from "./components/navigation-menu/navigation-menu";
+import { useScrollToAnchor } from "./routing/use-scroll-to-anchor";
export const useStyles = makeStyles({
navigation: {
@@ -27,55 +15,12 @@ export const useStyles = makeStyles({
export const MainPage = () => {
useStaticStyles();
- const styles = useStyles();
- const utilStyles = useUtilStyles();
-
- const navigate = useNavigate();
- const { pathname } = useLocation();
-
- const goTo = useCallback(
- (_: SelectTabEvent, { value }: SelectTabData) => {
- navigate(value as TRoute);
- },
- [navigate]
- );
-
- const storyRoutes = getRouteByGroup(RouteGroup.STORY);
-
- const renderStoryTabs = useMemo(
- () =>
- Array.from(storyRoutes.entries()).map((entry) => {
- const [key, [route, routeData]] = entry;
- return (
-
- {routeData.label}
-
- );
- }),
- [storyRoutes]
- );
-
- const homeRoute = routeMap.get(routes.Home);
+ useScrollToAnchor();
return (
}
- navigation={
-
- {homeRoute && (
-
- {homeRoute.label}
-
- )}
-
- {renderStoryTabs}
-
- }
+ navigation={
}
content={
}
/>
);
diff --git a/examples/src/routing/route-map.tsx b/examples/src/routing/route-map.tsx
index b16f28f7..02b4b459 100644
--- a/examples/src/routing/route-map.tsx
+++ b/examples/src/routing/route-map.tsx
@@ -1,54 +1,34 @@
-import {
- bundleIcon,
- DarkThemeFilled,
- DarkThemeRegular,
- DocumentCssFilled,
- DocumentCssRegular,
- EyeFilled,
- EyeRegular,
- HomeFilled,
- HomeRegular,
- IconsFilled,
- IconsRegular,
- OptionsFilled,
- OptionsRegular,
- StepsFilled,
- StepsRegular,
- TableFilled,
- TableRegular,
-} from "@fluentui/react-icons";
import React from "react";
+import { WelcomePage } from "../landingpage";
import { IconPage } from "../stories/icon-page";
-import { StepperPage } from "../stories/stepper-page";
-import { TableUtilitiesPage } from "../stories/table-utlities-page";
+import { PasswordInputPage } from "../stories/password-input/password-input-page";
+import { SliderPage } from "../stories/slider/slider-page";
+import { StepperPage } from "../stories/stepper/stepper-page";
+import { FluentUiTabStylesPage } from "../stories/tab-list-utilities/tab-list-utilities-page";
import { ThemePage } from "../stories/theme-page";
-import { VerticalStepperPage } from "../stories/vertical-stepper-page";
import { routes, TRoute } from "./routes";
-import { SliderPage } from "../stories/slider-page";
-import { WelcomePage } from "../landingpage";
-import { PasswordInputPage } from "../stories/password-input-page";
-import { FluentUiTabStylesPage } from "../stories/tab-list-utilities/tab-list-utilities-page";
-
-const HomeIcon = bundleIcon(HomeFilled, HomeRegular);
-const ThemeIcon = bundleIcon(DarkThemeFilled, DarkThemeRegular);
-const IconCatalogIcon = bundleIcon(IconsFilled, IconsRegular);
-const StepperIcon = bundleIcon(StepsFilled, StepsRegular);
-const VStepperIcon = bundleIcon(StepsFilled, StepsRegular);
-const TableUtilitiesIcon = bundleIcon(TableFilled, TableRegular);
-const SliderIcon = bundleIcon(OptionsFilled, OptionsRegular);
-const PasswordIcon = bundleIcon(EyeFilled, EyeRegular);
-const TabStylesIcon = bundleIcon(DocumentCssFilled, DocumentCssRegular);
+import { TableUtilitiesPage } from "../stories/table-utilities/table-utlities-page";
export enum RouteGroup {
MISC,
STORY,
}
+export enum RouteCategory {
+ MISC,
+ COMPONENT,
+ STYLE,
+}
+
type TRouteData = {
label: string;
element: JSX.Element;
- icon?: JSX.Element;
group: RouteGroup;
+ category?: RouteCategory;
+ ghInfo?: {
+ url: string;
+ packageName: string;
+ };
};
export const routeMap: Map
= new Map([
@@ -56,7 +36,6 @@ export const routeMap: Map = new Map([
routes.Home,
{
label: "Home",
- icon: ,
element: ,
group: RouteGroup.MISC,
},
@@ -64,19 +43,29 @@ export const routeMap: Map = new Map([
[
routes.Theme,
{
- label: "Theme",
+ label: "Themes",
element: ,
- icon: ,
group: RouteGroup.STORY,
+ category: RouteCategory.MISC,
+ ghInfo: {
+ url:
+ "https://github.com/AxisCommunications/fluent-components/pkgs/npm/fluent-theme",
+ packageName: "@axiscommunications/fluent-theme",
+ },
},
],
[
routes.IconCatalog,
{
- label: "Icon Catalog",
+ label: "Icons",
element: ,
- icon: ,
group: RouteGroup.STORY,
+ category: RouteCategory.MISC,
+ ghInfo: {
+ url:
+ "https://github.com/AxisCommunications/fluent-components/pkgs/npm/fluent-icons",
+ packageName: "@axiscommunications/fluent-icons",
+ },
},
],
[
@@ -84,17 +73,13 @@ export const routeMap: Map = new Map([
{
label: "Stepper",
element: ,
- icon: ,
- group: RouteGroup.STORY,
- },
- ],
- [
- routes.VerticalStepper,
- {
- label: "Vertical Stepper",
- element: ,
- icon: ,
group: RouteGroup.STORY,
+ category: RouteCategory.COMPONENT,
+ ghInfo: {
+ url:
+ "https://github.com/AxisCommunications/fluent-components/pkgs/npm/fluent-stepper",
+ packageName: "@axiscommunications/fluent-stepper",
+ },
},
],
[
@@ -102,8 +87,13 @@ export const routeMap: Map = new Map([
{
label: "Slider",
element: ,
- icon: ,
group: RouteGroup.STORY,
+ category: RouteCategory.COMPONENT,
+ ghInfo: {
+ url:
+ "https://github.com/AxisCommunications/fluent-components/pkgs/npm/fluent-slider",
+ packageName: "@axiscommunications/fluent-slider",
+ },
},
],
[
@@ -111,30 +101,61 @@ export const routeMap: Map = new Map([
{
label: "Password input",
element: ,
- icon: ,
group: RouteGroup.STORY,
+ category: RouteCategory.COMPONENT,
+ ghInfo: {
+ url:
+ "https://github.com/AxisCommunications/fluent-components/pkgs/npm/fluent-password-input",
+ packageName: "@axiscommunications/fluent-password-input ",
+ },
},
],
[
routes.TableUtilities,
{
- label: "Table Utilities",
+ label: "Table",
element: ,
- icon: ,
group: RouteGroup.STORY,
+ category: RouteCategory.STYLE,
+ ghInfo: {
+ url:
+ "https://github.com/AxisCommunications/fluent-components/pkgs/npm/fluent-styles",
+ packageName: "@axiscommunications/fluent-styles",
+ },
},
],
[
routes.TabListUtilities,
{
- label: "Tablist utilities",
+ label: "Tablist",
element: ,
- icon: ,
group: RouteGroup.STORY,
+ category: RouteCategory.STYLE,
+ ghInfo: {
+ url:
+ "https://github.com/AxisCommunications/fluent-components/pkgs/npm/fluent-styles",
+ packageName: "@axiscommunications/fluent-styles",
+ },
},
],
]);
+export function getGhInfoByKey(
+ routeKey: TRoute
+): { url: string; packageName: string } {
+ const routeData = routeMap.get(routeKey);
+
+ if (routeData?.ghInfo) {
+ return routeData.ghInfo;
+ }
+
+ throw new Error("getGhInfoByKey should not happen");
+}
+
export const getRouteByGroup = (group: RouteGroup) => {
return [...routeMap.entries()].filter((e) => e[1].group === group);
};
+
+export const getRouteByCategory = (category: RouteCategory) => {
+ return [...routeMap.entries()].filter((e) => e[1].category === category);
+};
diff --git a/examples/src/routing/routes.ts b/examples/src/routing/routes.ts
index a465f7b4..eaa95356 100644
--- a/examples/src/routing/routes.ts
+++ b/examples/src/routing/routes.ts
@@ -3,7 +3,6 @@ export const routes = {
Theme: "/theme",
IconCatalog: "/icon-catalog",
Stepper: "/stepper",
- VerticalStepper: "/vertical-stepper",
TableUtilities: "/table-utilities",
Slider: "/slider",
PasswordInput: "/password-input",
diff --git a/examples/src/routing/use-scroll-to-anchor.tsx b/examples/src/routing/use-scroll-to-anchor.tsx
new file mode 100644
index 00000000..ccd9a220
--- /dev/null
+++ b/examples/src/routing/use-scroll-to-anchor.tsx
@@ -0,0 +1,26 @@
+import { useEffect, useRef } from "react";
+import { useLocation } from "react-router-dom";
+
+export function useScrollToAnchor() {
+ const location = useLocation();
+ const lastHash = useRef("");
+
+ // listen to location change using useEffect with location as dependency
+ // https://jasonwatmore.com/react-router-v6-listen-to-location-route-change-without-history-listen
+ useEffect(() => {
+ if (location.hash) {
+ lastHash.current = location.hash.slice(1); // safe hash for further use after navigation
+ }
+
+ if (lastHash.current && document.getElementById(lastHash.current)) {
+ setTimeout(() => {
+ document
+ .getElementById(lastHash.current)
+ ?.scrollIntoView({ behavior: "smooth", block: "start" });
+ lastHash.current = "";
+ }, 100);
+ }
+ }, [location]);
+
+ return null;
+}
diff --git a/examples/src/stories/icon-page.tsx b/examples/src/stories/icon-page.tsx
index 0bbba7e6..645c945e 100644
--- a/examples/src/stories/icon-page.tsx
+++ b/examples/src/stories/icon-page.tsx
@@ -8,8 +8,11 @@ import {
shorthands,
} from "@fluentui/react-components";
import React from "react";
-import { PageHeader } from "../components/page-header";
import { SimpleHeader } from "../components/simple-header";
+import { StoryPage } from "../components/story/story-page";
+import { StorySection } from "../components/story/story-section";
+import { getGhInfoByKey } from "../routing/route-map";
+import { routes } from "../routing/routes";
import {
useFixedPageStyle,
useLayoutStyles,
@@ -59,6 +62,8 @@ const axisReactIcons: React.FC[] = Object.keys(AxisReactIcons)
.filter((icon) => !!icon && !!icon.displayName);
export const IconPage = (): JSX.Element => {
+ const gh = getGhInfoByKey(routes.IconCatalog);
+
const [search, setSearch] = React.useState("");
// Fluent default size is 20
const [size, setSize] = React.useState(20);
@@ -117,13 +122,13 @@ export const IconPage = (): JSX.Element => {
);
return (
-
-
-
+
+
@@ -170,7 +175,7 @@ export const IconPage = (): JSX.Element => {
-
-
+
+
);
};
diff --git a/examples/src/stories/password-input-page.tsx b/examples/src/stories/password-input-page.tsx
deleted file mode 100644
index 9a271373..00000000
--- a/examples/src/stories/password-input-page.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from "react";
-
-import { PasswordInput } from "@axiscommunications/fluent-password-input";
-
-import { mergeClasses } from "@fluentui/react-components";
-import { PageHeader } from "../components/page-header";
-import { useLayoutStyles, useScrollPageStyle } from "../styles/page";
-
-export const PasswordInputPage = () => {
- const scrollPageStyle = useScrollPageStyle();
- const layoutStyles = useLayoutStyles();
-
- return (
-
- );
-};
diff --git a/examples/src/stories/password-input/password-input-example.tsx b/examples/src/stories/password-input/password-input-example.tsx
new file mode 100644
index 00000000..9e31d3a3
--- /dev/null
+++ b/examples/src/stories/password-input/password-input-example.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { PasswordInput } from "@axiscommunications/fluent-password-input";
+
+export function PasswordInputExample() {
+ return
;
+}
+
+export const PasswordInputExampleAsString = `
+import React from "react";
+import { PasswordInput } from "@axiscommunications/fluent-password-input";
+
+export function PasswordInputExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/password-input/password-input-page.tsx b/examples/src/stories/password-input/password-input-page.tsx
new file mode 100644
index 00000000..d4e2f280
--- /dev/null
+++ b/examples/src/stories/password-input/password-input-page.tsx
@@ -0,0 +1,56 @@
+import { makeStyles } from "@fluentui/react-components";
+import { pageData } from "examples/src/components/story/story.utils";
+import React from "react";
+import { StoryPage } from "../../components/story/story-page";
+import { useExampleWithNavigation } from "../../components/story/story.utils";
+import { getGhInfoByKey } from "../../routing/route-map";
+import { routes } from "../../routing/routes";
+import {
+ PasswordInputExample,
+ PasswordInputExampleAsString,
+} from "./password-input-example";
+
+const useStyles = makeStyles({
+ example: {
+ maxWidth: "400px",
+ },
+});
+
+const examples: pageData[] = [
+ {
+ title: "Password input",
+ anchor: "PasswordInputExample",
+ example:
,
+ codeString: PasswordInputExampleAsString,
+ },
+];
+
+export const PasswordInputPage = () => {
+ const gh = getGhInfoByKey(routes.PasswordInput);
+ const styles = useStyles();
+
+ const { renderSections, renderNavigation } = useExampleWithNavigation(
+ examples.map(d => {
+ return {
+ ...d,
+ example: (
+
+ {d.example}
+
+ ),
+ };
+ })
+ );
+
+ return (
+
+ {renderSections}
+
+ );
+};
diff --git a/examples/src/stories/slider-page.tsx b/examples/src/stories/slider-page.tsx
deleted file mode 100644
index 03db7ac5..00000000
--- a/examples/src/stories/slider-page.tsx
+++ /dev/null
@@ -1,324 +0,0 @@
-import React, { useCallback, useState } from "react";
-
-import {
- RangeSlider,
- RangeSliderProps,
- Slider,
- SliderOnChangeData,
- SliderProps,
-} from "@axiscommunications/fluent-slider";
-import {
- Button,
- Label,
- makeStyles,
- mergeClasses,
- tokens,
- useId,
-} from "@fluentui/react-components";
-import { useLayoutStyles, useScrollPageStyle } from "../styles/page";
-import { PageHeader } from "../components/page-header";
-
-const useSliderPageStyles = makeStyles({
- container: {
- paddingBottom: "120px",
- },
- sliderContainer: {
- display: "flex",
- flexDirection: "column",
- marginLeft: tokens.spacingHorizontalM,
- maxWidth: "400px",
- },
- buttonContainer: {
- display: "flex",
- flexDirection: "row",
- justifyContent: "space-evenly",
- marginTop: tokens.spacingVerticalS,
- },
-});
-
-type DemoSliderProps = SliderProps & {
- title: string;
-};
-
-const DemoSlider = (props: DemoSliderProps) => {
- const classes = useSliderPageStyles();
- const { title, ...sliderProps } = props;
- const id = useId();
-
- return (
-
- {title}
-
-
- );
-};
-
-type DemoRangeSliderProps = RangeSliderProps & {
- title: string;
-};
-
-const DemoRangeSlider = (props: DemoRangeSliderProps) => {
- const classes = useSliderPageStyles();
- const { title, ...sliderProps } = props;
- const id = useId();
-
- return (
-
- {title}
-
-
- );
-};
-
-const RegularSlider = () => {
- return
;
-};
-
-const SliderWithMarks = () => {
- return (
-
- );
-};
-
-const SliderSteppingToMarks = () => {
- return (
-
- );
-};
-
-const TransformedValueSlider = () => {
- return (
-
(value < 100 ? value * 2 : "∞")}
- defaultValue={50}
- />
- );
-};
-
-const DisabledSlider = () => {
- return (
-
- );
-};
-
-const SliderWithRange = () => {
- return (
-
- );
-};
-
-const SliderWithSteps = () => {
- return (
-
- );
-};
-
-const RangeSliderWithSteps = () => {
- return (
-
- );
-};
-
-const SliderSmall = () => {
- return ;
-};
-
-const SliderWithExternalButtons = () => {
- const [value, setValue] = useState(50);
-
- const onChange = useCallback((data: SliderOnChangeData) => {
- setValue(data.value);
- }, []);
-
- const onClick = useCallback(
- (value: number) => () => {
- setValue(value);
- },
- []
- );
-
- const pageClasses = useSliderPageStyles();
- const id = useId();
-
- return (
-
-
Slider with external buttons
-
-
- 10
- 50
- 90
-
-
- );
-};
-
-const useCustomizedSliderStyles = makeStyles({
- thumb: {
- backgroundColor: tokens.colorPaletteRedBackground3,
- width: "28px",
- height: "28px",
- "&:hover": {
- backgroundColor: tokens.colorPaletteRedBackground2,
- },
- },
- thumbLabel: {
- backgroundColor: tokens.colorPaletteRedBackground3,
- color: tokens.colorPaletteYellowForeground1,
- },
- mark: {
- height: "8px",
- width: "5px",
- },
- markLabel: {
- color: tokens.colorPaletteRedForeground1,
- },
- track: {
- marginLeft: "2px",
- marginRight: "2px",
- backgroundColor: tokens.colorPaletteYellowBorder1,
- "&:hover": {
- backgroundColor: tokens.colorPaletteYellowBorderActive,
- },
- },
- rail: {
- height: "8px",
- backgroundColor: tokens.colorPaletteRedBorder1,
- "&:hover": {
- backgroundColor: tokens.colorPaletteRedBorderActive,
- },
- },
-});
-
-const CustomizedSlider = () => {
- const classes = useCustomizedSliderStyles();
- return (
- 50 },
- {
- value: 75,
- label: (
-
- 75
-
- ),
- },
- { value: 100 },
- ]}
- thumb={{
- style: { zIndex: 2 },
- className: classes.thumb,
- label: { className: classes.thumbLabel },
- }}
- markLabel={{
- style: { fontWeight: "bold" },
- }}
- mark={{
- style: { backgroundColor: tokens.colorNeutralBackgroundInverted },
- className: classes.mark,
- }}
- rail={{ style: { opacity: 0.8 }, className: classes.rail }}
- track={{ style: { zIndex: 2 }, className: classes.track }}
- />
- );
-};
-
-export const SliderPage = () => {
- const scrollPageStyle = useScrollPageStyle();
- const layoutStyles = useLayoutStyles();
- const classes = useSliderPageStyles();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/examples/src/stories/slider/examples/custom-example.tsx b/examples/src/stories/slider/examples/custom-example.tsx
new file mode 100644
index 00000000..c9dd584a
--- /dev/null
+++ b/examples/src/stories/slider/examples/custom-example.tsx
@@ -0,0 +1,171 @@
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+import React from "react";
+
+const useCustomizedSliderStyles = makeStyles({
+ thumb: {
+ backgroundColor: tokens.colorPaletteRedBackground3,
+ width: "28px",
+ height: "28px",
+ "&:hover": {
+ backgroundColor: tokens.colorPaletteRedBackground2,
+ },
+ },
+ thumbLabel: {
+ backgroundColor: tokens.colorPaletteRedBackground3,
+ color: tokens.colorPaletteYellowForeground1,
+ },
+ mark: {
+ height: "8px",
+ width: "5px",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ marginLeft: "2px",
+ marginRight: "2px",
+ backgroundColor: tokens.colorPaletteYellowBorder1,
+ "&:hover": {
+ backgroundColor: tokens.colorPaletteYellowBorderActive,
+ },
+ },
+ rail: {
+ height: "8px",
+ backgroundColor: tokens.colorPaletteRedBorder1,
+ "&:hover": {
+ backgroundColor: tokens.colorPaletteRedBorderActive,
+ },
+ },
+});
+
+export function CustomSliderExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+ 50 },
+ {
+ value: 75,
+ label: (
+
+ 75
+
+ ),
+ },
+ { value: 100 },
+ ]}
+ thumb={{
+ style: { zIndex: 2 },
+ className: classes.thumb,
+ label: { className: classes.thumbLabel },
+ }}
+ markLabel={{
+ style: { fontWeight: "bold" },
+ }}
+ mark={{
+ style: { backgroundColor: tokens.colorNeutralBackgroundInverted },
+ className: classes.mark,
+ }}
+ rail={{ style: { opacity: 0.8 }, className: classes.rail }}
+ track={{ style: { zIndex: 2 }, className: classes.track }}
+ />
+ );
+}
+
+export const CustomSliderExampleAsString = `
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+import React from "react";
+
+const useCustomizedSliderStyles = makeStyles({
+ thumb: {
+ backgroundColor: tokens.colorPaletteRedBackground3,
+ width: "28px",
+ height: "28px",
+ "&:hover": {
+ backgroundColor: tokens.colorPaletteRedBackground2,
+ },
+ },
+ thumbLabel: {
+ backgroundColor: tokens.colorPaletteRedBackground3,
+ color: tokens.colorPaletteYellowForeground1,
+ },
+ mark: {
+ height: "8px",
+ width: "5px",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ marginLeft: "2px",
+ marginRight: "2px",
+ backgroundColor: tokens.colorPaletteYellowBorder1,
+ "&:hover": {
+ backgroundColor: tokens.colorPaletteYellowBorderActive,
+ },
+ },
+ rail: {
+ height: "8px",
+ backgroundColor: tokens.colorPaletteRedBorder1,
+ "&:hover": {
+ backgroundColor: tokens.colorPaletteRedBorderActive,
+ },
+ },
+});
+
+export function CustomSliderExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+ 50 },
+ {
+ value: 75,
+ label: (
+
+ 75
+
+ ),
+ },
+ { value: 100 },
+ ]}
+ thumb={{
+ style: { zIndex: 2 },
+ className: classes.thumb,
+ label: { className: classes.thumbLabel },
+ }}
+ markLabel={{
+ style: { fontWeight: "bold" },
+ }}
+ mark={{
+ style: { backgroundColor: tokens.colorNeutralBackgroundInverted },
+ className: classes.mark,
+ }}
+ rail={{ style: { opacity: 0.8 }, className: classes.rail }}
+ track={{ style: { zIndex: 2 }, className: classes.track }}
+ />
+ );
+}
+`;
diff --git a/examples/src/stories/slider/examples/disabled-example.tsx b/examples/src/stories/slider/examples/disabled-example.tsx
new file mode 100644
index 00000000..dd354d58
--- /dev/null
+++ b/examples/src/stories/slider/examples/disabled-example.tsx
@@ -0,0 +1,29 @@
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function DisabledSliderExample() {
+ return (
+
+ );
+}
+
+export const DisabledSliderExampleAsString = `
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function DisabledSliderExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/slider/examples/range-slider-with-steps-example.tsx b/examples/src/stories/slider/examples/range-slider-with-steps-example.tsx
new file mode 100644
index 00000000..f1fd4b17
--- /dev/null
+++ b/examples/src/stories/slider/examples/range-slider-with-steps-example.tsx
@@ -0,0 +1,31 @@
+import { RangeSlider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function WithStepsRangeSliderExample() {
+ return (
+
+ );
+}
+
+export const WithStepsRangeSliderExampleAsString = `
+import { RangeSlider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function WithStepsRangeSliderExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/slider/examples/regular-example.tsx b/examples/src/stories/slider/examples/regular-example.tsx
new file mode 100644
index 00000000..c85f8ebe
--- /dev/null
+++ b/examples/src/stories/slider/examples/regular-example.tsx
@@ -0,0 +1,17 @@
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function RegularSliderExample() {
+ return ;
+}
+
+export const RegularSliderExampleAsString = `
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function RegularSliderExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/slider/examples/small-example.tsx b/examples/src/stories/slider/examples/small-example.tsx
new file mode 100644
index 00000000..a206a464
--- /dev/null
+++ b/examples/src/stories/slider/examples/small-example.tsx
@@ -0,0 +1,17 @@
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function SmallSliderExample() {
+ return ;
+}
+
+export const SmallSliderExampleAsString = `
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function SmallSliderExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/slider/examples/stepping-to-marks-example.tsx b/examples/src/stories/slider/examples/stepping-to-marks-example.tsx
new file mode 100644
index 00000000..f5a1fbfe
--- /dev/null
+++ b/examples/src/stories/slider/examples/stepping-to-marks-example.tsx
@@ -0,0 +1,39 @@
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function SteppingToMarksSliderExample() {
+ return (
+
+ );
+}
+
+export const SteppingToMarksSliderExampleAsString = `
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function SteppingToMarksSliderExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/slider/examples/transform-value-example.tsx b/examples/src/stories/slider/examples/transform-value-example.tsx
new file mode 100644
index 00000000..a097e4a5
--- /dev/null
+++ b/examples/src/stories/slider/examples/transform-value-example.tsx
@@ -0,0 +1,29 @@
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function TransformValueSliderExample() {
+ return (
+ (value < 100 ? value * 2 : "∞")}
+ defaultValue={50}
+ />
+ );
+}
+
+export const TransformValueSliderExampleAsString = `
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function TransformValueSliderExample() {
+ return (
+ (value < 100 ? value * 2 : "∞")}
+ defaultValue={50} />
+ )
+}
+`;
diff --git a/examples/src/stories/slider/examples/with-external-buttons-example.tsx b/examples/src/stories/slider/examples/with-external-buttons-example.tsx
new file mode 100644
index 00000000..2585f8ee
--- /dev/null
+++ b/examples/src/stories/slider/examples/with-external-buttons-example.tsx
@@ -0,0 +1,93 @@
+import { Slider, SliderOnChangeData } from "@axiscommunications/fluent-slider";
+import { Button, makeStyles, tokens } from "@fluentui/react-components";
+import React, { useCallback, useState } from "react";
+
+const useSliderPageStyles = makeStyles({
+ sliderContainer: {
+ display: "flex",
+ flexDirection: "column",
+ marginLeft: tokens.spacingHorizontalM,
+ },
+ buttonContainer: {
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "space-evenly",
+ marginTop: tokens.spacingVerticalS,
+ },
+});
+
+export function ExternalButtonsSliderExample() {
+ const [value, setValue] = useState(50);
+
+ const onChange = useCallback((data: SliderOnChangeData) => {
+ setValue(data.value);
+ }, []);
+
+ const onClick = useCallback(
+ (value: number) => () => {
+ setValue(value);
+ },
+ []
+ );
+
+ const pageClasses = useSliderPageStyles();
+
+ return (
+
+ );
+}
+
+export const ExternalButtonsSliderExampleAsString = `
+import { Slider, SliderOnChangeData } from "@axiscommunications/fluent-slider";
+import { Button, makeStyles, tokens } from "@fluentui/react-components";
+import React, { useCallback, useState } from "react";
+
+const useSliderPageStyles = makeStyles({
+ sliderContainer: {
+ display: "flex",
+ flexDirection: "column",
+ marginLeft: tokens.spacingHorizontalM,
+ },
+ buttonContainer: {
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "space-evenly",
+ marginTop: tokens.spacingVerticalS,
+ },
+});
+
+export function ExternalButtonsSliderExample() {
+ const [value, setValue] = useState(50);
+
+ const onChange = useCallback((data: SliderOnChangeData) => {
+ setValue(data.value);
+ }, []);
+
+ const onClick = useCallback(
+ (value: number) => () => {
+ setValue(value);
+ },
+ []
+ );
+
+ const pageClasses = useSliderPageStyles();
+
+ return (
+
+ );
+}
+`;
diff --git a/examples/src/stories/slider/examples/with-marks-example.tsx b/examples/src/stories/slider/examples/with-marks-example.tsx
new file mode 100644
index 00000000..b680b09f
--- /dev/null
+++ b/examples/src/stories/slider/examples/with-marks-example.tsx
@@ -0,0 +1,37 @@
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function WithMarkSliderExample() {
+ return (
+
+ );
+}
+
+export const WithMarkSliderExampleAsString = `
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function WithMarkSliderExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/slider/examples/with-range-example.tsx b/examples/src/stories/slider/examples/with-range-example.tsx
new file mode 100644
index 00000000..46e922fa
--- /dev/null
+++ b/examples/src/stories/slider/examples/with-range-example.tsx
@@ -0,0 +1,41 @@
+import { RangeSlider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function RangeSliderExample() {
+ return (
+
+ );
+}
+
+export const RangeSliderExampleAsString = `
+import { RangeSlider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function RangeSliderExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/slider/examples/with-steps-example.tsx b/examples/src/stories/slider/examples/with-steps-example.tsx
new file mode 100644
index 00000000..a34a8f93
--- /dev/null
+++ b/examples/src/stories/slider/examples/with-steps-example.tsx
@@ -0,0 +1,31 @@
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function WithStepsSliderExample() {
+ return (
+
+ );
+}
+
+export const WithStepsSliderExampleAsString = `
+import { Slider } from "@axiscommunications/fluent-slider";
+import React from "react";
+
+export function WithStepsSliderExample() {
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/slider/slider-page.tsx b/examples/src/stories/slider/slider-page.tsx
new file mode 100644
index 00000000..bcb03039
--- /dev/null
+++ b/examples/src/stories/slider/slider-page.tsx
@@ -0,0 +1,158 @@
+import { makeStyles } from "@fluentui/react-components";
+import React from "react";
+import { StoryPage } from "../../components/story/story-page";
+import {
+ pageData,
+ useExampleWithNavigation,
+} from "../../components/story/story.utils";
+import { getGhInfoByKey } from "../../routing/route-map";
+import { routes } from "../../routing/routes";
+import {
+ CustomSliderExample,
+ CustomSliderExampleAsString,
+} from "./examples/custom-example";
+import {
+ DisabledSliderExample,
+ DisabledSliderExampleAsString,
+} from "./examples/disabled-example";
+import {
+ WithStepsRangeSliderExample,
+ WithStepsRangeSliderExampleAsString,
+} from "./examples/range-slider-with-steps-example";
+import {
+ RegularSliderExample,
+ RegularSliderExampleAsString,
+} from "./examples/regular-example";
+import {
+ SmallSliderExample,
+ SmallSliderExampleAsString,
+} from "./examples/small-example";
+import {
+ SteppingToMarksSliderExample,
+ SteppingToMarksSliderExampleAsString,
+} from "./examples/stepping-to-marks-example";
+import {
+ TransformValueSliderExample,
+ TransformValueSliderExampleAsString,
+} from "./examples/transform-value-example";
+import {
+ ExternalButtonsSliderExample,
+ ExternalButtonsSliderExampleAsString,
+} from "./examples/with-external-buttons-example";
+import {
+ WithMarkSliderExample,
+ WithMarkSliderExampleAsString,
+} from "./examples/with-marks-example";
+import {
+ RangeSliderExample,
+ RangeSliderExampleAsString,
+} from "./examples/with-range-example";
+import {
+ WithStepsSliderExample,
+ WithStepsSliderExampleAsString,
+} from "./examples/with-steps-example";
+
+const useStyles = makeStyles({
+ example: {
+ maxWidth: "400px",
+ },
+});
+
+const examples: pageData[] = [
+ {
+ title: "Default",
+ anchor: "RegularSliderExample",
+ example: ,
+ codeString: RegularSliderExampleAsString,
+ },
+ {
+ title: "Disabled",
+ anchor: "DisabledSliderExample",
+ example: ,
+ codeString: DisabledSliderExampleAsString,
+ },
+ {
+ title: "Small",
+ anchor: "SmallSliderExample",
+ example: ,
+ codeString: SmallSliderExampleAsString,
+ },
+ {
+ title: "With marks",
+ anchor: "WithMarkSliderExample",
+ example: ,
+ codeString: WithMarkSliderExampleAsString,
+ },
+ {
+ title: "With steps",
+ anchor: "WithStepsSliderExample",
+ example: ,
+ codeString: WithStepsSliderExampleAsString,
+ },
+ {
+ title: "Stepping to marks",
+ anchor: "SteppingToMarksSliderExample",
+ example: ,
+ codeString: SteppingToMarksSliderExampleAsString,
+ },
+ {
+ title: "Transform value",
+ anchor: "TransformValueSliderExample",
+ example: ,
+ codeString: TransformValueSliderExampleAsString,
+ },
+ {
+ title: "External buttons",
+ anchor: "ExternalButtonsSliderExample",
+ example: ,
+ codeString: ExternalButtonsSliderExampleAsString,
+ },
+ {
+ title: "Custom",
+ anchor: "CustomSliderExample",
+ example: ,
+ codeString: CustomSliderExampleAsString,
+ },
+ {
+ title: "Range slider",
+ anchor: "RangeSliderExample",
+ example: ,
+ codeString: RangeSliderExampleAsString,
+ },
+ {
+ title: "Range slider with steps",
+ anchor: "WithStepsRangeSliderExample",
+ example: ,
+ codeString: WithStepsRangeSliderExampleAsString,
+ },
+];
+
+export const SliderPage = () => {
+ const gh = getGhInfoByKey(routes.Slider);
+ const styles = useStyles();
+
+ const { renderSections, renderNavigation } = useExampleWithNavigation(
+ examples.map(d => {
+ return {
+ ...d,
+ example: (
+
+ {d.example}
+
+ ),
+ };
+ })
+ );
+
+ return (
+
+ {renderSections}
+
+ );
+};
diff --git a/examples/src/stories/stepper-page.tsx b/examples/src/stories/stepper-page.tsx
deleted file mode 100644
index 3671db61..00000000
--- a/examples/src/stories/stepper-page.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import React, { useCallback, useState } from "react";
-
-import {
- DialogStep,
- Stepper,
- StepperDialog,
-} from "@axiscommunications/fluent-stepper";
-import { mergeClasses } from "@fluentui/react-components";
-import { PageHeader } from "../components/page-header";
-import { SectionTitle } from "../components/section-title";
-import { useLayoutStyles, useScrollPageStyle } from "../styles/page";
-
-export const steps: DialogStep[] = [
- {
- name: "First step",
- content: <>{"This is the content of the first step. ".repeat(20)}>,
- },
- {
- name: "Second step",
- content: <>{"This is the content of the second step. ".repeat(20)}>,
- },
- {
- name: "Third step",
- content: <>{"This is the content of the third step. ".repeat(20)}>,
- },
-];
-
-export const StepperPage = () => {
- const [step, setStep] = useState(0);
- const scrollPageStyle = useScrollPageStyle();
- const layoutStyles = useLayoutStyles();
-
- const onFinish = useCallback(() => alert("Finish!"), []);
- const onCancel = useCallback(() => setStep(0), []);
- return (
-
- );
-};
diff --git a/examples/src/stories/stepper/examples/stepper-dialog-example.tsx b/examples/src/stories/stepper/examples/stepper-dialog-example.tsx
new file mode 100644
index 00000000..273d6359
--- /dev/null
+++ b/examples/src/stories/stepper/examples/stepper-dialog-example.tsx
@@ -0,0 +1,82 @@
+import React, { useCallback, useState } from "react";
+
+import { DialogStep, StepperDialog } from "@axiscommunications/fluent-stepper";
+
+const steps: DialogStep[] = [
+ {
+ name: "First step",
+ content: <>{"This is the content of the first step. ".repeat(20)}>,
+ },
+ {
+ name: "Second step",
+ content: <>{"This is the content of the second step. ".repeat(20)}>,
+ },
+ {
+ name: "Third step",
+ content: <>{"This is the content of the third step. ".repeat(20)}>,
+ },
+];
+export function StepperDialogExample() {
+ const [step, setStep] = useState(0);
+ const onFinish = useCallback(() => alert("Finish!"), []);
+ const onCancel = useCallback(() => setStep(0), []);
+
+ return (
+
+ );
+}
+
+export const StepperDialogExampleAsString = `
+import React, { useCallback, useState } from "react";
+
+import {
+ DialogStep,
+ StepperDialog
+} from "@axiscommunications/fluent-stepper";
+
+const steps: DialogStep[] = [
+ {
+ name: "First step",
+ content: <>{"This is the content of the first step. ".repeat(20)}>,
+ },
+ {
+ name: "Second step",
+ content: <>{"This is the content of the second step. ".repeat(20)}>,
+ },
+ {
+ name: "Third step",
+ content: <>{"This is the content of the third step. ".repeat(20)}>,
+ },
+];
+export function StepperDialogExample() {
+ const [step, setStep] = useState(0);
+ const onFinish = useCallback(() => alert("Finish!"), []);
+ const onCancel = useCallback(() => setStep(0), []);
+
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/stepper/examples/vertical-stepper-dialog-example.tsx b/examples/src/stories/stepper/examples/vertical-stepper-dialog-example.tsx
new file mode 100644
index 00000000..7e1b7765
--- /dev/null
+++ b/examples/src/stories/stepper/examples/vertical-stepper-dialog-example.tsx
@@ -0,0 +1,82 @@
+import React, { useCallback, useState } from "react";
+
+import { DialogStep, StepperDialog } from "@axiscommunications/fluent-stepper";
+
+const steps: DialogStep[] = [
+ {
+ name: "First step",
+ content: <>{"This is the content of the first step. ".repeat(20)}>,
+ },
+ {
+ name: "Second step",
+ content: <>{"This is the content of the second step. ".repeat(20)}>,
+ },
+ {
+ name: "Third step",
+ content: <>{"This is the content of the third step. ".repeat(20)}>,
+ },
+];
+export function VerticalStepperDialogExample() {
+ const [step, setStep] = useState(0);
+ const onFinish = useCallback(() => alert("Finish!"), []);
+ const onCancel = useCallback(() => setStep(0), []);
+ return (
+
+ );
+}
+
+export const VerticalStepperDialogExampleAsString = `
+import React, { useCallback, useState } from "react";
+
+import {
+ DialogStep,
+ StepperDialog
+} from "@axiscommunications/fluent-stepper";
+
+const steps: DialogStep[] = [
+ {
+ name: "First step",
+ content: <>{"This is the content of the first step. ".repeat(20)}>,
+ },
+ {
+ name: "Second step",
+ content: <>{"This is the content of the second step. ".repeat(20)}>,
+ },
+ {
+ name: "Third step",
+ content: <>{"This is the content of the third step. ".repeat(20)}>,
+ },
+];
+export function VerticalStepperDialogExample() {
+ const [step, setStep] = useState(0);
+ const onFinish = useCallback(() => alert("Finish!"), []);
+ const onCancel = useCallback(() => setStep(0), []);
+ return (
+
+ )
+}
+`;
diff --git a/examples/src/stories/stepper/stepper-page.tsx b/examples/src/stories/stepper/stepper-page.tsx
new file mode 100644
index 00000000..a1cf835a
--- /dev/null
+++ b/examples/src/stories/stepper/stepper-page.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+import { StoryPage } from "../../components/story/story-page";
+import {
+ pageData,
+ useExampleWithNavigation,
+} from "../../components/story/story.utils";
+import { getGhInfoByKey } from "../../routing/route-map";
+import { routes } from "../../routing/routes";
+import {
+ StepperDialogExample,
+ StepperDialogExampleAsString,
+} from "./examples/stepper-dialog-example";
+import {
+ VerticalStepperDialogExample,
+ VerticalStepperDialogExampleAsString,
+} from "./examples/vertical-stepper-dialog-example";
+
+const examples: pageData[] = [
+ {
+ title: "Stepper dialog",
+ anchor: "StepperDialogExample",
+ example: ,
+ codeString: StepperDialogExampleAsString,
+ },
+ {
+ title: "Vertical stepper dialog",
+ anchor: "VerticalStepperDialogExample",
+ example: ,
+ codeString: VerticalStepperDialogExampleAsString,
+ },
+];
+
+export const StepperPage = () => {
+ const gh = getGhInfoByKey(routes.Stepper);
+ const { renderSections, renderNavigation } = useExampleWithNavigation(
+ examples
+ );
+
+ return (
+
+ {renderSections}
+
+ );
+};
diff --git a/examples/src/stories/tab-list-utilities/tab-list-example.tsx b/examples/src/stories/tab-list-utilities/tab-list-example.tsx
new file mode 100644
index 00000000..8c4a2717
--- /dev/null
+++ b/examples/src/stories/tab-list-utilities/tab-list-example.tsx
@@ -0,0 +1,145 @@
+import {
+ useTabListStyles,
+ useTabStyles,
+} from "@axiscommunications/fluent-styles";
+import {
+ Tab,
+ TabList,
+ TabListProps,
+ TabProps,
+} from "@fluentui/react-components";
+import { bundleIcon, HomeFilled, HomeRegular } from "@fluentui/react-icons";
+import React, { useState } from "react";
+
+const HomeIcon = bundleIcon(HomeFilled, HomeRegular);
+
+export type TTabListComponent = {
+ withText?: boolean;
+} & TabListProps;
+
+export function StyledTabListComponent(
+ { withText = true, ...props }: TTabListComponent
+) {
+ const [selectedTab, setSelectedTab] = useState("tab1");
+ const { rootStyle } = useTabListStyles({ vertical: props.vertical });
+
+ return (
+ {
+ setSelectedTab(value as unknown as string);
+ }}
+ {...props}
+ >
+ }
+ value="tab1"
+ selected={selectedTab === "tab1"}
+ >
+ {withText && "First Tab"}
+
+ }
+ value="tab2"
+ selected={selectedTab === "tab2"}
+ >
+ {withText && "First Tab"}
+
+ }
+ value="tab3"
+ selected={selectedTab === "tab3"}
+ >
+ {withText && "First Tab"}
+
+
+ );
+}
+
+export type TStyledTabComponent = {
+ selected?: boolean;
+} & TabProps;
+
+function StyledTabComponent(
+ { selected, children, ...props }: TStyledTabComponent
+) {
+ const { rootStyle } = useTabStyles({ selected });
+
+ return {children} ;
+}
+
+export const StyledTabListComponentAsJson = `
+import {
+ useTabListStyles,
+ useTabStyles,
+} from "@axiscommunications/fluent-styles";
+import {
+ Tab,
+ TabList,
+ TabListProps,
+ TabProps,
+} from "@fluentui/react-components";
+import { HomeFilled, HomeRegular, bundleIcon } from "@fluentui/react-icons";
+import React, { useState } from "react";
+
+const HomeIcon = bundleIcon(HomeFilled, HomeRegular);
+
+export type TTabListComponent = {
+ withText?: boolean;
+} & TabListProps;
+
+export function StyledTabListComponent(
+ { withText = true, ...props }: TTabListComponent
+) {
+ const [selectedTab, setSelectedTab] = useState("tab1");
+ const { rootStyle } = useTabListStyles({ vertical: props.vertical });
+
+ return (
+ {
+ setSelectedTab(value as unknown as string);
+ }}
+ {...props}
+ >
+ }
+ value="tab1"
+ selected={selectedTab === "tab1"}
+ >
+ {withText && "First Tab"}
+
+ }
+ value="tab2"
+ selected={selectedTab === "tab2"}
+ >
+ {withText && "First Tab"}
+
+ }
+ value="tab3"
+ selected={selectedTab === "tab3"}
+ >
+ {withText && "First Tab"}
+
+
+ );
+}
+
+export type TStyledTabComponent = {
+ selected?: boolean;
+} & TabProps;
+
+function StyledTabComponent(
+ { selected, children, ...props }: TStyledTabComponent
+) {
+ const { rootStyle } = useTabStyles({ selected });
+
+ return {children} ;
+}
+`;
diff --git a/examples/src/stories/tab-list-utilities/tab-list-styled.tsx b/examples/src/stories/tab-list-utilities/tab-list-styled.tsx
deleted file mode 100644
index fd2ff3ac..00000000
--- a/examples/src/stories/tab-list-utilities/tab-list-styled.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React from "react";
-import {
- Tab,
- TabList,
- TabListProps,
- TabProps,
-} from "@fluentui/react-components";
-import { bundleIcon, HomeFilled, HomeRegular } from "@fluentui/react-icons";
-import {
- useTabListStyles,
- useTabStyles,
-} from "@axiscommunications/fluent-styles";
-import { useTabListContext } from "./tab-list-utilities-page";
-
-export const codeBlockStyled = `
-...
-import {
- useTabListStyles,
- useTabStyles,
-} from "@axiscommunications/fluent-styles";
-...
-
-//standard usage
-const { rootStyle: tabListStyle } = useTabListStyles({ vertical: true/false });
-const { rootStyle: tabStyle } = useTabStyles({ selected: true/false });
-
-
- tab1
-
-
-//not happy with style? all styles can be grabbed from styles prop
-const { styles } = useTabListStyles();
-const newStyle = mergeClasses(styles.root, overrideStyles.root ...)
-...
-`;
-
-const HomeIcon = bundleIcon(HomeFilled, HomeRegular);
-
-export type TTabListComponent = {
- withText?: boolean;
-} & TabListProps;
-
-export function StyledTabListComponent(
- { withText = true, ...props }: TTabListComponent
-) {
- const { selectedTab, setSelectedTab } = useTabListContext();
- const { rootStyle } = useTabListStyles({ vertical: props.vertical });
-
- return (
- {
- setSelectedTab(value as unknown as string);
- }}
- {...props}
- >
- }
- value="tab1"
- selected={selectedTab === "tab1"}
- >
- {withText && "First Tab"}
-
- }
- value="tab2"
- selected={selectedTab === "tab2"}
- >
- {withText && "First Tab"}
-
- }
- value="tab3"
- selected={selectedTab === "tab3"}
- >
- {withText && "First Tab"}
-
-
- );
-}
-
-export type TStyledTabComponent = {
- selected?: boolean;
-} & TabProps;
-
-function StyledTabComponent(
- { selected, children, ...props }: TStyledTabComponent
-) {
- const { rootStyle } = useTabStyles({ selected });
-
- return {children} ;
-}
diff --git a/examples/src/stories/tab-list-utilities/tab-list-utilities-page.tsx b/examples/src/stories/tab-list-utilities/tab-list-utilities-page.tsx
index 9098033d..8a54d105 100644
--- a/examples/src/stories/tab-list-utilities/tab-list-utilities-page.tsx
+++ b/examples/src/stories/tab-list-utilities/tab-list-utilities-page.tsx
@@ -1,110 +1,47 @@
-import React, { createContext, useContext, useState } from "react";
+import React from "react";
+import { StoryPage } from "../../components/story/story-page";
import {
- makeStyles,
- mergeClasses,
- shorthands,
- Tab,
- TabList,
- tokens,
-} from "@fluentui/react-components";
-import { PageHeader } from "../../components/page-header";
-import { useLayoutStyles, useScrollPageStyle } from "../../styles/page";
-import { SectionTitle } from "../../components/section-title";
-import { bundleIcon, HomeFilled, HomeRegular } from "@fluentui/react-icons";
-import { CodeBlock } from "../../components/code-block";
+ pageData,
+ useExampleWithNavigation,
+} from "../../components/story/story.utils";
+import { getGhInfoByKey } from "../../routing/route-map";
+import { routes } from "../../routing/routes";
import {
- codeBlockStyled,
StyledTabListComponent,
- TTabListComponent,
-} from "./tab-list-styled";
-
-type TTabListContext = {
- selectedTab: string;
- setSelectedTab: (value: string) => void;
-};
-
-const TabListContext = createContext(null);
-export const useTabListContext = () => {
- const context = useContext(TabListContext);
- if (context === null) {
- throw new Error("cant use context outside its provider");
- }
- return context;
-};
-
-const HomeIcon = bundleIcon(HomeFilled, HomeRegular);
-
-const useTabListUtilitiesStyles = makeStyles({
- section: {
- display: "flex",
- flexDirection: "row",
- flexWrap: "wrap",
- ...shorthands.gap(tokens.spacingHorizontalXXL),
- },
- group: {
- display: "flex",
- alignItems: "flex-start",
- flexDirection: "column",
- ...shorthands.gap(tokens.spacingHorizontalXS),
+ StyledTabListComponentAsJson,
+} from "./tab-list-example";
+
+const examples: pageData[] = [
+ {
+ title: "Default",
+ anchor: "StepperDialogExample",
+ example: (
+ <>
+
+
+
+
+ >
+ ),
+ codeString: StyledTabListComponentAsJson,
},
-});
+];
export const FluentUiTabStylesPage = () => {
- const styles = useTabListUtilitiesStyles();
- const scrollPageStyle = useScrollPageStyle();
- const layoutStyles = useLayoutStyles();
- const [selectedTab, setSelectedTab] = useState("tab1");
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ const gh = getGhInfoByKey(routes.TabListUtilities);
+ const { renderSections, renderNavigation } = useExampleWithNavigation(
+ examples
);
-};
-function TabListComponent({ withText = true, ...props }: TTabListComponent) {
- const { selectedTab, setSelectedTab } = useTabListContext();
return (
- {
- setSelectedTab(value as unknown as string);
- }}
- {...props}
+
- } value="tab1">{withText && "First Tab"}
- } value="tab2">{withText && "First Tab"}
- } value="tab3">{withText && "First Tab"}
-
+ {renderSections}
+
);
-}
+};
diff --git a/examples/src/stories/table-utilities/table-example.tsx b/examples/src/stories/table-utilities/table-example.tsx
new file mode 100644
index 00000000..d9921adb
--- /dev/null
+++ b/examples/src/stories/table-utilities/table-example.tsx
@@ -0,0 +1,671 @@
+import { usePageController } from "@axiscommunications/fluent-hooks";
+import {
+ useColumnStyles,
+ useRowStyles,
+ useTableStyles,
+} from "@axiscommunications/fluent-styles";
+import {
+ Button,
+ makeStyles,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ MenuPopover,
+ MenuTrigger,
+ mergeClasses,
+ shorthands,
+ SkeletonItem,
+ Table,
+ TableBody,
+ TableCell,
+ TableCellLayout,
+ TableHeader,
+ TableHeaderCell,
+ TableRow,
+ TableSelectionCell,
+ tokens,
+} from "@fluentui/react-components";
+import {
+ bundleIcon,
+ ChevronLeft16Filled,
+ ChevronLeft16Regular,
+ ChevronRight16Filled,
+ ChevronRight16Regular,
+} from "@fluentui/react-icons";
+import React, { useMemo, useState } from "react";
+import { useAppContext } from "../../context/ApplicationStateProvider";
+
+const users = [
+ { user: "Robin", role: "Admin", luckyNumber: 1337 },
+ { user: "Batman", role: "Hero", luckyNumber: 7 },
+ { user: "Alfred", role: "Butler", luckyNumber: 9 },
+ { user: "Joker", role: "Villain", luckyNumber: 4 },
+ { user: "Harley Quinn", role: "Villain", luckyNumber: 5 },
+ { user: "Bane", role: "Villain", luckyNumber: 6 },
+ { user: "Poison Ivy", role: "Villain", luckyNumber: 7 },
+ { user: "Vicky Vale", role: "Reporter", luckyNumber: 22 },
+ { user: "Jim Gordon", role: "Commissioner", luckyNumber: 13 },
+];
+
+const pageSizes = [5, 7, 15];
+const ChevronLeft = bundleIcon(ChevronLeft16Filled, ChevronLeft16Regular);
+const ChevronRight = bundleIcon(ChevronRight16Filled, ChevronRight16Regular);
+
+const useStyles = makeStyles({
+ root: {
+ display: "flex",
+ flexDirection: "column",
+ ...shorthands.gap(tokens.spacingVerticalM),
+ },
+});
+
+export function TableExample() {
+ const [skip, setSkip] = useState(0);
+ const [take, setTake] = useState(pageSizes[0]);
+ const [loading, setLoading] = useState(false);
+ const [total /*, setTotal*/] = useState(users.length);
+
+ const pageController = usePageController({ total, skip, take, setSkip });
+ const page = users.slice(skip, skip + take);
+
+ const tableStyles = useTableStyles();
+ const rowStyles = useRowStyles();
+ const columnStyles = useColumnStyles();
+ const styles = useStyles();
+
+ return (
+
+
+ setLoading(prev => !prev)}>
+ Toggle loading
+
+
+
+
+
+
+
+ User
+
+
+ Role
+
+
+ Lucky number
+
+
+
+ {loading
+ ? (
+
+ )
+ : (
+
+ {page.map((rowContent, index) => (
+
+
+
+ {rowContent.user}
+
+
+ {rowContent.role}
+
+
+ {rowContent.luckyNumber}
+
+
+ ))}
+
+ )}
+
+
+
+ );
+}
+
+type TableFooterProps =
+ & Pick<
+ ReturnType,
+ "currentPage" | "totalPages" | "nextPage" | "prevPage" | "goToPage"
+ >
+ & { total: number; take: number; setTake: (take: number) => void };
+
+const useTableFooterStyles = makeStyles({
+ root: {
+ display: "flex",
+ justifyContent: "space-between",
+ marginTop: tokens.spacingVerticalM,
+ },
+ rowsSelectors: {
+ display: "flex",
+ alignItems: "center",
+ ...shorthands.gap(tokens.spacingHorizontalM),
+ },
+ pagesSelectors: { display: "flex" },
+});
+
+function TableFooter({
+ total,
+ currentPage,
+ totalPages,
+ take,
+ setTake,
+ nextPage,
+ prevPage,
+ goToPage,
+}: TableFooterProps) {
+ const dir = useAppContext((context) => context.dir);
+ const styles = useTableFooterStyles();
+ const from = currentPage * take + 1;
+ const to = Math.min(from + take - 1, total);
+
+ return (
+
+
+ Showing rows {from}-{to} of {total}
+
+
+
+ Rows per page: {take}
+
+
+
+
+ {pageSizes.map((size) => {
+ return (
+ {
+ setTake(size);
+ }}
+ >
+ {size}
+
+ );
+ })}
+
+
+
+
+
+
+
+
+ Page: {currentPage + 1} of {totalPages}
+
+
+
+
+ {Array(totalPages)
+ .fill(0)
+ .map((_, index) => {
+ return (
+ {
+ goToPage(index);
+ }}
+ >
+ {index + 1}
+
+ );
+ })}
+
+
+
+ {dir === "ltr"
+ ? (
+ <>
+ }
+ />
+ }
+ />
+ >
+ )
+ : (
+ <>
+ }
+ />
+ }
+ />
+ >
+ )}
+
+
+ );
+}
+
+/**
+ * Skeleton TableBody
+ * Renders a skeleton table matching the specified column widths.
+ * The number of rows will be used if set and non-zero, otherwise randomized
+ * for the duration of the component or until data changes.
+ *
+ * Switch out the real TableBody for this while query data is `loading` or `stale`,
+ * and set `rows` from the query's stale data. This makes the number of rows feel
+ * consistent.
+ */
+
+const useSkeletonTableBodyStyles = makeStyles({
+ fillTableCell: {
+ width: "100%",
+ },
+ nonInteractive: {
+ pointerEvents: "none",
+ },
+ // Simulates TableSelectionCell which is 44px with a centered 32px Checkbox.
+ // The inline padding is (44 - skeleton size) / 2
+ // (TableLayoutCell has 8px inline padding by default.)
+ fakeSelectionCell: {
+ width: "44px",
+ maxWidth: "44px",
+ boxSizing: "border-box",
+ ...shorthands.paddingInline("12px"),
+ },
+});
+
+// `undefined` doesn't set a width and can be used in place of TableSelectionCell
+type ColumnWidth = undefined | keyof ReturnType;
+
+const minRandomRows = 3;
+const maxRandomRows = 10;
+
+interface SkeletonTableBodyProps {
+ rows?: number;
+ rowType?: keyof ReturnType;
+ widths: Array;
+}
+
+// should be wrapped by according to docs,
+// however this is only necessary if overriding `animation` or `appearance`.
+// seems to work at any level above in the hierachy,
+// so it could perhaps go outside the whole Table to avoid interfering with the
+// layout, although this is less of a problem when using `noNativeElements`.
+export function SkeletonTableBody(
+ { rows, rowType = "normal", widths }: SkeletonTableBodyProps
+) {
+ const rowCount = useMemo(
+ () =>
+ rows
+ || (minRandomRows
+ + Math.floor((maxRandomRows - minRandomRows + 1) * Math.random())),
+ [rows]
+ );
+ const rowKeys = Array.from({ length: rowCount }, (_, k) => k);
+
+ const styles = useSkeletonTableBodyStyles();
+ const rowStyles = useRowStyles();
+ const rowStyle = mergeClasses(rowStyles[rowType], styles.nonInteractive);
+
+ const columnStyles = useColumnStyles();
+ return (
+
+ {rowKeys.map((rowKey) => (
+
+ {widths.map((w, cellKey) => {
+ const hasDefinedWidth = !!(w && columnStyles[w]);
+ if (hasDefinedWidth) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+
+ );
+ })}
+
+ ))}
+
+ );
+}
+
+export const TableExampleAsJson = `
+import { usePageController } from "@axiscommunications/fluent-hooks";
+import {
+ useColumnStyles,
+ useRowStyles,
+ useTableStyles,
+} from "@axiscommunications/fluent-styles";
+import {
+ Button,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ MenuPopover,
+ MenuTrigger,
+ SkeletonItem,
+ Table,
+ TableBody,
+ TableCell,
+ TableCellLayout,
+ TableHeader,
+ TableHeaderCell,
+ TableRow,
+ TableSelectionCell,
+ makeStyles,
+ mergeClasses,
+ shorthands,
+ tokens,
+} from "@fluentui/react-components";
+import {
+ ChevronLeft16Filled,
+ ChevronLeft16Regular,
+ ChevronRight16Filled,
+ ChevronRight16Regular,
+ bundleIcon,
+} from "@fluentui/react-icons";
+import React, { useMemo, useState } from "react";
+import { useAppContext } from "../../context/ApplicationStateProvider";
+
+const users = [
+ { user: "Robin", role: "Admin", luckyNumber: 1337 },
+ { user: "Batman", role: "Hero", luckyNumber: 7 },
+ { user: "Alfred", role: "Butler", luckyNumber: 9 },
+ { user: "Joker", role: "Villain", luckyNumber: 4 },
+ { user: "Harley Quinn", role: "Villain", luckyNumber: 5 },
+ { user: "Bane", role: "Villain", luckyNumber: 6 },
+ { user: "Poison Ivy", role: "Villain", luckyNumber: 7 },
+ { user: "Vicky Vale", role: "Reporter", luckyNumber: 22 },
+ { user: "Jim Gordon", role: "Commissioner", luckyNumber: 13 },
+];
+
+const pageSizes = [5, 7, 15];
+const ChevronLeft = bundleIcon(ChevronLeft16Filled, ChevronLeft16Regular);
+const ChevronRight = bundleIcon(ChevronRight16Filled, ChevronRight16Regular);
+
+const useStyles = makeStyles({
+ root: {
+ display: "flex",
+ flexDirection: "column",
+ ...shorthands.gap(tokens.spacingVerticalM)
+ }
+})
+
+export function TableExample() {
+ const [skip, setSkip] = useState(0);
+ const [take, setTake] = useState(pageSizes[0]);
+ const [loading, setLoading] = useState(false)
+ const [total /*, setTotal*/] = useState(users.length);
+
+ const pageController = usePageController({ total, skip, take, setSkip });
+ const page = users.slice(skip, skip + take);
+
+ const tableStyles = useTableStyles();
+ const rowStyles = useRowStyles();
+ const columnStyles = useColumnStyles();
+ const styles = useStyles()
+
+ return (
+
+
+ setLoading(prev => !prev)}>Toggle loading
+
+
+
+
+
+
+ User
+
+
+ Role
+
+
+ Lucky number
+
+
+
+ {loading
+ ? (
+
+ )
+ : (
+
+ {page.map((rowContent, index) => (
+
+
+
+ {rowContent.user}
+
+
+ {rowContent.role}
+
+
+ {rowContent.luckyNumber}
+
+
+ ))}
+
+ )}
+
+
+
+
+ );
+}
+
+type TableFooterProps =
+ & Pick<
+ ReturnType,
+ "currentPage" | "totalPages" | "nextPage" | "prevPage" | "goToPage"
+ >
+ & { total: number; take: number; setTake: (take: number) => void };
+
+const useTableFooterStyles = makeStyles({
+ root: {
+ display: "flex",
+ justifyContent: "space-between",
+ marginTop: tokens.spacingVerticalM,
+ },
+ rowsSelectors: {
+ display: "flex",
+ alignItems: "center",
+ ...shorthands.gap(tokens.spacingHorizontalM),
+ },
+ pagesSelectors: { display: "flex" },
+});
+
+function TableFooter({
+ total,
+ currentPage,
+ totalPages,
+ take,
+ setTake,
+ nextPage,
+ prevPage,
+ goToPage,
+}: TableFooterProps) {
+ const dir = useAppContext((context) => context.dir);
+ const styles = useTableFooterStyles();
+ const from = currentPage * take + 1;
+ const to = Math.min(from + take - 1, total);
+
+ return (
+
+
+ Showing rows {from}-{to} of {total}
+
+
+
+ Rows per page: {take}
+
+
+
+
+ {pageSizes.map((size) => {
+ return (
+ {
+ setTake(size);
+ }}
+ >
+ {size}
+
+ );
+ })}
+
+
+
+
+
+
+
+
+ Page: {currentPage + 1} of {totalPages}
+
+
+
+
+ {Array(totalPages)
+ .fill(0)
+ .map((_, index) => {
+ return (
+ {
+ goToPage(index);
+ }}
+ >
+ {index + 1}
+
+ );
+ })}
+
+
+
+ {dir === "ltr"
+ ? (
+ <>
+ }
+ />
+ }
+ />
+ >
+ )
+ : (
+ <>
+ }
+ />
+ }
+ />
+ >
+ )}
+
+
+ );
+}
+
+const useSkeletonTableBodyStyles = makeStyles({
+ fillTableCell: {
+ width: "100%",
+ },
+ nonInteractive: {
+ pointerEvents: "none",
+ },
+ // Simulates TableSelectionCell which is 44px with a centered 32px Checkbox.
+ // The inline padding is (44 - skeleton size) / 2
+ // (TableLayoutCell has 8px inline padding by default.)
+ fakeSelectionCell: {
+ width: "44px",
+ maxWidth: "44px",
+ boxSizing: "border-box",
+ ...shorthands.paddingInline("12px"),
+ },
+});
+
+type ColumnWidth = undefined | keyof ReturnType;
+
+const minRandomRows = 3;
+const maxRandomRows = 10;
+
+interface SkeletonTableBodyProps {
+ rows?: number;
+ rowType?: keyof ReturnType;
+ widths: Array;
+}
+
+export function SkeletonTableBody(
+ { rows, rowType = "normal", widths }: SkeletonTableBodyProps
+) {
+ const rowCount = useMemo(
+ () =>
+ rows
+ || (minRandomRows
+ + Math.floor((maxRandomRows - minRandomRows + 1) * Math.random())),
+ [rows]
+ );
+ const rowKeys = Array.from({ length: rowCount }, (_, k) => k);
+
+ const styles = useSkeletonTableBodyStyles();
+ const rowStyles = useRowStyles();
+ const rowStyle = mergeClasses(rowStyles[rowType], styles.nonInteractive);
+
+ const columnStyles = useColumnStyles();
+ return (
+
+ {rowKeys.map((rowKey) => (
+
+ {widths.map((w, cellKey) => {
+ const hasDefinedWidth = !!(w && columnStyles[w]);
+ if (hasDefinedWidth) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+
+ );
+ })}
+
+ ))}
+
+ );
+}
+`;
diff --git a/examples/src/stories/table-utilities/table-utlities-page.tsx b/examples/src/stories/table-utilities/table-utlities-page.tsx
new file mode 100644
index 00000000..34d44dc8
--- /dev/null
+++ b/examples/src/stories/table-utilities/table-utlities-page.tsx
@@ -0,0 +1,37 @@
+import React from "react";
+import { StoryPage } from "../../components/story/story-page";
+import {
+ pageData,
+ useExampleWithNavigation,
+} from "../../components/story/story.utils";
+import { getGhInfoByKey } from "../../routing/route-map";
+import { routes } from "../../routing/routes";
+import { TableExample, TableExampleAsJson } from "./table-example";
+
+const examples: pageData[] = [
+ {
+ title: "Default",
+ anchor: "TableExample",
+ example: ,
+ codeString: TableExampleAsJson,
+ },
+];
+
+export const TableUtilitiesPage = () => {
+ const gh = getGhInfoByKey(routes.TableUtilities);
+ const { renderSections, renderNavigation } = useExampleWithNavigation(
+ examples
+ );
+
+ return (
+
+ {renderSections}
+
+ );
+};
diff --git a/examples/src/stories/table-utlities-page.tsx b/examples/src/stories/table-utlities-page.tsx
deleted file mode 100644
index 23045faa..00000000
--- a/examples/src/stories/table-utlities-page.tsx
+++ /dev/null
@@ -1,358 +0,0 @@
-import { usePageController } from "@axiscommunications/fluent-hooks";
-import {
- useColumnStyles,
- useRowStyles,
- useTableStyles,
-} from "@axiscommunications/fluent-styles";
-import {
- Button,
- makeStyles,
- Menu,
- MenuButton,
- MenuItem,
- MenuList,
- MenuPopover,
- MenuTrigger,
- mergeClasses,
- shorthands,
- SkeletonItem,
- TableSelectionCell,
- tokens,
-} from "@fluentui/react-components";
-import {
- Table,
- TableBody,
- TableCell,
- TableCellLayout,
- TableHeader,
- TableHeaderCell,
- TableRow,
-} from "@fluentui/react-components";
-import {
- bundleIcon,
- ChevronLeft16Filled,
- ChevronLeft16Regular,
- ChevronRight16Filled,
- ChevronRight16Regular,
-} from "@fluentui/react-icons";
-import React, { useEffect, useMemo, useState } from "react";
-import { PageHeader } from "../components/page-header";
-import { useAppContext } from "../context/ApplicationStateProvider";
-import { useLayoutStyles, useScrollPageStyle } from "../styles/page";
-
-const users = [
- { user: "Robin", role: "Admin", luckyNumber: 1337 },
- { user: "Batman", role: "Hero", luckyNumber: 7 },
- { user: "Alfred", role: "Butler", luckyNumber: 9 },
- { user: "Joker", role: "Villain", luckyNumber: 4 },
- { user: "Harley Quinn", role: "Villain", luckyNumber: 5 },
- { user: "Bane", role: "Villain", luckyNumber: 6 },
- { user: "Poison Ivy", role: "Villain", luckyNumber: 7 },
- { user: "Vicky Vale", role: "Reporter", luckyNumber: 22 },
- { user: "Jim Gordon", role: "Commissioner", luckyNumber: 13 },
-];
-
-const pageSizes = [5, 7, 15];
-const ChevronLeft = bundleIcon(ChevronLeft16Filled, ChevronLeft16Regular);
-const ChevronRight = bundleIcon(ChevronRight16Filled, ChevronRight16Regular);
-
-export const TableUtilitiesPage = () => {
- const scrollPageStyle = useScrollPageStyle();
- const layoutStyles = useLayoutStyles();
- const [skip, setSkip] = useState(0);
- const [take, setTake] = useState(pageSizes[0]);
- const [total /*, setTotal*/] = useState(users.length);
-
- const pageController = usePageController({ total, skip, take, setSkip });
-
- const [loading, setLoading] = useState(false);
- useEffect(() => {
- if (loading) {
- const simulatedLoadingDuration = 700;
- const id = setTimeout(() => setLoading(false), simulatedLoadingDuration);
- return () => clearTimeout(id);
- }
- }, [loading]);
-
- return (
-
-
-
-
-
-
- setLoading(true)}>Reload data
-
-
-
- );
-};
-
-function TableExample(
- { loading, skip, take }: { loading: boolean; skip: number; take: number }
-) {
- const tableStyles = useTableStyles();
- const rowStyles = useRowStyles();
- const columnStyles = useColumnStyles();
-
- const page = users.slice(skip, skip + take);
-
- return (
-
-
-
-
-
- User
-
-
- Role
-
-
- Lucky number
-
-
-
- {loading
- ? (
-
- )
- : (
-
- {page.map((rowContent, index) => (
-
-
-
- {rowContent.user}
-
-
- {rowContent.role}
-
-
- {rowContent.luckyNumber}
-
-
- ))}
-
- )}
-
- );
-}
-
-type TableFooterProps =
- & Pick<
- ReturnType,
- "currentPage" | "totalPages" | "nextPage" | "prevPage" | "goToPage"
- >
- & { total: number; take: number; setTake: (take: number) => void };
-
-const useTableFooterStyles = makeStyles({
- root: {
- display: "flex",
- justifyContent: "space-between",
- marginTop: tokens.spacingVerticalM,
- },
- rowsSelectors: {
- display: "flex",
- alignItems: "center",
- ...shorthands.gap(tokens.spacingHorizontalM),
- },
- pagesSelectors: { display: "flex" },
-});
-
-function TableFooter({
- total,
- currentPage,
- totalPages,
- take,
- setTake,
- nextPage,
- prevPage,
- goToPage,
-}: TableFooterProps) {
- const dir = useAppContext((context) => context.dir);
- const styles = useTableFooterStyles();
- const from = currentPage * take + 1;
- const to = Math.min(from + take - 1, total);
-
- return (
-
-
- Showing rows {from}-{to} of {total}
-
-
-
- Rows per page: {take}
-
-
-
-
- {pageSizes.map((size) => {
- return (
- {
- setTake(size);
- }}
- >
- {size}
-
- );
- })}
-
-
-
-
-
-
-
-
- Page: {currentPage + 1} of {totalPages}
-
-
-
-
- {Array(totalPages)
- .fill(0)
- .map((_, index) => {
- return (
- {
- goToPage(index);
- }}
- >
- {index + 1}
-
- );
- })}
-
-
-
- {dir === "ltr"
- ? (
- <>
- }
- />
- }
- />
- >
- )
- : (
- <>
- }
- />
- }
- />
- >
- )}
-
-
- );
-}
-
-/**
- * Skeleton TableBody
- * Renders a skeleton table matching the specified column widths.
- * The number of rows will be used if set and non-zero, otherwise randomized
- * for the duration of the component or until data changes.
- *
- * Switch out the real TableBody for this while query data is `loading` or `stale`,
- * and set `rows` from the query's stale data. This makes the number of rows feel
- * consistent.
- */
-
-const useSkeletonTableBodyStyles = makeStyles({
- fillTableCell: {
- width: "100%",
- },
- nonInteractive: {
- pointerEvents: "none",
- },
- // Simulates TableSelectionCell which is 44px with a centered 32px Checkbox.
- // The inline padding is (44 - skeleton size) / 2
- // (TableLayoutCell has 8px inline padding by default.)
- fakeSelectionCell: {
- width: "44px",
- maxWidth: "44px",
- boxSizing: "border-box",
- ...shorthands.paddingInline("12px"),
- },
-});
-
-// `undefined` doesn't set a width and can be used in place of TableSelectionCell
-type ColumnWidth = undefined | keyof ReturnType;
-
-const minRandomRows = 3;
-const maxRandomRows = 10;
-
-interface SkeletonTableBodyProps {
- rows?: number;
- rowType?: keyof ReturnType;
- widths: Array;
-}
-
-// should be wrapped by according to docs,
-// however this is only necessary if overriding `animation` or `appearance`.
-// seems to work at any level above in the hierachy,
-// so it could perhaps go outside the whole Table to avoid interfering with the
-// layout, although this is less of a problem when using `noNativeElements`.
-export function SkeletonTableBody(
- { rows, rowType = "normal", widths }: SkeletonTableBodyProps
-) {
- const rowCount = useMemo(
- () =>
- rows
- || (minRandomRows
- + Math.floor((maxRandomRows - minRandomRows + 1) * Math.random())),
- [rows]
- );
- const rowKeys = Array.from({ length: rowCount }, (_, k) => k);
-
- const styles = useSkeletonTableBodyStyles();
- const rowStyles = useRowStyles();
- const rowStyle = mergeClasses(rowStyles[rowType], styles.nonInteractive);
-
- const columnStyles = useColumnStyles();
- return (
-
- {rowKeys.map((rowKey) => (
-
- {widths.map((w, cellKey) => {
- const hasDefinedWidth = !!(w && columnStyles[w]);
- if (hasDefinedWidth) {
- return (
-
-
-
- );
- }
- return (
-
-
-
- );
- })}
-
- ))}
-
- );
-}
diff --git a/examples/src/stories/theme-page.tsx b/examples/src/stories/theme-page.tsx
index 212a8f6c..95fb3322 100644
--- a/examples/src/stories/theme-page.tsx
+++ b/examples/src/stories/theme-page.tsx
@@ -21,16 +21,15 @@ import {
TabList,
Theme,
} from "@fluentui/react-components";
-import { useAppContext } from "../context/ApplicationStateProvider";
import { DarkThemeRegular } from "@fluentui/react-icons";
import React, { memo, useCallback, useState } from "react";
-import { PageHeader } from "../components/page-header";
import { SimpleHeader } from "../components/simple-header";
-import {
- useFixedPageStyle,
- useLayoutStyles,
- useScrollPageStyle,
-} from "../styles/page";
+import { StoryPage } from "../components/story/story-page";
+import { StorySection } from "../components/story/story-section";
+import { useAppContext } from "../context/ApplicationStateProvider";
+import { getGhInfoByKey } from "../routing/route-map";
+import { routes } from "../routing/routes";
+import { useLayoutStyles, useScrollPageStyle } from "../styles/page";
const useStyles = makeStyles({
tablist: {
@@ -133,16 +132,17 @@ const tokenVariant = {
status: "colorStatus",
} as const;
-type TtokenVariant = typeof tokenVariant[keyof typeof tokenVariant];
+type TTokenVariant = typeof tokenVariant[keyof typeof tokenVariant];
export const ThemePage = () => {
+ const gh = getGhInfoByKey(routes.Theme);
+
const styles = useStyles();
- const fixedPageStyle = useFixedPageStyle();
const scrollPageStyle = useScrollPageStyle();
const layoutStyles = useLayoutStyles();
const [selectedTab, setSelectedTab] = useState(axisThemes.main);
- const [selectedVariant, setSelectedVariant] = useState(
+ const [selectedVariant, setSelectedVariant] = useState(
tokenVariant.brand
);
@@ -158,7 +158,7 @@ export const ThemePage = () => {
const onVariantSelect = useCallback(
(_: SelectTabEvent, { value }: SelectTabData) =>
- setSelectedVariant(value as TtokenVariant),
+ setSelectedVariant(value as TTokenVariant),
[]
);
@@ -173,13 +173,13 @@ export const ThemePage = () => {
}, [selectedTab, setAppTheme]);
return (
-
-
-
+
+
@@ -234,7 +234,7 @@ export const ThemePage = () => {
/>
-
-
+
+
);
};
diff --git a/examples/src/stories/vertical-stepper-page.tsx b/examples/src/stories/vertical-stepper-page.tsx
deleted file mode 100644
index b8897c79..00000000
--- a/examples/src/stories/vertical-stepper-page.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, { useCallback, useState } from "react";
-
-import { Stepper, StepperDialog } from "@axiscommunications/fluent-stepper";
-import { mergeClasses } from "@fluentui/react-components";
-import { PageHeader } from "../components/page-header";
-import { SectionTitle } from "../components/section-title";
-import { steps } from "./stepper-page";
-import { useLayoutStyles, useScrollPageStyle } from "../styles/page";
-
-export const VerticalStepperPage = () => {
- const [step, setStep] = useState(0);
- const scrollPageStyle = useScrollPageStyle();
- const layoutStyles = useLayoutStyles();
- const onFinish = useCallback(() => alert("Finish!"), []);
- const onCancel = useCallback(() => setStep(0), []);
- return (
-
- );
-};