& {
value: number;
label: ReactNode;
+ /** Label is ***active*** only when slider value is **equal** to mark value. */
+ activeEqual?: boolean;
};
export type MarkLabelState =
diff --git a/components/slider/src/mark/label/use-mark-label.ts b/components/slider/src/mark/label/use-mark-label.ts
index 40290db7..fe50ca8b 100644
--- a/components/slider/src/mark/label/use-mark-label.ts
+++ b/components/slider/src/mark/label/use-mark-label.ts
@@ -14,6 +14,10 @@ export const useMarkLabel_unstable = (
const minValue = values.length > 1 ? Math.min(...values) : 0;
const maxValue = Math.max(...values);
+ const active = props.activeEqual
+ ? minValue === props.value || maxValue === props.value
+ : props.value >= minValue && props.value <= maxValue;
+
return {
root: getNativeElementProps("span", {
ref,
@@ -26,6 +30,6 @@ export const useMarkLabel_unstable = (
offset: toPercent(props.value, min, max),
value: props.value,
disabled,
- active: props.value >= minValue && props.value <= maxValue,
+ active,
};
};
diff --git a/components/slider/src/mark/mark.types.ts b/components/slider/src/mark/mark.types.ts
index b894c4af..4a949a48 100644
--- a/components/slider/src/mark/mark.types.ts
+++ b/components/slider/src/mark/mark.types.ts
@@ -8,6 +8,8 @@ import {
export type MarkDef = {
value: number;
label?: ReactNode;
+ /** Mark label is ***active*** only when slider value is **equal** to mark value. */
+ labelEqualActive?: boolean;
};
export type MarkSlots = {
diff --git a/components/slider/src/range-slider.spec.tsx b/components/slider/src/range-slider.spec.tsx
index 32c13b3e..00d22ed2 100644
--- a/components/slider/src/range-slider.spec.tsx
+++ b/components/slider/src/range-slider.spec.tsx
@@ -1,9 +1,10 @@
+import React from "react";
+import { describe, expect, it, vi } from "vitest";
import { fireEvent, render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
+
import { RangeSlider } from "./range-slider";
-import React from "react";
import { getControlRoot } from "./test-helpers";
-import { expect, vi } from "vitest";
const expectSliderValues = (elements: HTMLElement[], values: number[]) => {
expect(elements).toHaveLength(values.length);
diff --git a/components/slider/src/render-slider.tsx b/components/slider/src/render-slider.tsx
index 24baefcc..06247492 100644
--- a/components/slider/src/render-slider.tsx
+++ b/components/slider/src/render-slider.tsx
@@ -1,8 +1,9 @@
+import React from "react";
+
import { getSlots } from "@fluentui/react-utilities";
import { SliderContextProvider } from "./context/slider-context";
import { SliderContextValues, SliderSlots, SliderState } from "./slider.types";
-import React from "react";
export const renderSlider_unstable = (
state: SliderState,
@@ -10,7 +11,7 @@ export const renderSlider_unstable = (
) => {
const { slots, slotProps } = getSlots
(state);
- const { marks, markLabels, thumbs } = state;
+ const { marks, markLabels, sectionLabels, thumbs } = state;
return (
@@ -18,14 +19,14 @@ export const renderSlider_unstable = (
- {marks.map((markProps) => (
-
- ))}
+ {marks.map((markProps) => (
+
+ ))}
{markLabels.map((markLabelProps) => (
))}
+
+ {sectionLabels.map((sectionLabelProps) => (
+
+ ))}
{thumbs.map((thumbProps) => (
{
+ const { slots, slotProps } = getSlots(state);
+
+ return ;
+};
diff --git a/components/slider/src/section/section.tsx b/components/slider/src/section/section.tsx
new file mode 100644
index 00000000..1ce564fc
--- /dev/null
+++ b/components/slider/src/section/section.tsx
@@ -0,0 +1,16 @@
+import { ForwardRefComponent } from "@fluentui/react-utilities";
+import React from "react";
+
+import { SectionProps } from "./section.types";
+import { useSection_unstable } from "./use-section";
+import { useSectionStyles_unstable } from "./use-section-styles";
+import { renderSection_unstable } from "./render-section";
+
+export const Section: ForwardRefComponent = React
+ .forwardRef((props, ref) => {
+ const state = useSection_unstable(props, ref);
+
+ useSectionStyles_unstable(state);
+
+ return renderSection_unstable(state);
+ });
diff --git a/components/slider/src/section/section.types.ts b/components/slider/src/section/section.types.ts
new file mode 100644
index 00000000..942b914f
--- /dev/null
+++ b/components/slider/src/section/section.types.ts
@@ -0,0 +1,35 @@
+import {
+ ComponentProps,
+ ComponentState,
+ Slot,
+} from "@fluentui/react-utilities";
+import { ReactNode } from "react";
+
+export type SectionDef = {
+ edges: SectionEdges;
+ label: ReactNode;
+ trackColor?: string;
+};
+
+export type SectionSlots = {
+ root: Slot<"span">;
+};
+
+interface SectionEdges {
+ from?: number;
+ to?: number;
+}
+
+export type SectionProps = ComponentProps & {
+ edges: Required;
+ label: ReactNode;
+ trackColor?: string;
+};
+
+export type SectionState =
+ & ComponentState
+ & {
+ offset: number;
+ disabled: boolean;
+ active: boolean;
+ };
diff --git a/components/slider/src/section/use-section-styles.ts b/components/slider/src/section/use-section-styles.ts
new file mode 100644
index 00000000..41880000
--- /dev/null
+++ b/components/slider/src/section/use-section-styles.ts
@@ -0,0 +1,71 @@
+import {
+ makeStyles,
+ mergeClasses,
+ shorthands,
+ tokens,
+ useFluent,
+} from "@fluentui/react-components";
+
+import {
+ sliderClassNames,
+ sliderDurations,
+ sliderEasings,
+ sliderVars,
+} from "../use-slider-styles";
+import { SectionState } from "./section.types";
+
+const useStyles = makeStyles({
+ root: {
+ ...shorthands.transition(
+ "color",
+ sliderDurations.short,
+ sliderEasings.easeOutFast
+ ),
+ position: "absolute",
+ color: `var(${sliderVars.section.color})`,
+ top: `var(${sliderVars.thumb.size})`,
+ transform: "translateX(-50%)",
+ whiteSpace: "nowrap",
+ },
+ active: {
+ [sliderVars.section.color]: tokens.colorNeutralForeground1,
+ },
+ enabled: {
+ [sliderVars.section.color]: tokens.colorNeutralForeground2,
+ },
+ disabled: {
+ [sliderVars.section.color]: tokens.colorNeutralForegroundDisabled,
+ },
+});
+
+export const useSectionStyles_unstable = (
+ state: SectionState
+): SectionState => {
+ const styles = useStyles();
+
+ const { offset, disabled, active } = state;
+
+ const colorStyles = disabled
+ ? styles.disabled
+ : active
+ ? styles.active
+ : styles.enabled;
+
+ state.root.className = mergeClasses(
+ sliderClassNames.section.label,
+ styles.root,
+ colorStyles,
+ state.root.className
+ );
+
+ const { dir } = useFluent();
+ const offsetDirection = dir === "rtl" ? "right" : "left";
+ state.root.title = "";
+ state.root.style = {
+ cursor: "default",
+ [offsetDirection]: `${offset}%`,
+ ...state.root.style,
+ };
+
+ return state;
+};
diff --git a/components/slider/src/section/use-section.ts b/components/slider/src/section/use-section.ts
new file mode 100644
index 00000000..f385431a
--- /dev/null
+++ b/components/slider/src/section/use-section.ts
@@ -0,0 +1,43 @@
+import { getNativeElementProps } from "@fluentui/react-utilities";
+import React from "react";
+
+import { useSliderContext } from "../context/slider-context";
+import { SectionProps, SectionState } from "./section.types";
+import { toPercent } from "../utils";
+
+export const useSection_unstable = (
+ props: SectionProps,
+ ref: React.Ref
+): SectionState => {
+ const { min, max, disabled, values } = useSliderContext();
+
+ const from = props.edges.from ?? min;
+ const to = props.edges.to ?? max;
+
+ /**
+ * The sections center position, calculated as the midpoint between left and right.
+ */
+ const center = from + (to - from) / 2;
+
+ const minValue = values.length > 1 ? Math.min(...values) : min;
+ const maxValue = Math.max(...values);
+
+ const active =
+ (minValue > from && minValue < to) ||
+ (minValue <= from && maxValue > to) ||
+ (maxValue > from && maxValue <= to);
+
+ return {
+ root: getNativeElementProps("span", {
+ ref,
+ children: props.label,
+ ...props,
+ }),
+ components: {
+ root: "span",
+ },
+ offset: toPercent(center, min, max),
+ disabled,
+ active,
+ };
+};
diff --git a/components/slider/src/slider.types.ts b/components/slider/src/slider.types.ts
index ccc9e153..60ca2192 100644
--- a/components/slider/src/slider.types.ts
+++ b/components/slider/src/slider.types.ts
@@ -8,6 +8,7 @@ import { SliderContextValue } from "./context/slider-context";
import { MarkDef, MarkProps } from "./mark/mark.types";
import { ThumbProps } from "./thumb/thumb.types";
import { MarkLabelProps } from "./mark/label/mark-label.types";
+import { SectionDef, SectionProps } from "./section/section.types";
export type SliderContextValues = {
slider: SliderContextValue;
@@ -21,6 +22,7 @@ export type SliderSlots = {
thumb: Slot>;
mark: Slot>;
markLabel: Slot>;
+ sectionLabel: Slot>;
};
export type SliderOnChangeData = {
@@ -39,6 +41,7 @@ export type RangeSliderProps =
& {
disabled?: boolean;
marks?: boolean | MarkDef[];
+ sectionLabels?: SectionDef[];
step?: number | "marks";
size?: "small" | "medium";
min: number;
@@ -69,6 +72,7 @@ export type SliderState =
values: number[];
marks: MarkProps[];
markLabels: MarkLabelProps[];
+ sectionLabels: SectionProps[];
thumbs: ThumbProps[];
trackOffset: number;
trackWidth: number;
diff --git a/components/slider/src/thumb/thumb.types.ts b/components/slider/src/thumb/thumb.types.ts
index cbf4fbc1..2fba859f 100644
--- a/components/slider/src/thumb/thumb.types.ts
+++ b/components/slider/src/thumb/thumb.types.ts
@@ -11,30 +11,26 @@ export type ThumbSlots = {
label: NonNullable>;
};
-export type ThumbProps =
- & Omit<
- ComponentProps, "input">,
- "value"
- >
- & {
- children?: never;
- value: number;
- handleFocus: () => void;
- handleBlur: () => void;
- handleMouseOver: () => void;
- handleMouseLeave: () => void;
- handleInputChange: (event: ChangeEvent) => void;
- valueLabelTransform?: (value: number) => number | string;
- active: boolean;
- open: boolean;
- dragging: boolean;
- "data-index": number;
- };
+export type ThumbProps = Omit<
+ ComponentProps, "input">,
+ "value"
+> & {
+ children?: never;
+ value: number;
+ handleFocus: () => void;
+ handleBlur: () => void;
+ handleMouseOver: () => void;
+ handleMouseLeave: () => void;
+ handleInputChange: (event: ChangeEvent) => void;
+ valueLabelTransform?: (value: number) => number | string;
+ active: boolean;
+ open: boolean;
+ dragging: boolean;
+ "data-index": number;
+};
-export type ThumbState =
- & ComponentState
- & Required>
- & {
+export type ThumbState = ComponentState &
+ Required> & {
offset: number;
disabled: boolean;
};
diff --git a/components/slider/src/use-range-slider.ts b/components/slider/src/use-range-slider.ts
index 2401370f..4b315d11 100644
--- a/components/slider/src/use-range-slider.ts
+++ b/components/slider/src/use-range-slider.ts
@@ -29,6 +29,8 @@ import { toPercent } from "./utils";
import { MarkLabelProps } from "./mark/label/mark-label.types";
import { MarkLabel } from "./mark/label/mark-label";
import { sliderClassNames } from "./use-slider-styles";
+import { SectionProps } from "./section/section.types";
+import { Section } from "./section/section";
const asc = (a: number, b: number): number => a - b;
@@ -83,8 +85,9 @@ const findClosest = (value: number, candidates: number[]) => {
const isMarkLabelElement = (target: EventTarget | null): boolean => {
if (
- !target || !(target instanceof Element)
- || target.classList.contains(sliderClassNames.root)
+ !target ||
+ !(target instanceof Element) ||
+ target.classList.contains(sliderClassNames.root)
) {
return false;
}
@@ -94,6 +97,20 @@ const isMarkLabelElement = (target: EventTarget | null): boolean => {
return isMarkLabelElement(target.parentNode);
};
+const isSectionLabelElement = (target: EventTarget | null): boolean => {
+ if (
+ !target ||
+ !(target instanceof Element) ||
+ target.classList.contains(sliderClassNames.root)
+ ) {
+ return false;
+ }
+ if (target.classList.contains(sliderClassNames.section.label)) {
+ return true;
+ }
+ return isSectionLabelElement(target.parentNode);
+};
+
export const useRangeSlider_unstable = (
props: RangeSliderProps,
ref: React.Ref
@@ -103,6 +120,7 @@ export const useRangeSlider_unstable = (
disabled,
size,
marks: markDefs,
+ sectionLabels: sectionDefs,
step: stepProp,
min,
max,
@@ -155,6 +173,7 @@ export const useRangeSlider_unstable = (
markLabels.push({
value: markDef.value,
label: markDef.label,
+ activeEqual: markDef.labelEqualActive,
});
}
}
@@ -162,6 +181,23 @@ export const useRangeSlider_unstable = (
return { marks, markLabels };
}, [markDefs, stepProp, steps]);
+ const sectionLabels: SectionProps[] = useMemo(() => {
+ const sectionLabels: SectionProps[] = [];
+ if (Array.isArray(sectionDefs)) {
+ for (const sectionDef of sectionDefs) {
+ sectionLabels.push({
+ ...sectionDef,
+ edges: {
+ from: sectionDef.edges.from ?? min,
+ to: sectionDef.edges.to ?? max,
+ },
+ });
+ }
+ }
+
+ return sectionLabels;
+ }, [sectionDefs, min, max]);
+
const markValues = useMemo(() => marks.map((m) => m.value), [marks]);
const snapValues = useMemo(() => {
@@ -234,6 +270,10 @@ export const useRangeSlider_unstable = (
// avoid text selection
e.preventDefault();
+ if (isSectionLabelElement(e.target)) {
+ return;
+ }
+
const closestThumbIndex = findClosest(newValue, internalValues).index;
const newValues = internalValues.slice();
@@ -310,6 +350,10 @@ export const useRangeSlider_unstable = (
touchId.current = touch.identifier;
}
+ if (isSectionLabelElement(event.target)) {
+ return;
+ }
+
const newValue = getNewValue(
touch.clientX,
isMarkLabelElement(event.target)
@@ -351,7 +395,8 @@ export const useRangeSlider_unstable = (
};
}, [controlRef, handleTouchStart, removeListeners]);
- const minValue = internalValues.length > 1 ? Math.min(...internalValues) : 0;
+ const minValue =
+ internalValues.length > 1 ? Math.min(...internalValues) : min;
const maxValue = Math.max(...internalValues);
const trackOffset = toPercent(minValue, min, max);
@@ -441,6 +486,7 @@ export const useRangeSlider_unstable = (
}),
mark: resolveShorthand(props.mark, { required: true }),
markLabel: resolveShorthand(props.markLabel, { required: true }),
+ sectionLabel: resolveShorthand(props.sectionLabel, { required: true }),
components: {
root: "span",
control: "span",
@@ -449,9 +495,11 @@ export const useRangeSlider_unstable = (
thumb: Thumb as React.FC>,
mark: Mark as React.FC>,
markLabel: MarkLabel as React.FC>,
+ sectionLabel: Section as React.FC>,
},
marks,
markLabels,
+ sectionLabels,
thumbs: internalValues.map((value, index) => ({
value,
valueLabelTransform,
diff --git a/components/slider/src/use-slider-styles.ts b/components/slider/src/use-slider-styles.ts
index 49e4b712..cd658885 100644
--- a/components/slider/src/use-slider-styles.ts
+++ b/components/slider/src/use-slider-styles.ts
@@ -1,3 +1,5 @@
+import { useMemo } from "react";
+
import {
createFocusOutlineStyle,
makeStyles,
@@ -8,6 +10,7 @@ import {
} from "@fluentui/react-components";
import { SliderState } from "./slider.types";
+import { toPercent } from "./utils";
export const sliderClassNames = {
root: "axis-Slider",
@@ -22,6 +25,10 @@ export const sliderClassNames = {
root: "axis-Slider__mark",
label: "axis-Slider__mark__label",
},
+ section: {
+ root: "axis-Slider__section",
+ label: "axis-Slider__section__label",
+ },
};
export const sliderVars = {
@@ -45,6 +52,9 @@ export const sliderVars = {
mark: {
color: "--axis-Slider__mark--color",
},
+ section: {
+ color: "--axis-Slider__section--color",
+ },
};
export const sliderDurations = {
@@ -92,6 +102,9 @@ const useRootStyles = makeStyles({
hasMarkLabel: {
[sliderVars.root.paddingBottom]: "1rem",
},
+ hasSectionLabels: {
+ [sliderVars.root.paddingBottom]: "1rem",
+ },
});
const useControlStyles = makeStyles({
@@ -154,13 +167,23 @@ export const useSliderStyles_unstable = (state: SliderState): SliderState => {
const railStyles = useRailStyles();
const trackStyles = useTrackStyles();
- const { disabled, trackOffset, trackWidth, active, markLabels, size } = state;
+ const {
+ disabled,
+ trackOffset,
+ trackWidth,
+ active,
+ markLabels,
+ sectionLabels,
+ size,
+ values,
+ } = state;
const { dir } = useFluent();
const rtl = dir === "rtl";
const hasMarkLabel = markLabels.length > 0;
+ const hasSectionLabels = sectionLabels.length > 0;
state.root.className = mergeClasses(
sliderClassNames.root,
@@ -169,6 +192,7 @@ export const useSliderStyles_unstable = (state: SliderState): SliderState => {
size === "medium" && rootStyles.medium,
rootStyles.focusIndicator,
hasMarkLabel && rootStyles.hasMarkLabel,
+ hasSectionLabels && rootStyles.hasSectionLabels,
state.root.className
);
@@ -193,10 +217,112 @@ export const useSliderStyles_unstable = (state: SliderState): SliderState => {
state.track.className
);
+ const hasSectionLabelsAndColors = sectionLabels.length > 0;
+
const offsetDirection = rtl ? "right" : "left";
+ const offset = hasSectionLabelsAndColors
+ ? undefined
+ : {
+ [offsetDirection]: `${trackOffset}%`,
+ };
+
+ const sectionStyles = useMemo(() => {
+ /** trackColorGradient */
+ let tcg;
+ let thumbColor;
+
+ if (hasSectionLabelsAndColors) {
+ const { min, max } = state;
+ const [rangeStart, rangeEnd] = values;
+ const [trackStart, trackEnd] =
+ rangeEnd === undefined ? [min, rangeStart] : values;
+
+ const defaultColor = tokens.colorCompoundBrandBackground;
+
+ const gradientDirection = !rtl ? "right" : "left";
+
+ tcg = `linear-gradient(to ${gradientDirection}, `;
+ for (const sl of state.sectionLabels) {
+ const color = sl.trackColor ?? defaultColor;
+
+ // start
+ if (sl.edges.from <= trackStart) {
+ if (sl.edges.from < trackStart) {
+ // transparent block before track
+ tcg += `transparent ${toPercent(sl.edges.from, min, max)}%, `;
+ tcg += `transparent ${toPercent(trackStart, min, max)}%, `;
+ }
+
+ if (sl.edges.to > trackStart && sl.edges.to < trackEnd) {
+ tcg += `${color} ${toPercent(trackStart, min, max)}%, `;
+ tcg += `${color} ${toPercent(sl.edges.to, min, max)}%, `;
+ }
+ }
+
+ // middle full
+ if (
+ (sl.edges.from ?? max) >= trackStart &&
+ (sl.edges.to ?? max) <= trackEnd
+ ) {
+ tcg += `${color} ${toPercent(sl.edges.from, min, max)}%, `;
+ tcg += `${color} ${toPercent(sl.edges.to, min, max)}%, `;
+ }
+ // middle inside
+ if (
+ (sl.edges.from ?? max) <= trackStart &&
+ (sl.edges.to ?? max) >= trackEnd
+ ) {
+ tcg += `${color} ${toPercent(trackStart, min, max)}%, `;
+ tcg += `${color} ${toPercent(trackEnd, min, max)}%, `;
+
+ // transparent block after track
+ tcg += `transparent ${toPercent(trackEnd, min, max)}%, `;
+ tcg += `transparent ${toPercent(sl.edges.to, min, max)}%, `;
+ }
+
+ // end
+ if (sl.edges.from > trackStart && sl.edges.to > trackEnd) {
+ // colored block of track
+ tcg += `${color} ${toPercent(sl.edges.from, min, max)}%, `;
+ tcg += `${color} ${toPercent(trackEnd, min, max)}%, `;
+
+ // transparent block after track
+ tcg += `transparent ${toPercent(trackEnd, min, max)}%, `;
+ tcg += `transparent ${toPercent(sl.edges.to, min, max)}%, `;
+ }
+
+ if (sl.edges.from > trackEnd) {
+ // transparent block after track
+ tcg += `transparent ${toPercent(sl.edges.from, min, max)}%, `;
+ tcg += `transparent ${toPercent(sl.edges.to, min, max)}%, `;
+ }
+ }
+ tcg = tcg.substring(0, tcg.length - 2); // remove trailing ", "
+ tcg += ")";
+
+ thumbColor = tokens.colorCompoundBrandBackground;
+
+ for (const [index, sectionLabel] of state.sectionLabels.entries()) {
+ if (
+ trackEnd === min ? index === 0 : trackEnd > sectionLabel.edges.from
+ ) {
+ thumbColor = sectionLabel.trackColor ?? defaultColor;
+ }
+ }
+
+ return { trackColorGradient: tcg, thumbColor };
+ }
+ }, [values]);
+
+ state.thumb = {
+ style: {
+ backgroundColor: sectionStyles?.thumbColor,
+ },
+ };
state.track.style = {
- [offsetDirection]: `${trackOffset}%`,
- width: `${trackWidth}%`,
+ backgroundImage: sectionStyles?.trackColorGradient,
+ width: hasSectionLabelsAndColors ? "100%" : `${trackWidth}%`,
+ ...offset,
...state.track.style,
};
diff --git a/examples/src/stories/slider/examples/dual-section-example.tsx b/examples/src/stories/slider/examples/dual-section-example.tsx
new file mode 100644
index 00000000..39a9ca86
--- /dev/null
+++ b/examples/src/stories/slider/examples/dual-section-example.tsx
@@ -0,0 +1,103 @@
+import React from "react";
+
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+const markValue = 45.5;
+
+export function DualSectionSliderExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}
+
+export const DualSectionSliderExampleAsString = `
+import React from "react";
+
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+const markValue = 45.5;
+
+export function DualSectionSliderExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}
+`;
diff --git a/examples/src/stories/slider/examples/range-slider-with-section-example.tsx b/examples/src/stories/slider/examples/range-slider-with-section-example.tsx
new file mode 100644
index 00000000..d8281480
--- /dev/null
+++ b/examples/src/stories/slider/examples/range-slider-with-section-example.tsx
@@ -0,0 +1,138 @@
+import React from "react";
+
+import { RangeSlider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+export function RangeSliderWithSectionExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}
+
+export const RangeSliderWithSectionExampleAsString = `
+import React from "react";
+
+import { RangeSlider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+export function RangeSliderWithSectionExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}`;
diff --git a/examples/src/stories/slider/examples/triple-section-example-rtl.tsx b/examples/src/stories/slider/examples/triple-section-example-rtl.tsx
new file mode 100644
index 00000000..668c1397
--- /dev/null
+++ b/examples/src/stories/slider/examples/triple-section-example-rtl.tsx
@@ -0,0 +1,125 @@
+import React from "react";
+
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+export function TripleSectionSliderExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}
+
+export const TripleSectionSliderExampleAsString = `
+import React from "react";
+
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+export function TripleSectionSliderExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}
+`;
diff --git a/examples/src/stories/slider/examples/triple-section-example.tsx b/examples/src/stories/slider/examples/triple-section-example.tsx
new file mode 100644
index 00000000..834e7476
--- /dev/null
+++ b/examples/src/stories/slider/examples/triple-section-example.tsx
@@ -0,0 +1,125 @@
+import React from "react";
+
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+export function TripleSectionSliderExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}
+
+export const TripleSectionSliderExampleAsString = `
+import React from "react";
+
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+export function TripleSectionSliderExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}
+`;
diff --git a/examples/src/stories/slider/examples/triple-section-no-zero-start-example.tsx b/examples/src/stories/slider/examples/triple-section-no-zero-start-example.tsx
new file mode 100644
index 00000000..4cb3113b
--- /dev/null
+++ b/examples/src/stories/slider/examples/triple-section-no-zero-start-example.tsx
@@ -0,0 +1,136 @@
+import React from "react";
+
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+export function TripleSectionSliderNoZeroStartExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}
+
+export const TripleSectionSliderNoZeroStartExampleAsString = `
+import React from "react";
+
+import { Slider } from "@axiscommunications/fluent-slider";
+import { makeStyles, tokens } from "@fluentui/react-components";
+
+const useCustomizedSliderStyles = makeStyles({
+ mark: {
+ height: "16px",
+ width: "1px",
+ backgroundColor: "white",
+ },
+ markLabel: {
+ color: tokens.colorPaletteRedForeground1,
+ },
+ track: {
+ backgroundColor: "gray",
+ },
+});
+
+export function TripleSectionSliderNoZeroStartExample() {
+ const classes = useCustomizedSliderStyles();
+ return (
+
+ );
+}`;
diff --git a/examples/src/stories/slider/slider-page.tsx b/examples/src/stories/slider/slider-page.tsx
index 01d9d2cd..b165a0f2 100644
--- a/examples/src/stories/slider/slider-page.tsx
+++ b/examples/src/stories/slider/slider-page.tsx
@@ -56,6 +56,23 @@ import {
WithStepsAndMarksRangeSliderExampleAsString,
} from "./examples/range-slider-with-streps-and-marks-example";
+import {
+ DualSectionSliderExample,
+ DualSectionSliderExampleAsString,
+} from "./examples/dual-section-example";
+import {
+ TripleSectionSliderExample,
+ TripleSectionSliderExampleAsString,
+} from "./examples/triple-section-example";
+import {
+ TripleSectionSliderNoZeroStartExample,
+ TripleSectionSliderNoZeroStartExampleAsString,
+} from "./examples/triple-section-no-zero-start-example";
+import {
+ RangeSliderWithSectionExample,
+ RangeSliderWithSectionExampleAsString,
+} from "./examples/range-slider-with-section-example";
+
const useStyles = makeStyles({
example: {
maxWidth: "auto",
@@ -135,6 +152,31 @@ const examples: pageData[] = [
example: ,
codeString: WithStepsRangeSliderExampleAsString,
},
+
+ {
+ title: "Dual section",
+ anchor: "DualSectionSliderExample",
+ example: ,
+ codeString: DualSectionSliderExampleAsString,
+ },
+ {
+ title: "Triple section",
+ anchor: "TripleSectionSliderExample",
+ example: ,
+ codeString: TripleSectionSliderExampleAsString,
+ },
+ {
+ title: "Triple section with negative section",
+ anchor: "TripleSectionSliderNoZeroStartExample",
+ example: ,
+ codeString: TripleSectionSliderNoZeroStartExampleAsString,
+ },
+ {
+ title: "Range slider with section",
+ anchor: "RangeSliderWithSliderExample",
+ example: ,
+ codeString: RangeSliderWithSectionExampleAsString,
+ },
];
export const SliderPage = () => {
@@ -142,14 +184,10 @@ export const SliderPage = () => {
const styles = useStyles();
const { renderSections, renderNavigation } = useExampleWithNavigation(
- examples.map(d => {
+ examples.map((d) => {
return {
...d,
- example: (
-
- {d.example}
-
- ),
+ example: {d.example}
,
};
})
);
diff --git a/tools/generated/changelog/changeset.js b/tools/generated/changelog/changeset.js
index 0eca7d17..e31f1850 100644
--- a/tools/generated/changelog/changeset.js
+++ b/tools/generated/changelog/changeset.js
@@ -63,7 +63,7 @@ function parseConventionalCommitMessage(msg) {
if (match === null) {
throw new Error("no matches found");
}
- const [_, group, scope, breaking, title] = match;
+ const [, group, scope, breaking, title] = match;
return {
group,
scope,
diff --git a/tools/src/changelog/changeset.ts b/tools/src/changelog/changeset.ts
index 0df373c7..b4f6ef33 100644
--- a/tools/src/changelog/changeset.ts
+++ b/tools/src/changelog/changeset.ts
@@ -115,7 +115,7 @@ function parseConventionalCommitMessage(msg: string): ConventionalCommit {
if (match === null) {
throw new Error("no matches found");
}
- const [_, group, scope, breaking, title] = match;
+ const [, group, scope, breaking, title] = match;
return {
group,
scope,