diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index c206d79..b268636 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -22,7 +22,8 @@
"eamodio.gitlens",
"wayou.vscode-todo-highlight",
"PKief.material-icon-theme",
- "yoavbls.pretty-ts-errors"
+ "yoavbls.pretty-ts-errors",
+ "unifiedjs.vscode-mdx"
]
}
},
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 738aa22..716f75c 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -50,8 +50,6 @@ extends:
- "plugin:jest/style"
# -- STORYBOOK --
# NOTE: Quote from docs: "This plugin will only be applied to files following the *.stories.* (we recommend this) or *.story.* pattern."
- # Its recommended to lint also the config files in .storybook, which in our current file context, is painfull to achieve, so this is emmited.
- # It would not have made any major difference anyhow, can only help with mistyped addon names, we will survive without it...
- "plugin:storybook/recommended"
# --------------------------- Formatting -------------------------
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..bf357fb
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "trailingComma": "all"
+}
diff --git a/.storybook/main.ts b/.storybook/main.ts
index 9346b83..7e085f9 100644
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
@@ -1,7 +1,7 @@
import { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
- stories: ["../**/stories.@(ts|tsx)"],
+ stories: ["../**/stories.@(ts|tsx)", "../**/readme.mdx"],
addons: ["@storybook/addon-essentials"],
framework: "@storybook/react-vite",
};
diff --git a/package-lock.json b/package-lock.json
index e9c8abf..9d3d030 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "fluentui-helpers",
- "version": "0.0.1",
+ "version": "0.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fluentui-helpers",
- "version": "0.0.1",
+ "version": "0.0.3",
"license": "MIT",
"devDependencies": {
"@babel/preset-env": "^7.25.9",
diff --git a/package.json b/package.json
index 06a8a14..75344b6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fluentui-helpers",
- "version": "0.0.3",
+ "version": "0.1.0",
"description": "Helper library for microsofts fluentui react library",
"main": "src/index.ts",
"author": "bubulus",
diff --git a/readme.md b/readme.md
index bda3328..e4769d0 100644
--- a/readme.md
+++ b/readme.md
@@ -1,3 +1,29 @@
-TEST FOR PUBLISHING; UNSTABLE DO NOT USE
+**Disclaimer:** _I am not affiliated in any way with Microsoft or Fluent UI. This is **NOT** an official repository from the Fluent UI ecosystem_.
-FURTHER REL FOR PIPELINE TEST 0.0.2
+### Who is this for?
+
+This library is aimed at folks who work with Microsoft's Fluent UI library but think it lacks some features here and there.
+
+### Is this stable?
+
+Not quite yet, from `1.*` onward it will be, as long as its in `0.*` consider it unstable.
+
+### What does it contain?
+
+It contains helpful hooks, components, and theme utilities that I was maintaining across different projects. I decided to create this central repository to share these utilities.
+
+These utilities can be common layout utilities (like `Flex`, `Grid`, etc.), generic and library-specific hooks, extended theme tokens, common animations, and also full-fledged components that are not yet available in Fluent UI itself (like `Pagination`, for instance).
+
+### Where can I find the docs?
+
+The library documentation resides at a dedicated [Storybook](https://bubulux.github.io/fluentui-helpers). You will also find planned updates and preview components there.
+
+### How long will I be committed to this?
+
+I am maintaining two larger projects with Fluent UI, one private and one for my company, so there are long-lasting factors that will bind me to this for a while.
+
+### How to contribute or report bugs?
+
+If there are any bugs, please open an issue on GitHub.
+
+If you want to contribute (other than a bug fix) or suggest a change, open a discussion thread :)
diff --git a/src/components/layout/Flex/component.tsx b/src/components/layout/Flex/component.tsx
new file mode 100644
index 0000000..dac19ca
--- /dev/null
+++ b/src/components/layout/Flex/component.tsx
@@ -0,0 +1,121 @@
+import React from "react";
+import type { ReactNode } from "react";
+
+import { mergeClasses } from "@fluentui/react-components";
+import type { TThemeSpacing, TThemeShorthandSpacing } from "@theme";
+
+import {
+ useGap,
+ useFlexBox,
+ useMargin,
+ usePadding,
+ useShorthandDimension,
+ useAriaProps,
+ usePosition,
+} from "@components/layout/Flex/hooks";
+
+import type {
+ TFlexDirection,
+ TFlexOption,
+ TFlexShorthandDimensions,
+ TFlexPosition,
+} from "@components/layout/Flex/types";
+
+type TProps = {
+ children: ReactNode;
+ position?: TFlexPosition;
+ direction?: TFlexDirection;
+ justifyContent?: TFlexOption;
+ alignItems?: TFlexOption;
+ wrap?: boolean;
+ className?: string;
+ gap?: TThemeSpacing;
+ margin?: TThemeShorthandSpacing;
+ padding?: TThemeShorthandSpacing;
+ shWidth?: TFlexShorthandDimensions;
+ shHeight?: TFlexShorthandDimensions;
+ testId?: string;
+};
+
+/**
+ * @description
+ * - fluent does not provide a `Flex` component for consistent layout (it was removed in the latest version)
+ * - having this allows to use fewer makeStyles call and repeting flex configurations in the code
+ * - its especially usefull when certain layout styles have to be applied conditionally
+ * - for this the entire conditional logic is abstracted inside this component, providing very much styled-component like ergonomics
+ * - supports direct data-testid prop as well as all aria props
+ *
+ * @props
+ * - `direction`: flex-direction property
+ * - `justifyContent`: justify-content property
+ * - `alignItems`: align-items property
+ * - `wrap`: flex-wrap property
+ * - `gap`: gap between children, with fixed predefined values from the design system, not discriminating between horizontal and vertical gap (because there are literally the same values)
+ * - `margin`: margin property, using the same values like gap, expects the shorthand notation
+ * - `padding`: same like margin, but for padding, concrete example below
+ *
+ * ```jsx
+ * // the shorthand is not a simple string, but rather defined as an array that can be of size 1 up to 4
+ * // each element provides additional restraint from the design system tokens
+ * // following examples will only use padding, but the same applies to margin
+ * // the * between tokens.spacing and the further specifier can be interpreted as either horizontal or vertical (both the same values, reasoning is explained in gap comment above)
+ *
+ * // like saying padding: tokens.spacing*S;
+ * // like saying padding: `${tokens.spacing*S} ${tokens.spacing*M}`;
+ * // like saying padding: `${tokens.spacing*S} ${tokens.spacing*M} ${tokens.spacing*L}`;
+ * // like saying padding: `${tokens.spacing*S} ${tokens.spacing*M} ${tokens.spacing*L} ${tokens.spacing*XL}`;
+ *
+ * ```
+ * - `shWidth`: shorthand for width property
+ * - `shHeight`: shorthand for height property
+ * - `className`: to add additional classes to the component, will override all specified styles from props
+ * - `aria-*`: all aria props are supported, they will be spread on the root div
+ * - `testId`: passed down the data-testid attribute
+ *
+ *
+ * @default
+ * direction = "row", justifyContent = "start", alignItems = "start", wrap = false, gap = "None", margin = ["None"], padding = ["None"], shHeight = "auto", shWidth = "auto"
+ */
+export default function Flex({
+ direction = "row",
+ position = "static",
+ justifyContent = "start",
+ alignItems = "start",
+ wrap = false,
+ gap = "None",
+ margin = ["None"],
+ padding = ["None"],
+ shHeight = "auto",
+ shWidth = "auto",
+ className = undefined,
+ testId = undefined,
+ children,
+ ...rest
+}: TProps) {
+ const flexBoxClass = useFlexBox(justifyContent, alignItems, direction, wrap);
+ const gapClass = useGap(gap);
+ const marginClass = useMargin(margin);
+ const paddingClass = usePadding(padding);
+ const dimensionClass = useShorthandDimension(shWidth, shHeight);
+ const positionClass = usePosition(position);
+ const ariaProps = useAriaProps(rest);
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/layout/Flex/func.tsx b/src/components/layout/Flex/func.tsx
deleted file mode 100644
index c96bcd7..0000000
--- a/src/components/layout/Flex/func.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Flex() {
- return Flex
;
-}
diff --git a/src/components/layout/Flex/hooks/index.ts b/src/components/layout/Flex/hooks/index.ts
new file mode 100644
index 0000000..47af25e
--- /dev/null
+++ b/src/components/layout/Flex/hooks/index.ts
@@ -0,0 +1,17 @@
+import useGap from "@components/layout/Flex/hooks/useGap";
+import useMargin from "@components/layout/Flex/hooks/useMargin";
+import usePadding from "@components/layout/Flex/hooks/usePadding";
+import useFlexBox from "@components/layout/Flex/hooks/useFlexBox";
+import useShorthandDimension from "@components/layout/Flex/hooks/useShorthandDimension";
+import useAriaProps from "@components/layout/Flex/hooks/useAriaProps";
+import usePosition from "@components/layout/Flex/hooks/usePosition";
+
+export {
+ useGap,
+ useMargin,
+ usePadding,
+ useFlexBox,
+ useShorthandDimension,
+ useAriaProps,
+ usePosition,
+};
diff --git a/src/components/layout/Flex/hooks/useAriaProps.ts b/src/components/layout/Flex/hooks/useAriaProps.ts
new file mode 100644
index 0000000..7e187c8
--- /dev/null
+++ b/src/components/layout/Flex/hooks/useAriaProps.ts
@@ -0,0 +1,13 @@
+type TAriaAttributes = Record;
+
+export default function useAriaAprops(
+ props: Record,
+): TAriaAttributes {
+ return Object.keys(props).reduce((acc, key) => {
+ if (key.startsWith("aria-")) {
+ // @ts-expect-error - we know that key is a string, its just difficult to prove to TS by doing key.startsWith
+ acc[key] = props[key];
+ }
+ return acc;
+ }, {});
+}
diff --git a/src/components/layout/Flex/hooks/useFlexBox.ts b/src/components/layout/Flex/hooks/useFlexBox.ts
new file mode 100644
index 0000000..8cebc82
--- /dev/null
+++ b/src/components/layout/Flex/hooks/useFlexBox.ts
@@ -0,0 +1,36 @@
+import { mergeClasses } from "@fluentui/react-components";
+
+import { useFlexBoxClasses } from "@components/layout/Flex/styles";
+
+import type {
+ TFlexDirection,
+ TFlexOption,
+} from "@components/layout/Flex/types";
+
+export default function useFlexBox(
+ justifyContent?: TFlexOption,
+ alignItems?: TFlexOption,
+ direction?: TFlexDirection,
+ wrap?: boolean,
+) {
+ const classes = useFlexBoxClasses();
+ const directionClass = direction
+ ? classes[`${direction}Direction`]
+ : undefined;
+ const justifyContentClass = justifyContent
+ ? classes[`${justifyContent}Content`]
+ : undefined;
+ const alignItemsClass = alignItems
+ ? classes[`${alignItems}Items`]
+ : undefined;
+
+ const wrapClass = wrap ? classes.wrap : classes.nowrap;
+
+ return mergeClasses(
+ classes.base,
+ directionClass,
+ justifyContentClass,
+ alignItemsClass,
+ wrapClass,
+ );
+}
diff --git a/src/components/layout/Flex/hooks/useGap.ts b/src/components/layout/Flex/hooks/useGap.ts
new file mode 100644
index 0000000..8809c84
--- /dev/null
+++ b/src/components/layout/Flex/hooks/useGap.ts
@@ -0,0 +1,9 @@
+import { useGapClasses } from "@components/layout/Flex/styles";
+import type { TThemeSpacing } from "@theme";
+
+function useGap(gap?: TThemeSpacing) {
+ const classes = useGapClasses();
+ return gap ? classes[`gap${gap}`] : undefined;
+}
+
+export default useGap;
diff --git a/src/components/layout/Flex/hooks/useMargin.ts b/src/components/layout/Flex/hooks/useMargin.ts
new file mode 100644
index 0000000..799f764
--- /dev/null
+++ b/src/components/layout/Flex/hooks/useMargin.ts
@@ -0,0 +1,39 @@
+import { useMarginClasses } from "@components/layout/Flex/styles";
+import { mergeClasses } from "@fluentui/react-components";
+
+import type { TThemeShorthandSpacing } from "@theme";
+
+function useMargin(margin?: TThemeShorthandSpacing) {
+ const classes = useMarginClasses();
+
+ if (margin === undefined) {
+ return "noMarginValue";
+ }
+ if (margin.length === 1) {
+ return classes[`margin${margin[0]}`];
+ }
+ if (margin.length === 2) {
+ return mergeClasses(
+ classes[`marginTop${margin[0]}`],
+ classes[`marginRight${margin[1]}`],
+ classes[`marginBottom${margin[0]}`],
+ classes[`marginLeft${margin[1]}`],
+ );
+ }
+ if (margin.length === 3) {
+ return mergeClasses(
+ classes[`marginTop${margin[0]}`],
+ classes[`marginRight${margin[1]}`],
+ classes[`marginBottom${margin[2]}`],
+ classes[`marginLeft${margin[1]}`],
+ );
+ }
+ return mergeClasses(
+ classes[`marginTop${margin[0]}`],
+ classes[`marginRight${margin[1]}`],
+ classes[`marginBottom${margin[2]}`],
+ classes[`marginLeft${margin[3]}`],
+ );
+}
+
+export default useMargin;
diff --git a/src/components/layout/Flex/hooks/usePadding.ts b/src/components/layout/Flex/hooks/usePadding.ts
new file mode 100644
index 0000000..bbaa4d6
--- /dev/null
+++ b/src/components/layout/Flex/hooks/usePadding.ts
@@ -0,0 +1,39 @@
+import { usePaddingClasses } from "@components/layout/Flex/styles";
+import { mergeClasses } from "@fluentui/react-components";
+
+import type { TThemeShorthandSpacing } from "@theme";
+
+function usePadding(padding?: TThemeShorthandSpacing) {
+ const classes = usePaddingClasses();
+
+ if (padding === undefined) {
+ return "noPaddingValue";
+ }
+ if (padding.length === 1) {
+ return classes[`padding${padding[0]}`];
+ }
+ if (padding.length === 2) {
+ return mergeClasses(
+ classes[`paddingTop${padding[0]}`],
+ classes[`paddingRight${padding[1]}`],
+ classes[`paddingBottom${padding[0]}`],
+ classes[`paddingLeft${padding[1]}`],
+ );
+ }
+ if (padding.length === 3) {
+ return mergeClasses(
+ classes[`paddingTop${padding[0]}`],
+ classes[`paddingRight${padding[1]}`],
+ classes[`paddingBottom${padding[2]}`],
+ classes[`paddingLeft${padding[1]}`],
+ );
+ }
+ return mergeClasses(
+ classes[`paddingTop${padding[0]}`],
+ classes[`paddingRight${padding[1]}`],
+ classes[`paddingBottom${padding[2]}`],
+ classes[`paddingLeft${padding[3]}`],
+ );
+}
+
+export default usePadding;
diff --git a/src/components/layout/Flex/hooks/usePosition.ts b/src/components/layout/Flex/hooks/usePosition.ts
new file mode 100644
index 0000000..de36b5e
--- /dev/null
+++ b/src/components/layout/Flex/hooks/usePosition.ts
@@ -0,0 +1,8 @@
+import type { TFlexPosition } from "@components/layout/Flex/types";
+
+import { usePositionClasses } from "@components/layout/Flex/styles";
+
+export default function usePosition(position: TFlexPosition) {
+ const classes = usePositionClasses();
+ return classes[position];
+}
diff --git a/src/components/layout/Flex/hooks/useShorthandDimension.ts b/src/components/layout/Flex/hooks/useShorthandDimension.ts
new file mode 100644
index 0000000..1ffb935
--- /dev/null
+++ b/src/components/layout/Flex/hooks/useShorthandDimension.ts
@@ -0,0 +1,17 @@
+import { mergeClasses } from "@fluentui/react-components";
+
+import { useDimensionClasses } from "@components/layout/Flex/styles";
+
+import type { TFlexShorthandDimensions } from "@components/layout/Flex/types";
+
+export default function useShorthandDimension(
+ shorthandWidth: TFlexShorthandDimensions,
+ shorthandHeight: TFlexShorthandDimensions,
+) {
+ const classes = useDimensionClasses();
+
+ const widthClass = classes[`${shorthandWidth}Width`];
+ const heightClass = classes[`${shorthandHeight}Height`];
+
+ return mergeClasses(widthClass, heightClass);
+}
diff --git a/src/components/layout/Flex/index.ts b/src/components/layout/Flex/index.ts
new file mode 100644
index 0000000..e26aa32
--- /dev/null
+++ b/src/components/layout/Flex/index.ts
@@ -0,0 +1,3 @@
+import Flex from "@components/layout/Flex/component";
+
+export default Flex;
diff --git a/src/components/layout/Flex/stories.ts b/src/components/layout/Flex/stories.ts
deleted file mode 100644
index b31361b..0000000
--- a/src/components/layout/Flex/stories.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react";
-
-import Flex from "@components/layout/Flex/func";
-
-const meta: Meta = {
- title: "Components/Layout/Flex",
- component: Flex,
- args: {},
-};
-
-export default meta;
-
-type Story = StoryObj;
-
-export const Index: Story = {};
diff --git a/src/components/layout/Flex/stories.tsx b/src/components/layout/Flex/stories.tsx
new file mode 100644
index 0000000..5ec2523
--- /dev/null
+++ b/src/components/layout/Flex/stories.tsx
@@ -0,0 +1,79 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { Flex } from "@components";
+import { makeStyles } from "@fluentui/react-components";
+
+import type { TThemeSpacing } from "@theme";
+
+const meta: Meta = {
+ component: Flex,
+ title: "Components/Layout/Flex",
+ args: {
+ children: (
+ <>
+
+ Child-1
+
+
+ Child-2
+
+ >
+ ),
+ },
+ argTypes: {
+ className: { control: false },
+ children: { control: false },
+ gap: {
+ control: "select",
+ options: [
+ "XXS",
+ "XS",
+ "SNudge",
+ "S",
+ "MNudge",
+ "M",
+ "L",
+ "XL",
+ "XXL",
+ "XXXL",
+ ] as TThemeSpacing[],
+ },
+ },
+};
+
+export default meta;
+
+type TStory = StoryObj;
+
+const useClasses = makeStyles({
+ root: {
+ border: "4px solid black",
+ height: "100px",
+ },
+});
+
+export const Index: TStory = {
+ args: {},
+ render: (args) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const classes = useClasses();
+ return (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ {args.children}
+
+ );
+ },
+};
diff --git a/src/components/layout/Flex/styles/dimensions.ts b/src/components/layout/Flex/styles/dimensions.ts
new file mode 100644
index 0000000..356a216
--- /dev/null
+++ b/src/components/layout/Flex/styles/dimensions.ts
@@ -0,0 +1,37 @@
+import { makeStyles } from "@fluentui/react-components";
+
+const useDimensionClasses = makeStyles({
+ autoWidth: {
+ width: "auto",
+ },
+ "25%Width": {
+ width: "25%",
+ },
+ "50%Width": {
+ width: "50%",
+ },
+ "75%Width": {
+ width: "75%",
+ },
+ "100%Width": {
+ width: "100%",
+ },
+
+ autoHeight: {
+ height: "auto",
+ },
+ "25%Height": {
+ height: "25%",
+ },
+ "50%Height": {
+ height: "50%",
+ },
+ "75%Height": {
+ height: "75%",
+ },
+ "100%Height": {
+ height: "100%",
+ },
+});
+
+export default useDimensionClasses;
diff --git a/src/components/layout/Flex/styles/flexBox.ts b/src/components/layout/Flex/styles/flexBox.ts
new file mode 100644
index 0000000..272f5a3
--- /dev/null
+++ b/src/components/layout/Flex/styles/flexBox.ts
@@ -0,0 +1,67 @@
+import { makeStyles } from "@fluentui/react-components";
+
+const useFlexBoxClasses = makeStyles({
+ base: {
+ display: "flex",
+ },
+ rowDirection: {
+ flexDirection: "row",
+ },
+ columnDirection: {
+ flexDirection: "column",
+ },
+ // justify-content
+ centerContent: {
+ justifyContent: "center",
+ },
+ startContent: {
+ justifyContent: "flex-start",
+ },
+ endContent: {
+ justifyContent: "flex-end",
+ },
+ spaceBetweenContent: {
+ justifyContent: "space-between",
+ },
+ spaceAroundContent: {
+ justifyContent: "space-around",
+ },
+ spaceEvenlyContent: {
+ justifyContent: "space-evenly",
+ },
+ stretchContent: {
+ justifyContent: "stretch",
+ },
+ // align-items
+ centerItems: {
+ alignItems: "center",
+ },
+ startItems: {
+ alignItems: "flex-start",
+ },
+ endItems: {
+ alignItems: "flex-end",
+ },
+ stretchItems: {
+ alignItems: "stretch",
+ },
+ spaceBetweenItems: {
+ alignItems: "space-between",
+ },
+ spaceAroundItems: {
+ alignItems: "space-around",
+ },
+ spaceEvenlyItems: {
+ alignItems: "space-evenly",
+ },
+
+ // wrap
+ wrap: {
+ flexWrap: "wrap",
+ },
+ nowrap: {
+ flexWrap: "nowrap",
+ },
+});
+
+export default useFlexBoxClasses;
diff --git a/src/components/layout/Flex/styles/gap.ts b/src/components/layout/Flex/styles/gap.ts
new file mode 100644
index 0000000..5ddf608
--- /dev/null
+++ b/src/components/layout/Flex/styles/gap.ts
@@ -0,0 +1,40 @@
+import { makeStyles } from "@fluentui/react-components";
+import { EThemeSpacing } from "@theme";
+
+const useGapClasses = makeStyles({
+ gapNone: {
+ gap: EThemeSpacing.None,
+ },
+ gapXXS: {
+ gap: EThemeSpacing.XXS,
+ },
+ gapXS: {
+ gap: EThemeSpacing.XS,
+ },
+ gapSNudge: {
+ gap: EThemeSpacing.SNudge,
+ },
+ gapS: {
+ gap: EThemeSpacing.S,
+ },
+ gapMNudge: {
+ gap: EThemeSpacing.MNudge,
+ },
+ gapM: {
+ gap: EThemeSpacing.M,
+ },
+ gapL: {
+ gap: EThemeSpacing.L,
+ },
+ gapXL: {
+ gap: EThemeSpacing.XL,
+ },
+ gapXXL: {
+ gap: EThemeSpacing.XXL,
+ },
+ gapXXXL: {
+ gap: EThemeSpacing.XXXL,
+ },
+});
+
+export default useGapClasses;
diff --git a/src/components/layout/Flex/styles/index.ts b/src/components/layout/Flex/styles/index.ts
new file mode 100644
index 0000000..71d4db6
--- /dev/null
+++ b/src/components/layout/Flex/styles/index.ts
@@ -0,0 +1,15 @@
+import useFlexBoxClasses from "@components/layout/Flex/styles/flexBox";
+import useMarginClasses from "@components/layout/Flex/styles/margin";
+import usePaddingClasses from "@components/layout/Flex/styles/padding";
+import useGapClasses from "@components/layout/Flex/styles/gap";
+import useDimensionClasses from "@components/layout/Flex/styles/dimensions";
+import usePositionClasses from "@components/layout/Flex/styles/position";
+
+export {
+ useDimensionClasses,
+ useFlexBoxClasses,
+ useMarginClasses,
+ usePaddingClasses,
+ useGapClasses,
+ usePositionClasses,
+};
diff --git a/src/components/layout/Flex/styles/margin.ts b/src/components/layout/Flex/styles/margin.ts
new file mode 100644
index 0000000..98a8943
--- /dev/null
+++ b/src/components/layout/Flex/styles/margin.ts
@@ -0,0 +1,172 @@
+import { makeStyles } from "@fluentui/react-components";
+import { EThemeSpacing } from "@theme";
+
+const useMarginClasses = makeStyles({
+ marginNone: {
+ margin: EThemeSpacing.None,
+ },
+ marginXXS: {
+ margin: EThemeSpacing.XXS,
+ },
+ marginXS: {
+ margin: EThemeSpacing.XS,
+ },
+ marginSNudge: {
+ margin: EThemeSpacing.SNudge,
+ },
+ marginS: {
+ margin: EThemeSpacing.S,
+ },
+ marginMNudge: {
+ margin: EThemeSpacing.MNudge,
+ },
+ marginM: {
+ margin: EThemeSpacing.M,
+ },
+ marginL: {
+ margin: EThemeSpacing.L,
+ },
+ marginXL: {
+ margin: EThemeSpacing.XL,
+ },
+ marginXXL: {
+ margin: EThemeSpacing.XXL,
+ },
+ marginXXXL: {
+ margin: EThemeSpacing.XXXL,
+ },
+ marginLeftNone: {
+ marginLeft: EThemeSpacing.None,
+ },
+ marginLeftXXS: {
+ marginLeft: EThemeSpacing.XXS,
+ },
+ marginLeftXS: {
+ marginLeft: EThemeSpacing.XS,
+ },
+ marginLeftSNudge: {
+ marginLeft: EThemeSpacing.SNudge,
+ },
+ marginLeftS: {
+ marginLeft: EThemeSpacing.S,
+ },
+ marginLeftMNudge: {
+ marginLeft: EThemeSpacing.MNudge,
+ },
+ marginLeftM: {
+ marginLeft: EThemeSpacing.M,
+ },
+ marginLeftL: {
+ marginLeft: EThemeSpacing.L,
+ },
+ marginLeftXL: {
+ marginLeft: EThemeSpacing.XL,
+ },
+ marginLeftXXL: {
+ marginLeft: EThemeSpacing.XXL,
+ },
+ marginLeftXXXL: {
+ marginLeft: EThemeSpacing.XXXL,
+ },
+ marginRightNone: {
+ marginRight: EThemeSpacing.None,
+ },
+ marginRightXXS: {
+ marginRight: EThemeSpacing.XXS,
+ },
+ marginRightXS: {
+ marginRight: EThemeSpacing.XS,
+ },
+ marginRightSNudge: {
+ marginRight: EThemeSpacing.SNudge,
+ },
+ marginRightS: {
+ marginRight: EThemeSpacing.S,
+ },
+ marginRightMNudge: {
+ marginRight: EThemeSpacing.MNudge,
+ },
+ marginRightM: {
+ marginRight: EThemeSpacing.M,
+ },
+ marginRightL: {
+ marginRight: EThemeSpacing.L,
+ },
+ marginRightXL: {
+ marginRight: EThemeSpacing.XL,
+ },
+ marginRightXXL: {
+ marginRight: EThemeSpacing.XXL,
+ },
+ marginRightXXXL: {
+ marginRight: EThemeSpacing.XXXL,
+ },
+ marginTopNone: {
+ marginTop: EThemeSpacing.None,
+ },
+ marginTopXXS: {
+ marginTop: EThemeSpacing.XXS,
+ },
+ marginTopXS: {
+ marginTop: EThemeSpacing.XS,
+ },
+ marginTopSNudge: {
+ marginTop: EThemeSpacing.SNudge,
+ },
+ marginTopS: {
+ marginTop: EThemeSpacing.S,
+ },
+ marginTopMNudge: {
+ marginTop: EThemeSpacing.MNudge,
+ },
+ marginTopM: {
+ marginTop: EThemeSpacing.M,
+ },
+ marginTopL: {
+ marginTop: EThemeSpacing.L,
+ },
+ marginTopXL: {
+ marginTop: EThemeSpacing.XL,
+ },
+ marginTopXXL: {
+ marginTop: EThemeSpacing.XXL,
+ },
+ marginTopXXXL: {
+ marginTop: EThemeSpacing.XXXL,
+ },
+ marginBottomNone: {
+ marginBottom: EThemeSpacing.None,
+ },
+ marginBottomXXS: {
+ marginBottom: EThemeSpacing.XXS,
+ },
+ marginBottomXS: {
+ marginBottom: EThemeSpacing.XS,
+ },
+ marginBottomSNudge: {
+ marginBottom: EThemeSpacing.SNudge,
+ },
+ marginBottomS: {
+ marginBottom: EThemeSpacing.S,
+ },
+ marginBottomMNudge: {
+ marginBottom: EThemeSpacing.MNudge,
+ },
+ marginBottomM: {
+ marginBottom: EThemeSpacing.M,
+ },
+ marginBottomL: {
+ marginBottom: EThemeSpacing.L,
+ },
+ marginBottomXL: {
+ marginBottom: EThemeSpacing.XL,
+ },
+ marginBottomXXL: {
+ marginBottom: EThemeSpacing.XXL,
+ },
+ marginBottomXXXL: {
+ marginBottom: EThemeSpacing.XXXL,
+ },
+});
+
+export default useMarginClasses;
diff --git a/src/components/layout/Flex/styles/padding.ts b/src/components/layout/Flex/styles/padding.ts
new file mode 100644
index 0000000..a7f073e
--- /dev/null
+++ b/src/components/layout/Flex/styles/padding.ts
@@ -0,0 +1,172 @@
+import { makeStyles } from "@fluentui/react-components";
+import { EThemeSpacing } from "@theme";
+
+const usePaddingClasses = makeStyles({
+ paddingNone: {
+ padding: EThemeSpacing.None,
+ },
+ paddingXXS: {
+ padding: EThemeSpacing.XXS,
+ },
+ paddingXS: {
+ padding: EThemeSpacing.XS,
+ },
+ paddingSNudge: {
+ padding: EThemeSpacing.SNudge,
+ },
+ paddingS: {
+ padding: EThemeSpacing.S,
+ },
+ paddingMNudge: {
+ padding: EThemeSpacing.MNudge,
+ },
+ paddingM: {
+ padding: EThemeSpacing.M,
+ },
+ paddingL: {
+ padding: EThemeSpacing.L,
+ },
+ paddingXL: {
+ padding: EThemeSpacing.XL,
+ },
+ paddingXXL: {
+ padding: EThemeSpacing.XXL,
+ },
+ paddingXXXL: {
+ padding: EThemeSpacing.XXXL,
+ },
+ paddingLeftNone: {
+ paddingLeft: EThemeSpacing.None,
+ },
+ paddingLeftXXS: {
+ paddingLeft: EThemeSpacing.XXS,
+ },
+ paddingLeftXS: {
+ paddingLeft: EThemeSpacing.XS,
+ },
+ paddingLeftSNudge: {
+ paddingLeft: EThemeSpacing.SNudge,
+ },
+ paddingLeftS: {
+ paddingLeft: EThemeSpacing.S,
+ },
+ paddingLeftMNudge: {
+ paddingLeft: EThemeSpacing.MNudge,
+ },
+ paddingLeftM: {
+ paddingLeft: EThemeSpacing.M,
+ },
+ paddingLeftL: {
+ paddingLeft: EThemeSpacing.L,
+ },
+ paddingLeftXL: {
+ paddingLeft: EThemeSpacing.XL,
+ },
+ paddingLeftXXL: {
+ paddingLeft: EThemeSpacing.XXL,
+ },
+ paddingLeftXXXL: {
+ paddingLeft: EThemeSpacing.XXXL,
+ },
+ paddingRightNone: {
+ paddingRight: EThemeSpacing.None,
+ },
+ paddingRightXXS: {
+ paddingRight: EThemeSpacing.XXS,
+ },
+ paddingRightXS: {
+ paddingRight: EThemeSpacing.XS,
+ },
+ paddingRightSNudge: {
+ paddingRight: EThemeSpacing.SNudge,
+ },
+ paddingRightS: {
+ paddingRight: EThemeSpacing.S,
+ },
+ paddingRightMNudge: {
+ paddingRight: EThemeSpacing.MNudge,
+ },
+ paddingRightM: {
+ paddingRight: EThemeSpacing.M,
+ },
+ paddingRightL: {
+ paddingRight: EThemeSpacing.L,
+ },
+ paddingRightXL: {
+ paddingRight: EThemeSpacing.XL,
+ },
+ paddingRightXXL: {
+ paddingRight: EThemeSpacing.XXL,
+ },
+ paddingRightXXXL: {
+ paddingRight: EThemeSpacing.XXXL,
+ },
+ paddingTopNone: {
+ paddingTop: EThemeSpacing.None,
+ },
+ paddingTopXXS: {
+ paddingTop: EThemeSpacing.XXS,
+ },
+ paddingTopXS: {
+ paddingTop: EThemeSpacing.XS,
+ },
+ paddingTopSNudge: {
+ paddingTop: EThemeSpacing.SNudge,
+ },
+ paddingTopS: {
+ paddingTop: EThemeSpacing.S,
+ },
+ paddingTopMNudge: {
+ paddingTop: EThemeSpacing.MNudge,
+ },
+ paddingTopM: {
+ paddingTop: EThemeSpacing.M,
+ },
+ paddingTopL: {
+ paddingTop: EThemeSpacing.L,
+ },
+ paddingTopXL: {
+ paddingTop: EThemeSpacing.XL,
+ },
+ paddingTopXXL: {
+ paddingTop: EThemeSpacing.XXL,
+ },
+ paddingTopXXXL: {
+ paddingTop: EThemeSpacing.XXXL,
+ },
+ paddingBottomNone: {
+ paddingBottom: EThemeSpacing.None,
+ },
+ paddingBottomXXS: {
+ paddingBottom: EThemeSpacing.XXS,
+ },
+ paddingBottomXS: {
+ paddingBottom: EThemeSpacing.XS,
+ },
+ paddingBottomSNudge: {
+ paddingBottom: EThemeSpacing.SNudge,
+ },
+ paddingBottomS: {
+ paddingBottom: EThemeSpacing.S,
+ },
+ paddingBottomMNudge: {
+ paddingBottom: EThemeSpacing.MNudge,
+ },
+ paddingBottomM: {
+ paddingBottom: EThemeSpacing.M,
+ },
+ paddingBottomL: {
+ paddingBottom: EThemeSpacing.L,
+ },
+ paddingBottomXL: {
+ paddingBottom: EThemeSpacing.XL,
+ },
+ paddingBottomXXL: {
+ paddingBottom: EThemeSpacing.XXL,
+ },
+ paddingBottomXXXL: {
+ paddingBottom: EThemeSpacing.XXXL,
+ },
+});
+
+export default usePaddingClasses;
diff --git a/src/components/layout/Flex/styles/position.ts b/src/components/layout/Flex/styles/position.ts
new file mode 100644
index 0000000..9bc1e7e
--- /dev/null
+++ b/src/components/layout/Flex/styles/position.ts
@@ -0,0 +1,42 @@
+import { makeStyles } from "@fluentui/react-components";
+
+const usePositionClasses = makeStyles({
+ "-moz-initial": {
+ position: "-moz-initial",
+ },
+ "-webkit-sticky": {
+ position: "-webkit-sticky",
+ },
+ absolute: {
+ position: "absolute",
+ },
+ fixed: {
+ position: "fixed",
+ },
+ inherit: {
+ position: "inherit",
+ },
+ initial: {
+ position: "initial",
+ },
+ relative: {
+ position: "relative",
+ },
+ revert: {
+ position: "revert",
+ },
+ "revert-layer": {
+ position: "revert-layer",
+ },
+ static: {
+ position: "static",
+ },
+ sticky: {
+ position: "sticky",
+ },
+ unset: {
+ position: "unset",
+ },
+});
+
+export default usePositionClasses;
diff --git a/src/components/layout/Flex/tests.tsx b/src/components/layout/Flex/tests.tsx
index 56ae881..0be08c1 100644
--- a/src/components/layout/Flex/tests.tsx
+++ b/src/components/layout/Flex/tests.tsx
@@ -1,8 +1,388 @@
-// import { render, screen } from '@test-utils';
+import { render, screen } from "@test-utils";
import "@testing-library/jest-dom";
+import { makeStyles } from "@fluentui/react-components";
+import { Flex } from "@components";
+
describe("Flex", () => {
- it("should render", () => {
- expect(true).toBe(true);
+ it("should render without required props default config", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+
+ expect(FlexElement).toBeInTheDocument();
+ expect(FlexElement).toHaveTextContent("FlexChild");
+ expect(FlexElement).toHaveStyle("display: flex");
+ expect(FlexElement).toHaveStyle("flex-direction: row");
+ expect(FlexElement).toHaveStyle("justify-content: flex-start");
+ expect(FlexElement).toHaveStyle("align-items: flex-start");
+ expect(FlexElement).toHaveStyle("flex-wrap: nowrap");
+ expect(FlexElement).toHaveStyle("gap: 0rem");
+ expect(FlexElement).toHaveStyle("margin: 0rem");
+ expect(FlexElement).toHaveStyle("padding: 0rem");
+ expect(FlexElement).toHaveStyle("height: auto");
+ expect(FlexElement).toHaveStyle("width: auto");
+ expect(FlexElement).toHaveStyle("position: static");
+ });
+ describe("when props are passed", () => {
+ describe("for direction", () => {
+ it("should render with direction column", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("flex-direction: column");
+ });
+ it("should render with direction row", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("flex-direction: row");
+ });
+ });
+ describe("for justifyContent", () => {
+ it("should render with justifyContent center", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("justify-content: center");
+ });
+ it("should render with justifyContent end", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("justify-content: flex-end");
+ });
+ it("should render with justifyContent spaceBetween", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("justify-content: space-between");
+ });
+ it("should render with justifyContent spaceAround", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("justify-content: space-around");
+ });
+ it("should render with justifyContent spaceEvenly", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("justify-content: space-evenly");
+ });
+ it("should render with justifyContent stretch", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("justify-content: stretch");
+ });
+ });
+ describe("for alignItems", () => {
+ it("should render with alignItems center", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("align-items: center");
+ });
+ it("should render with alignItems end", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("align-items: flex-end");
+ });
+ it("should render with alignItems spaceBetween", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("align-items: space-between");
+ });
+ it("should render with alignItems spaceAround", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("align-items: space-around");
+ });
+ it("should render with alignItems spaceEvenly", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("align-items: space-evenly");
+ });
+ it("should render with alignItems stretch", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("align-items: stretch");
+ });
+ });
+ describe("for wrap", () => {
+ it("should render with wrap wrap", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("flex-wrap: wrap");
+ });
+ it("should render with wrap nowrap", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("flex-wrap: nowrap");
+ });
+ });
+ describe("for gap", () => {
+ it("should render with gap None", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 0rem");
+ });
+ it("should render with gap XXS", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 0.125rem");
+ });
+ it("should render with gap XS", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 0.25rem");
+ });
+ it("should render with gap SNudge", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 0.375rem");
+ });
+ it("should render with gap S", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 0.5rem");
+ });
+ it("should render with gap MNudge", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 0.625rem");
+ });
+ it("should render with gap M", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 0.75rem");
+ });
+ it("should render with gap L", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 1rem");
+ });
+ it("should render with gap XL", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 1.25rem");
+ });
+ it("should render with gap XXL", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 1.5rem");
+ });
+ it("should render with gap XXXL", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("gap: 2rem");
+ });
+ });
+ describe("for margin", () => {
+ it("should render with margin None", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("margin: 0rem");
+ });
+ it("should render with margin XS, L", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("margin: 0.25rem 1rem 0.25rem 1rem");
+ });
+ it("should render with margin XS, L, XL", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("margin: 0.25rem 1rem 1.25rem 1rem");
+ });
+ it("should render with margin XS, L, XL, XXL", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("margin: 0.25rem 1rem 1.25rem 1.5rem");
+ });
+ });
+ describe("for padding", () => {
+ it("should render with padding None", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("padding: 0rem");
+ });
+ it("should render with padding XS, L", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("padding: 0.25rem 1rem 0.25rem 1rem");
+ });
+ it("should render with padding XS, L, XL", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("padding: 0.25rem 1rem 1.25rem 1rem");
+ });
+ it("should render with padding XS, L, XL, XXL", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("padding: 0.25rem 1rem 1.25rem 1.5rem");
+ });
+ });
+ describe("for shHeight", () => {
+ it("should render with height 100%", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("height: 100%");
+ });
+ it("should render with height 25%", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("height: 25%");
+ });
+ it("should render with height 50%", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("height: 50%");
+ });
+ it("should render with height 75%", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("height: 75%");
+ });
+ it("should render with height auto", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("height: auto");
+ });
+ });
+ describe("for shWidth", () => {
+ it("should render with width 100%", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("width: 100%");
+ });
+ it("should render with width 25%", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("width: 25%");
+ });
+ it("should render with width 50%", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("width: 50%");
+ });
+ it("should render with width 75%", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("width: 75%");
+ });
+ it("should render with width auto", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("width: auto");
+ });
+ });
+ describe("for className", () => {
+ it("should render with the properties applied from the class and override any other props styles", () => {
+ const useClasses = makeStyles({
+ testClass: {
+ height: "100px",
+ border: "1px solid black",
+ },
+ });
+
+ function Wrapper() {
+ return FlexChild ;
+ }
+
+ render( );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("height: 100px");
+ expect(FlexElement).toHaveStyle("border: 1px solid black");
+ });
+ });
+ describe("for testId", () => {
+ it("should render with data-testid", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByTestId("testId");
+ expect(FlexElement).toBeInTheDocument();
+ });
+ });
+ describe("for aria-* attributes (only checks some, cannot be exhaustive)", () => {
+ // not exhaustive, but at least one example of each type
+ const ariaAttributes = [
+ { name: "aria-label", value: "test-label" },
+ { name: "aria-labelledby", value: "test-labelledby" },
+ { name: "aria-describedby", value: "test-describedby" },
+ { name: "aria-hidden", value: true },
+ { name: "aria-placeholder", value: "test-placeholder" },
+ { name: "aria-expanded", value: "true" },
+ { name: "aria-controls", value: "test-controls" },
+ { name: "aria-pressed", value: "true" },
+ { name: "aria-current", value: "page" },
+ { name: "aria-invalid", value: "true" },
+ { name: "aria-busy", value: "true" },
+ { name: "aria-readonly", value: "true" },
+ { name: "aria-required", value: "true" },
+ { name: "aria-modal", value: "true" },
+ { name: "aria-orientation", value: "horizontal" },
+ { name: "aria-valuemin", value: "0" },
+ { name: "aria-valuemax", value: "100" },
+ { name: "aria-valuenow", value: "50" },
+ { name: "aria-valuetext", value: "50%" },
+ ];
+
+ ariaAttributes.forEach(({ name, value }) => {
+ it(`should render with ${name}`, () => {
+ const props = { [name]: value };
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ if (value === true) {
+ // For boolean attributes like aria-hidden
+ expect(FlexElement).toHaveAttribute(name);
+ } else {
+ expect(FlexElement).toHaveAttribute(name, value.toString());
+ }
+ });
+ });
+ });
+ describe("for position", () => {
+ it("should render with position -moz-initial", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: -moz-initial");
+ });
+ it("should render with position -webkit-sticky", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: -webkit-sticky");
+ });
+ it("should render with position absolute", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: absolute");
+ });
+ it("should render with position fixed", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: fixed");
+ });
+ it("should render with position inherit", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: inherit");
+ });
+ it("should render with position initial", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: initial");
+ });
+ it("should render with position relative", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: relative");
+ });
+ it("should render with position revert", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: revert");
+ });
+ it("should render with position revert-layer", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: revert-layer");
+ });
+ it("should render with position static", () => {
+ render(FlexChild );
+ const FlexElement = screen.getByText("FlexChild");
+ expect(FlexElement).toHaveStyle("position: static");
+ });
+ });
});
});
diff --git a/src/components/layout/Flex/types.ts b/src/components/layout/Flex/types.ts
new file mode 100644
index 0000000..14334d5
--- /dev/null
+++ b/src/components/layout/Flex/types.ts
@@ -0,0 +1,28 @@
+export type TFlexDirection = "row" | "column";
+
+export type TFlexOption =
+ | "start"
+ | "center"
+ | "end"
+ | "spaceBetween"
+ | "spaceAround"
+ | "spaceEvenly"
+ | "stretch";
+
+export type TFlexWrap = "wrap" | "nowrap";
+
+export type TFlexShorthandDimensions = "25%" | "50%" | "75%" | "100%" | "auto";
+
+export type TFlexPosition =
+ | "-moz-initial"
+ | "-webkit-sticky"
+ | "absolute"
+ | "fixed"
+ | "inherit"
+ | "initial"
+ | "relative"
+ | "revert"
+ | "revert-layer"
+ | "static"
+ | "sticky"
+ | "unset";
diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts
index 15ebd2e..35f4da9 100644
--- a/src/components/layout/index.ts
+++ b/src/components/layout/index.ts
@@ -1 +1 @@
-export { default as Flex } from "@components/layout/Flex/func";
+export { default as Flex } from "@components/layout/Flex";
diff --git a/src/hooks/useFuiProviderNode.ts b/src/hooks/useFuiProviderNode/hook.ts
similarity index 100%
rename from src/hooks/useFuiProviderNode.ts
rename to src/hooks/useFuiProviderNode/hook.ts
diff --git a/src/hooks/useFuiProviderNode/index.ts b/src/hooks/useFuiProviderNode/index.ts
new file mode 100644
index 0000000..52d3594
--- /dev/null
+++ b/src/hooks/useFuiProviderNode/index.ts
@@ -0,0 +1,3 @@
+import useFuiProviderNode from "@hooks/useFuiProviderNode/hook";
+
+export default useFuiProviderNode;
diff --git a/src/hooks/useFuiProviderNode/stories.tsx b/src/hooks/useFuiProviderNode/stories.tsx
new file mode 100644
index 0000000..ac49f98
--- /dev/null
+++ b/src/hooks/useFuiProviderNode/stories.tsx
@@ -0,0 +1,68 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { Tooltip, Button } from "@fluentui/react-components";
+import useFuiProviderNode from "@hooks/useFuiProviderNode";
+
+/**
+ * @description
+ * - when you use a component in fluent that requires the parent provider node, for example for relative positioning, you can use this hook to get the parent provider node
+ * - some components like Tooltip for example allow to set a mountNode prop, which is the parent provider node
+ * - this will only work if you have exactly one provider node in your app
+ *
+ * @example
+ *
+ * ```tsx
+ * import { Tooltip, Button } from "@fluentui/react-components";
+ * import useFuiProviderNode from "@hooks/useFuiProviderNode";
+ *
+ * export default function SomeHigherComponent() {
+ * const { fuiProviderNode } = useFuiProviderNode();
+ * return (
+ *
+ * Hover me
+ *
+ * );
+ * }
+ * ```
+ */
+export function Wrapper({ hasProviderNode }: { hasProviderNode: boolean }) {
+ const { fuiProviderNode } = useFuiProviderNode();
+ return hasProviderNode ? (
+
+ Hover me
+
+ ) : (
+
+ Hover me
+
+ );
+}
+
+const meta: Meta = {
+ title: "Hooks/Specific/useFuiProviderNode",
+ component: Wrapper,
+ args: {
+ hasProviderNode: true,
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const WithNodeFromHook: Story = {};
+
+export const DefaultProviderNode: Story = {
+ args: { hasProviderNode: false },
+};
diff --git a/src/readme.mdx b/src/readme.mdx
new file mode 100644
index 0000000..c81dcee
--- /dev/null
+++ b/src/readme.mdx
@@ -0,0 +1,24 @@
+import { Meta } from "@storybook/blocks";
+
+
+
+### Implemented
+
+For the implemented features just browse the Storybook.
+Not only visual components are there, but also hooks and other utilities showcase through wrappers.
+
+### Planned
+
+#### Components
+
+- Atoms: Pagination
+- Layout: Grid (not sure, might cause more problems than it solves)
+- Animations: FadeIn, FadeOut
+
+#### Hooks
+
+_nothing planned_
+
+#### Theme
+
+- More materials / Morphism for web: Window 11 brings lots of glass / acrylic effects from the [fluent UI language](https://fluent2.microsoft.design/material), there should also be a way to access these in web apps.
diff --git a/src/theme/index.ts b/src/theme/index.ts
index bb3a547..5a826f8 100644
--- a/src/theme/index.ts
+++ b/src/theme/index.ts
@@ -5,3 +5,5 @@ export {
EThemeIconSizes,
EThemeDimensions,
} from "@theme/tokens";
+
+export type { TThemeSpacing, TThemeShorthandSpacing } from "@theme/types";
diff --git a/src/theme/tokens/stories.tsx b/src/theme/tokens/stories.tsx
new file mode 100644
index 0000000..0b8a424
--- /dev/null
+++ b/src/theme/tokens/stories.tsx
@@ -0,0 +1,731 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { DeleteFilled } from "@fluentui/react-icons";
+
+import { Flex } from "@components";
+import {
+ EThemeDimensions,
+ EThemeIconSizes,
+ EThemeSpacing,
+} from "@theme/tokens/index";
+
+function Wrapper({ token }: { token: "dimension" | "iconSize" | "spacing" }) {
+ const showcases = {
+ iconSize: (
+
+
+
+ XXS - 16px
+
+
+
+ XS - 20px
+
+
+ S - 24px
+
+
+ M - 28px
+
+
+ L - 32px
+
+
+
+ XL - 48px
+
+
+ ),
+ dimension: (
+
+
+
+ XS1 - 4px
+
+
+
+ XS2 - 8px
+
+
+
+ XS3 - 12px
+
+
+
+ XS4 - 16px
+
+
+
+ XS5 - 20px
+
+
+
+ XS6 - 24px
+
+
+
+ XS7 - 28px
+
+
+
+ XS8 - 32px
+
+
+
+ XS9 - 36px
+
+
+
+ S1 - 40px
+
+
+
+ S2 - 48px
+
+
+
+ S3 - 56px
+
+
+
+ S4 - 64px
+
+
+
+ S5 - 72px
+
+
+
+ S6 - 80px
+
+
+
+ S7 - 88px
+
+
+
+ S8 - 96px
+
+
+
+ S9 - 104px
+
+
+
+ M1 - 116px
+
+
+
+ M2 - 128px
+
+
+
+ M3 - 140px
+
+
+
+ M4 - 152px
+
+
+
+ M5 - 164px
+
+
+
+ M6 - 176px
+
+
+
+ M7 - 188px
+
+
+
+ M8 - 200px
+
+
+
+ M9 - 212px
+
+
+
+ L1 - 228px
+
+
+
+ L2 - 244px
+
+
+
+ L3 - 260px
+
+
+
+ L4 - 276px
+
+
+
+ L5 - 292px
+
+
+
+ L6 - 308px
+
+
+
+ L7 - 324px
+
+
+
+ L8 - 340px
+
+
+
+ L9 - 356px
+
+
+
+ XL1 - 376px
+
+
+
+ XL2 - 392px
+
+
+
+ XL3 - 408px
+
+
+
+ XL4 - 424px
+
+
+
+ XL5 - 440px
+
+
+
+ XL6 - 456px
+
+
+
+ XL7 - 472px
+
+
+
+ XL8 - 488px
+
+
+
+ XL9 - 504px
+
+
+
+ O1 - 528px
+
+
+
+ O2 - 552px
+
+
+
+ O3 - 576px
+
+
+
+ O4 - 600px
+
+
+
+ O5 - 624px
+
+
+
+ O6 - 648px
+
+
+
+ O7 - 672px
+
+
+
+ O8 - 696px
+
+
+
+ O9 - 720px
+
+
+
+ O10 - 744px
+
+
+ ),
+ spacing: (
+
+
+
+ XXS - 2px
+
+
+
+ XS - 4px
+
+
+
+ SNudge - 6px
+
+
+
+ S - 8px
+
+
+
+ MNudge - 10px
+
+
+
+ M - 12px
+
+
+
+ L - 16px
+
+
+
+ XL - 20px
+
+
+
+ XXL - 24px
+
+
+
+ XXXL - 32px
+
+
+ ),
+ };
+
+ return showcases[token];
+}
+
+const meta: Meta = {
+ title: "Theme/Tokens",
+ component: Wrapper,
+ args: {
+ token: "iconSize",
+ },
+ argTypes: {
+ token: {
+ control: false,
+ },
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const IconSizes: Story = {};
+
+export const Spacing: Story = {
+ args: {
+ token: "spacing",
+ },
+};
+
+export const Dimensions: Story = {
+ args: {
+ token: "dimension",
+ },
+};
diff --git a/src/theme/types.ts b/src/theme/types.ts
new file mode 100644
index 0000000..095897c
--- /dev/null
+++ b/src/theme/types.ts
@@ -0,0 +1,7 @@
+import type { EThemeSpacing } from "@theme/tokens";
+
+export type TThemeSpacing = keyof typeof EThemeSpacing;
+
+type TThemeShorthand = [T] | [T, T] | [T, T, T] | [T, T, T, T];
+
+export type TThemeShorthandSpacing = TThemeShorthand;
diff --git a/tsconfig.json b/tsconfig.json
index b0bbe77..18e8b91 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -24,10 +24,12 @@
// Aliases
"baseUrl": ".",
"paths": {
- "@animations/*": ["src/animations/*"],
"@components/*": ["src/components/*"],
+ "@components": ["src/components/index.ts"],
"@hooks/*": ["src/hooks/*"],
+ "@hooks": ["src/hooks/index.ts"],
"@theme/*": ["src/theme/*"],
+ "@theme": ["src/theme/index.ts"],
"@test-utils": ["tests/react-testing-library.tsx"]
}
},