= (args) => (
Card content
-
+
Card content
diff --git a/src/tedi/components/misc/separator/separator.module.scss b/src/tedi/components/misc/separator/separator.module.scss
index 0e17b456..6d648458 100644
--- a/src/tedi/components/misc/separator/separator.module.scss
+++ b/src/tedi/components/misc/separator/separator.module.scss
@@ -13,6 +13,15 @@ $sizes: (
'2-5': 2.5rem,
'5': 5rem,
);
+$thicknesses: (
+ '1': 1px,
+ '2': 2px,
+);
+$dot-colors: (
+ 'primary': var(--general-border-primary),
+ 'secondary': var(--general-border-secondary),
+ 'accent': var(--general-border-accent),
+);
.tedi-separator {
--vertical-separator-height: 100%;
@@ -30,12 +39,8 @@ $sizes: (
border-left: 1px solid var(--general-border-primary);
}
- &--secondary {
- border-color: var(--general-border-secondary);
- }
-
- &--accent {
- border-color: var(--general-border-accent);
+ &--horizontal {
+ display: block;
}
&--block {
@@ -45,84 +50,38 @@ $sizes: (
&--inline {
display: inline;
}
-}
-
-.tedi-separator--dotted,
-.tedi-separator--dotted-small {
- &::before {
- position: absolute;
- top: 1.25rem;
- width: var(--separator-dotted-dot-lg);
- height: var(--separator-dotted-dot-lg);
- content: '';
- background-color: var(--general-border-primary);
- border-radius: 100%;
- transform: translateX(-8px);
-
- @include mixins.print-grayscale;
- }
- &.tedi-separator--secondary::before {
- background-color: var(--general-border-secondary);
+ &--secondary {
+ border-color: var(--general-border-secondary);
}
- &.tedi-separator--accent::before {
- background-color: var(--general-border-accent);
+ &--accent {
+ border-color: var(--general-border-accent);
}
-}
-.tedi-separator--dotted-small::before {
- top: 1.5rem;
- width: var(--separator-dotted-dot-md);
- height: var(--separator-dotted-dot-md);
- transform: translateX(-5px);
-}
-
-.tedi-separator--is-stretched {
- margin-right: calc(var(--card-content-padding-right) * -1);
- margin-left: calc(var(--card-content-padding-left) * -1);
+ &--is-stretched {
+ margin-right: calc(var(--card-content-padding-right) * -1);
+ margin-left: calc(var(--card-content-padding-left) * -1);
- &.tedi-separator--vertical {
- height: calc(100% + (var(--card-content-padding-top) + var(--card-content-padding-bottom)));
- margin: calc(var(--card-content-padding-top) * -1) 0 calc(var(--card-content-padding-bottom) * -1);
+ &.tedi-separator--vertical {
+ height: calc(100% + (var(--card-content-padding-top) + var(--card-content-padding-bottom)));
+ margin: calc(var(--card-content-padding-top) * -1) 0 calc(var(--card-content-padding-bottom) * -1);
+ }
}
}
-@each $size, $value in $sizes {
- .tedi-separator--horizontal.tedi-separator--top-#{$size} {
- margin-top: #{$value};
- }
-
- .tedi-separator--horizontal.tedi-separator--bottom-#{$size} {
- margin-bottom: #{$value};
- }
-
- .tedi-separator--horizontal.tedi-separator--spacing-#{$size}:not(.tedi-separator--dot-only) {
- margin-top: #{$value};
- margin-bottom: #{$value};
- }
+.tedi-separator--dotted {
+ &::before {
+ position: absolute;
+ content: '';
+ background-color: var(--general-border-primary);
+ border-radius: 50%;
- .tedi-separator--horizontal.tedi-separator--dot-only.tedi-separator--spacing-#{$size},
- .tedi-separator--vertical.tedi-separator--spacing-#{$size} {
- margin-right: #{$value};
- margin-left: #{$value};
+ @include mixins.print-grayscale;
}
-}
-
-$thicknesses: (
- '1': 1px,
- '2': 2px,
-);
-@each $thickness, $value in $thicknesses {
- .tedi-separator {
- &.tedi-separator--thickness-#{$thickness} {
- border-top-width: #{$value};
- }
-
- &--vertical.tedi-separator--thickness-#{$thickness} {
- border-left-width: #{$value};
- }
+ &.tedi-separator--vertical {
+ min-height: 3rem;
}
}
@@ -138,58 +97,139 @@ $thicknesses: (
width: var(--separator-dotted-dot-sm);
height: var(--separator-dotted-dot-sm);
content: '';
- border-radius: 100%;
+ border-radius: 50%;
@include mixins.print-grayscale;
}
- &.tedi-separator--dot-only-extra-small::before {
- width: var(--separator-dotted-dot-xs);
- height: var(--separator-dotted-dot-xs);
+ &.tedi-separator--dot-style-outlined::before {
+ background: transparent;
}
+}
- &.tedi-separator--dot-only-small::before {
- width: var(--separator-dotted-dot-sm);
- height: var(--separator-dotted-dot-sm);
+@each $name, $color in $dot-colors {
+ .tedi-separator--#{$name} {
+ border-color: $color;
+
+ &::before {
+ background-color: $color;
+ }
+
+ &.tedi-separator--dot-style-outlined:not(.tedi-separator--dot-only)::before {
+ border-color: $color;
+ }
}
+}
- &.tedi-separator--dot-only-medium::before {
- width: var(--separator-dotted-dot-md);
- height: var(--separator-dotted-dot-md);
+@each $size, $var in (extra-small: xs, small: sm, medium: md, large: lg) {
+ .tedi-separator--dot-only-#{$size}::before,
+ .tedi-separator--dotted-#{$size}::before,
+ .tedi-separator--dot-style-outlined.tedi-separator--dotted-#{$size}::before {
+ width: var(--separator-dot-size-#{$var});
+ height: var(--separator-dot-size-#{$var});
}
+}
- &.tedi-separator--dot-only-large::before {
- width: var(--separator-dotted-dot-lg);
- height: var(--separator-dotted-dot-lg);
+@each $axis in (horizontal, vertical) {
+ @each $pos in (start, center, end) {
+ .tedi-separator--#{$axis}.tedi-separator--dot-position-#{$pos}::before {
+ @if $axis == horizontal {
+ @if $pos == start {
+ right: auto;
+ left: 0;
+ } @else if $pos == center {
+ right: 0;
+ left: 0;
+ margin: auto;
+ } @else {
+ right: 0;
+ left: auto;
+ }
+ } @else {
+ @if $pos == start {
+ top: 0;
+ bottom: auto;
+ } @else if $pos == center {
+ top: 0;
+ bottom: 0;
+ margin: auto 0;
+ } @else {
+ top: auto;
+ bottom: 0;
+ }
+ }
+ }
}
+}
- &.tedi-separator--primary::before {
- background-color: var(--general-border-primary);
+.tedi-separator--horizontal.tedi-separator--dot-position-custom::before {
+ right: auto;
+ left: var(--separator-dot-position);
+}
+
+.tedi-separator--vertical.tedi-separator--dot-position-custom::before {
+ top: var(--separator-dot-position);
+ bottom: auto;
+}
+
+@each $size, $value in $sizes {
+ .tedi-separator--spacing-#{$size}:not(.tedi-separator--dot-only) {
+ margin: #{$value} 0;
+ }
+
+ .tedi-separator--dot-only.tedi-separator--spacing-#{$size} {
+ &.tedi-separator--horizontal {
+ margin-right: $value;
+ margin-left: $value;
+ }
+
+ &.tedi-separator--vertical {
+ margin-top: $value;
+ margin-bottom: $value;
+ }
}
- &.tedi-separator--secondary::before {
- background-color: var(--general-border-secondary);
+ @each $side in (top, bottom, left, right) {
+ .tedi-separator--#{$side}-#{$size} {
+ margin-#{$side}: $value;
+ }
}
+}
- &.tedi-separator--accent::before {
- background-color: var(--general-border-accent);
+@each $thickness, $value in $thicknesses {
+ .tedi-separator--thickness-#{$thickness} {
+ border-top-width: $value;
+ }
+ .tedi-separator--vertical.tedi-separator--thickness-#{$thickness} {
+ border-left-width: $value;
}
}
-.tedi-separator--horizontal {
- &.tedi-separator--dotted::before,
- &.tedi-separator--dotted-small::before {
- right: 0;
- left: 0;
- margin: 0 auto;
- transform: initial;
+@each $axis in (horizontal, vertical) {
+ @each $size, $var in (extra-small: xs, small: sm, medium: md, large: lg) {
+ .tedi-separator--#{$axis}.tedi-separator--dotted-#{$size}::before,
+ .tedi-separator--#{$axis}.tedi-separator--dot-style-outlined.tedi-separator--dotted-#{$size}::before {
+ @if $axis == horizontal {
+ top: calc((var(--separator-dot-size-#{$var}) / 2 + var(--separator-thickness)) * -1);
+ } @else {
+ left: calc((var(--separator-dot-size-#{$var}) / 2 + var(--separator-thickness) / 2) * -1);
+ }
+ }
}
+}
- &.tedi-separator--dotted::before {
- top: calc(var(--separator-dotted-dot-lg) / 2 * -1);
+.tedi-separator--dot-style-outlined {
+ &::before {
+ background-color: var(--general-surface-primary);
+ border-style: solid;
+ border-width: var(--separator-thickness);
}
- &.tedi-separator--dotted-small::before {
- top: calc(var(--separator-dotted-dot-md) / 2 * -1);
+ @each $name, $color in $dot-colors {
+ &.tedi-separator--#{$name}::before,
+ &.tedi-separator--dotted-#{$name}::before {
+ z-index: 10;
+ border-color: $color;
+ }
}
}
diff --git a/src/tedi/components/misc/separator/separator.spec.tsx b/src/tedi/components/misc/separator/separator.spec.tsx
index 65d9c7e2..694961d9 100644
--- a/src/tedi/components/misc/separator/separator.spec.tsx
+++ b/src/tedi/components/misc/separator/separator.spec.tsx
@@ -17,132 +17,141 @@ describe('Separator Component', () => {
});
});
- const renderComponent = (props: SeparatorProps) => render();
+ const renderComponent = (props: P) =>
+ render();
- it('should render a div element by default', () => {
- const { getByTestId } = renderComponent({});
+ it('renders a div by default', () => {
+ const { getByTestId } = renderComponent({ element: 'div' });
expect(getByTestId('separator').tagName).toBe('DIV');
});
- it('should render the specified element', () => {
+ it('renders the specified element', () => {
const { getByTestId } = renderComponent({ element: 'hr' });
expect(getByTestId('separator').tagName).toBe('HR');
});
- it('should apply default classes', () => {
- const { getByTestId } = renderComponent({});
- const separator = getByTestId('separator');
- expect(separator).toHaveClass(styles['tedi-separator']);
- expect(separator).toHaveClass(styles['tedi-separator--thickness-1']);
- expect(separator).toHaveClass(styles['tedi-separator--horizontal']);
- });
-
- it('should apply additional classes from className prop', () => {
- const { getByTestId } = renderComponent({ className: 'custom-class' });
- expect(getByTestId('separator')).toHaveClass('custom-class');
- });
-
- it('should apply correct spacing class', () => {
- const { getByTestId } = renderComponent({ spacing: 1.25 });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--spacing-1-25']);
- });
+ it('applies base and default classes', () => {
+ const { getByTestId } = renderComponent({ axis: 'horizontal' });
+ const el = getByTestId('separator');
- it('should apply correct top and bottom spacing classes', () => {
- const { getByTestId } = renderComponent({ topSpacing: 0.5, bottomSpacing: 1 });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--top-0-5']);
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--bottom-1']);
+ expect(el).toHaveClass(styles['tedi-separator']);
+ expect(el).toHaveClass(styles['tedi-separator--horizontal']);
+ expect(el).toHaveClass(styles['tedi-separator--thickness-1']);
});
- it('should apply axis specific class', () => {
- const { getByTestId } = renderComponent({ axis: 'vertical' });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--vertical']);
+ it('applies custom className', () => {
+ const { getByTestId } = renderComponent({ className: 'my-extra-class' });
+ expect(getByTestId('separator')).toHaveClass('my-extra-class');
});
- it('should apply color class', () => {
+ it('applies color modifier', () => {
const { getByTestId } = renderComponent({ color: 'accent' });
expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--accent']);
});
- it('should apply variant class', () => {
- const { getByTestId } = renderComponent({ variant: 'dotted' });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--dotted']);
+ it('applies vertical axis class and allows height', () => {
+ const { getByTestId } = renderComponent({ axis: 'vertical', height: 5.5 });
+ const el = getByTestId('separator');
+
+ expect(el).toHaveClass(styles['tedi-separator--vertical']);
+ expect(el).toHaveStyle('--vertical-separator-height: 5.5rem');
});
- it('should apply thickness class when no variant is used', () => {
- const { getByTestId } = renderComponent({ thickness: 2 });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--thickness-2']);
+ it('applies spacing classes — number value', () => {
+ const { getByTestId } = renderComponent({ spacing: 1.5 });
+ const el = getByTestId('separator');
+
+ expect(el).toHaveClass(styles['tedi-separator--top-1-5']);
+ expect(el).toHaveClass(styles['tedi-separator--bottom-1-5']);
});
- it('should not apply thickness class when variant is used', () => {
- const { getByTestId } = renderComponent({ variant: 'dotted', thickness: 2 });
- expect(getByTestId('separator')).not.toHaveClass(styles['tedi-separator--thickness-2']);
+ it('applies spacing classes — object value', () => {
+ const { getByTestId } = renderComponent({
+ spacing: { top: 2, bottom: 0.5, left: 1 },
+ axis: 'horizontal',
+ });
+ const el = getByTestId('separator');
+
+ expect(el).toHaveClass(styles['tedi-separator--top-2']);
+ expect(el).toHaveClass(styles['tedi-separator--bottom-0-5']);
+ expect(el).toHaveClass(styles['tedi-separator--left-1']);
});
- it('should apply isStretched class', () => {
+ it('applies isStretched class', () => {
const { getByTestId } = renderComponent({ isStretched: true });
expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--is-stretched']);
});
- it('should set height CSS variable for vertical separator', () => {
- const { getByTestId } = renderComponent({ axis: 'vertical', height: 2 });
- expect(getByTestId('separator')).toHaveStyle('--vertical-separator-height: 2rem');
+ it('applies thickness class when no variant', () => {
+ const { getByTestId } = renderComponent({ thickness: 2 });
+ expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--thickness-2']);
});
+ it('applies dotted class and dot size', () => {
+ const { getByTestId } = renderComponent({
+ variant: 'dotted',
+ dotSize: 'small',
+ dotPosition: 'center',
+ });
+ const el = getByTestId('separator');
- it('should apply dot-only variant class', () => {
- const { getByTestId } = renderComponent({ variant: 'dot-only' });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--dot-only']);
+ expect(el).toHaveClass(styles['tedi-separator--dotted']);
+ expect(el).toHaveClass(styles['tedi-separator--dotted-small']);
+ expect(el).toHaveClass(styles['tedi-separator--dot-position-center']);
});
- it('should apply correct dot size class when variant is dot-only and dotSize is extra small', () => {
- const { getByTestId } = renderComponent({ variant: 'dot-only', dotSize: 'extra-small' });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--dot-only-extra-small']);
+ it('applies custom dot position via CSS var', () => {
+ const { getByTestId } = renderComponent({
+ variant: 'dotted',
+ dotPosition: 2.75,
+ });
+ expect(getByTestId('separator')).toHaveStyle('--separator-dot-position: 2.75rem');
+ expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--dot-position-custom']);
});
- it('should apply correct dot size class when variant is dot-only and dotSize is small', () => {
- const { getByTestId } = renderComponent({ variant: 'dot-only', dotSize: 'small' });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--dot-only-small']);
- });
+ it('applies dot-only and dot size class', () => {
+ const { getByTestId } = renderComponent({
+ variant: 'dot-only',
+ dotSize: 'large',
+ dotStyle: 'filled',
+ });
+ const el = getByTestId('separator');
- it('should apply correct dot size class when variant is dot-only and dotSize is medium', () => {
- const { getByTestId } = renderComponent({ variant: 'dot-only', dotSize: 'medium' });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--dot-only-medium']);
+ expect(el).toHaveClass(styles['tedi-separator--dot-only']);
+ expect(el).toHaveClass(styles['tedi-separator--dot-only-large']);
+ expect(el).not.toHaveClass(styles['tedi-separator--dot-style-outlined']);
});
- it('should apply correct dot size class when variant is dot-only and dotSize is large', () => {
- const { getByTestId } = renderComponent({ variant: 'dot-only', dotSize: 'large' });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--dot-only-large']);
- });
+ it('applies outlined style and thickness var when outlined', () => {
+ const { getByTestId } = renderComponent({
+ variant: 'dot-only',
+ dotSize: 'medium',
+ dotStyle: 'outlined',
+ thickness: 2,
+ });
+ const el = getByTestId('separator');
- it('should not apply dot size class when variant is not dot-only', () => {
- const { getByTestId } = renderComponent({ variant: 'dotted', dotSize: 'large' });
- expect(getByTestId('separator')).not.toHaveClass(styles['tedi-separator--dot-only-large']);
+ expect(el).toHaveClass(styles['tedi-separator--dot-style-outlined']);
+ expect(el).toHaveStyle('--separator-thickness: 2px');
+ expect(el).toHaveClass(styles['tedi-separator--thickness-2']);
});
- it('should default vertical separator display to block', () => {
+ it('requires dotSize (but test fallback/default)', () => {
+ const { getByTestId } = renderComponent({
+ variant: 'dot-only',
+ dotSize: 'large',
+ });
+ expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--dot-only-large']);
+ });
+ it('defaults to block display in vertical mode', () => {
const { getByTestId } = renderComponent({ axis: 'vertical' });
expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--block']);
});
- it('should apply inline display class to vertical separator when specified', () => {
- const { getByTestId } = renderComponent({ axis: 'vertical', display: 'inline' });
- expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--inline']);
- });
-
- it('should not apply inline display class to horizontal separator', () => {
- const { getByTestId } = renderComponent({ axis: 'horizontal', display: 'block' });
- expect(getByTestId('separator')).not.toHaveClass(styles['tedi-separator--inline']);
- });
- it('should apply dotSize class when variant is dot-only', () => {
- const { getByTestId } = renderComponent({ variant: 'dot-only', dotSize: 'medium' });
- const separator = getByTestId('separator');
- expect(separator).toHaveClass(styles['tedi-separator--dot-only']);
- expect(separator).toHaveClass(styles['tedi-separator--dot-only-medium']);
- });
-
- it('should ignore dotSize when variant is not dot-only', () => {
- const { getByTestId } = renderComponent({ variant: 'dotted', dotSize: 'medium' });
- const separator = getByTestId('separator');
- expect(separator).toHaveClass(styles['tedi-separator--dotted']);
- expect(separator).not.toHaveClass(styles['tedi-separator--dot-only-medium']);
+ it('applies inline display when specified (vertical)', () => {
+ const { getByTestId } = renderComponent({
+ axis: 'vertical',
+ display: 'inline-block',
+ });
+ expect(getByTestId('separator')).toHaveClass(styles['tedi-separator--inline-block']);
});
});
diff --git a/src/tedi/components/misc/separator/separator.stories.tsx b/src/tedi/components/misc/separator/separator.stories.tsx
index 5d5a4f6f..385b2a01 100644
--- a/src/tedi/components/misc/separator/separator.stories.tsx
+++ b/src/tedi/components/misc/separator/separator.stories.tsx
@@ -1,11 +1,10 @@
import { Meta, StoryFn, StoryObj } from '@storybook/react';
-import { Fragment } from 'react/jsx-runtime';
import { Text } from '../../base/typography/text/text';
import { Card, CardContent } from '../../cards/card';
import { Col, Row } from '../../layout/grid';
import { VerticalSpacing } from '../../layout/vertical-spacing';
-import Separator, { SeparatorProps } from './separator';
+import Separator, { DotSize, SeparatorProps } from './separator';
/**
* Figma ↗
@@ -32,86 +31,254 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
-const colorArray: SeparatorProps['color'][] = ['primary', 'secondary', 'accent'];
+const spacingArray: SeparatorProps['spacing'][] = [0, 0.5, 1, 1.5, 2, 2.5];
+const sizeArray: SeparatorProps['dotSize'][] = ['large', 'medium'];
+type TemplateMultipleProps = SeparatorProps & {
+ array: Type[];
+ property: keyof SeparatorProps;
+};
+const Template: StoryFn = (args) => ;
-const Template: StoryFn = (args) => (
- <>
- Some content
-
- Other content
- >
-);
+const SizesTemplate: StoryFn = (args) => {
+ const { array } = args;
-const ColorsAndThickness: StoryFn = (args) => (
- <>
- {colorArray.map((color) => (
-
-
-
-
+ return (
+
+ {array.map((value, key) => (
+
+
+ {value === 'large' ? 'Large' : 'Medium'}
-
-
-
-
+
+
+
+
+
-
- ))}
- >
-);
+ ))}
+
+ );
+};
-const VerticalColorTemplate: StoryFn = (args) => (
+const ColorsAndThickness: StoryFn = (args) => (
- {colorArray.map((color) => (
-
-
-
-
-
-
-
-
-
-
- ))}
+
+
+
+
);
-const DotOnlyTemplate: StoryFn = (args) => (
+const SpacingHorizontal: StoryFn = (args) => (
-
-
-
-
+ {spacingArray.map((spacing, index) => (
+
+ ))}
);
+const SpacingVertical: StoryFn = (args) => (
+
+ {spacingArray.map((spacing, index) => (
+
+
+
+ ))}
+
+);
+
export const Default: Story = {
render: Template,
args: { spacing: 1 },
};
-export const HorizontalColors: Story = {
+export const HorizontalSpacings: Story = {
+ render: SpacingHorizontal,
+ args: {
+ axis: 'horizontal',
+ },
+};
+
+export const HorizontalThickness: Story = {
render: ColorsAndThickness,
- args: { spacing: 1 },
};
-export const VerticalColors: Story = {
- render: VerticalColorTemplate,
- args: { axis: 'vertical', height: 5 },
+export const Vertical: Story = {
+ render: Template,
+ args: { axis: 'vertical', height: 3 },
+};
+
+export const VerticalSpacings: Story = {
+ render: SpacingVertical,
+ args: {
+ axis: 'vertical',
+ height: 3,
+ display: 'inline-block',
+ },
+};
+
+export const VerticalThickness: Story = {
+ render: ColorsAndThickness,
+ args: { axis: 'vertical', height: 3, display: 'inline' },
};
-export const PaddedEven: Story = {
+export const DottedLineHorizontal: Story = {
render: Template,
- args: { spacing: 1 },
+ args: { axis: 'horizontal', variant: 'dotted', color: 'accent', dotPosition: 'center' },
};
-export const PaddedUneven: Story = {
+export const DottedLineVertical: Story = {
render: Template,
- args: { topSpacing: 2.5, bottomSpacing: 0.5 },
+ args: { axis: 'vertical', variant: 'dotted', color: 'accent', height: 5, dotPosition: 'center' },
+};
+
+export const Sizes: StoryObj = {
+ render: SizesTemplate,
+
+ args: {
+ property: 'dotSize',
+ array: sizeArray,
+ },
+};
+
+export const SpacingTopDefault: Story = {
+ render: () => {
+ return (
+
+
+
+
+
+
+
+
+ );
+ },
+};
+
+export const SpacingTopSmall: Story = {
+ render: () => {
+ return (
+
+
+
+
+
+
+
+
+ );
+ },
+};
+
+export const Position: Story = {
+ render: () => {
+ return (
+ <>
+
+
+
+ Start
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Center
+
+
+
+
+
+
+
+
+
+
+
+
+ End
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ },
};
const TemplateVertical: StoryFn = (args) => (
@@ -137,88 +304,132 @@ const TemplateVertical: StoryFn = (args) => (
);
-export const VerticalThick: Story = {
- render: TemplateVertical,
+export const DotFilled: Story = {
+ render: () => {
+ return (
+ <>
+
+ >
+ );
+ },
+};
+
+export const DotOutlined: Story = {
+ render: () => {
+ return (
+ <>
+
+ >
+ );
+ },
+};
+
+const dotSizeToPxMap: Record = {
+ xs: '2px',
+ sm: '4px',
+ md: '8px',
+ lg: '15px',
+};
+
+const DottedSizesTemplate: StoryFn = (args) => {
+ const { array } = args;
+
+ return (
+
+ {array.map((value, key) => (
+
+
+ {value !== undefined ? dotSizeToPxMap[value] || value : '—'}
+
+
+
+
+
+
+
+
+ ))}
+
+ );
+};
+
+export const DottedSizes: StoryObj = {
+ render: DottedSizesTemplate,
args: {
- axis: 'horizontal',
- thickness: 1,
- isStretched: true,
- topSpacing: 1,
- bottomSpacing: 1,
- md: { axis: 'vertical', thickness: 1 },
+ property: 'dotSize',
+ array: ['extra-small', 'small', 'medium', 'large'],
},
};
-export const VerticalDotted: Story = {
+const InlineSeparatorTemplate: StoryFn = (args) => {
+ const { dotPosition, ...safeArgs } = args;
+
+ return (
+ <>
+
+ Lorem ipsum dolor sit, amet
+
+ consectetur adipisicing elit.
+
+
+ Lorem ipsum dolor sit, amet
+
+ consectetur adipisicing elit.
+
+
+ Lorem ipsum dolor sit, amet
+
+ consectetur adipisicing elit.
+
+
+ Lorem ipsum dolor sit, amet
+
+ consectetur adipisicing elit.
+
+ >
+ );
+};
+
+export const InlineSeparatorUsage: Story = {
+ render: InlineSeparatorTemplate,
+ args: { axis: 'vertical', display: 'inline' },
+};
+
+export const VerticalDottedCardExample: Story = {
render: TemplateVertical,
args: {
axis: 'horizontal',
variant: 'dotted',
color: 'accent',
- topSpacing: 1,
- bottomSpacing: 1,
+ spacing: 1,
isStretched: true,
+ dotPosition: 1.25,
md: { axis: 'vertical' },
},
};
-export const VerticalDottedSmall: Story = {
+export const VerticalDottedSmallCardExample: Story = {
render: TemplateVertical,
args: {
axis: 'horizontal',
- topSpacing: 1,
- bottomSpacing: 1,
- variant: 'dotted-small',
+ spacing: 1,
+ variant: 'dotted',
+ dotSize: 'medium',
color: 'accent',
isStretched: true,
+ dotPosition: 1.25,
md: { axis: 'vertical' },
},
};
-
-export const HorizontalDottedSeparator: Story = {
- render: () => (
-
-
-
-
-
-
-
-
- ),
-};
-
-export const DotOnly: Story = {
- render: DotOnlyTemplate,
- args: { spacing: 0.5 },
-};
-
-const InlineSeparatorTemplate: StoryFn = (args) => (
-
-
- Lorem ipsum dolor sit, amet
-
- consectetur adipisicing elit.
-
-
- Lorem ipsum dolor sit, amet
-
- consectetur adipisicing elit.
-
-
- Lorem ipsum dolor sit, amet
-
- consectetur adipisicing elit.
-
-
- Lorem ipsum dolor sit, amet
-
- consectetur adipisicing elit.
-
-
-);
-
-export const InlineSeparatorUsedInText: Story = {
- render: InlineSeparatorTemplate,
- args: { axis: 'vertical', display: 'inline' },
-};
diff --git a/src/tedi/components/misc/separator/separator.tsx b/src/tedi/components/misc/separator/separator.tsx
index bcf4f768..cabe5bd3 100644
--- a/src/tedi/components/misc/separator/separator.tsx
+++ b/src/tedi/components/misc/separator/separator.tsx
@@ -4,130 +4,162 @@ import { CSSProperties } from 'react';
import { BreakpointSupport, useBreakpointProps } from '../../../helpers';
import styles from './separator.module.scss';
-export type SeparatorSpacing = 0 | 0.25 | 0.5 | 0.75 | 1 | 1.25 | 1.5 | 1.75 | 2 | 2.5 | 5;
+export type SeparatorVariant = 'dotted' | 'dot-only';
+export type DotSize = 'large' | 'medium' | 'small' | 'extra-small';
+export type DotStyle = 'filled' | 'outlined';
+export type DotPosition = 'start' | 'center' | 'end' | number;
+
+/**
+ * Margin/padding-like spacing around the separator
+ * - number → uniform spacing on main axis
+ * - object → fine-grained control (top/bottom/left/right)
+ */
+export type SeparatorSpacing =
+ | number
+ | {
+ top?: number;
+ bottom?: number;
+ left?: number;
+ right?: number;
+ };
export interface SeparatorSharedProps {
/**
- * Additional class.
+ * Additional class names
*/
className?: string;
/**
- * Rendered HTML element.
- * @default div
+ * HTML element to render — most common are 'hr', 'div', 'span'
*/
element?: 'hr' | 'div' | 'span';
/**
- * Whether the separator should stretch to fill the full spacing inside cardContent.
+ * When true, the separator stretches to fill available space (100%)
*/
isStretched?: boolean;
- /*
- * Color of separator
- * @default default
+ /**
+ * Semantic color token
+ * @default primary
*/
color?: 'primary' | 'secondary' | 'accent';
- /*
- * Separator style variant.
- */
- variant?: 'dotted' | 'dotted-small' | 'dot-only';
- /*
- * Dot size.
- * Only used when variant="dot-only"
+ /**
+ * Visual style — line with dots vs standalone centered dot(s)
*/
- dotSize?: 'large' | 'medium' | 'small' | 'extra-small';
- /*
- * Thickness in pixels (ignored if variant is used).
- * @default 1
+ variant?: SeparatorVariant;
+ /**
+ * Line thickness in pixels (1 or 2) — affects outlined & solid lines
*/
thickness?: 1 | 2;
/**
- * Spacing applied based on the axis:
- * - For horizontal axis, spacing is applied to top and bottom of the separator.
- * - For vertical axis, spacing is applied to left and right of the separator.
+ * Spacing (margin) around the separator
+ * @example
+ * spacing={16} // 16px top & bottom (horizontal) or left & right (vertical)
+ * spacing={{ top: 24, bottom: 8 }}
*/
spacing?: SeparatorSpacing;
}
-
export interface SeparatorVerticalProps extends SeparatorSharedProps {
/**
- * Height of separator. Use with vertical axis, when full-width separator is not needed.
- * Height can be number in rem units. It's customizable to allow for more flexibility around X components.
+ * Must be set to 'vertical'
+ */
+ axis: 'vertical';
+ /**
+ * Height of the vertical separator in rem units
*/
height?: number;
/**
- * Axis of separator, vertical and horizontal separators support different props
+ * CSS display value — usually 'block' or 'inline-block'
*/
- axis: 'vertical';
- topSpacing?: undefined;
- bottomSpacing?: undefined;
- display?: 'block' | 'inline';
+ display?: 'block' | 'inline' | 'inline-block';
}
-
export interface SeparatorHorizontalProps extends SeparatorSharedProps {
/**
- * Spacing on top of separator. Ignored when spacing is also used. Only for horizontal axis.
+ * Must be set to 'horizontal' or left undefined (defaults to horizontal)
*/
- topSpacing?: SeparatorSpacing;
+ axis?: 'horizontal';
/**
- * Spacing on bottom of separator. Ignored when spacing is also used. Only for horizontal axis.
- */
- bottomSpacing?: SeparatorSpacing;
+ Vertical height is not used in horizontal mode
+ */
+ height?: undefined;
/**
- * Axis of separator, vertical and horizontal separators support different props
+ * Display is forced to 'block' in horizontal mode
*/
- axis?: 'horizontal';
- height?: undefined;
display?: 'block';
}
-export type SeparatorBreakpointProps = {
+type DottedSeparatorProps = {
+ variant?: 'dotted';
+ dotSize?: DotSize;
+ dotStyle?: DotStyle;
/**
- * Spacing values based on breakpoints.
+ * Position of the single dot
+ * @example
+ * 'center' | 'start' | 'end' | 2.5 // 2.5rem from start
*/
- spacing?: Omit;
- /**
- * Height values based on breakpoints (for vertical separators).
- */
- height?: Omit;
+ dotPosition?: DotPosition;
+};
+
+type DotOnlySeparatorProps = {
+ variant: 'dot-only';
+ dotSize: DotSize;
+ dotStyle?: DotStyle;
+ dotPosition?: never;
+};
+
+export type SeparatorBreakpointProps = {
+ spacing?: SeparatorSpacing;
+ height?: number;
axis?: 'horizontal' | 'vertical';
};
export type SeparatorProps = BreakpointSupport<
- | (
- | SeparatorHorizontalProps
- | (SeparatorVerticalProps & {
- variant?: 'dotted' | 'dotted-small';
- dotSize?: undefined;
- })
- )
- | (
- | SeparatorHorizontalProps
- | (SeparatorVerticalProps & {
- variant: 'dot-only';
- dotSize: 'large' | 'medium' | 'small' | 'extra-small';
- })
- )
+ | (SeparatorHorizontalProps & (DottedSeparatorProps | DotOnlySeparatorProps))
+ | (SeparatorVerticalProps & (DottedSeparatorProps | DotOnlySeparatorProps))
> &
SeparatorBreakpointProps;
export const Separator = (props: SeparatorProps): JSX.Element => {
const { getCurrentBreakpointProps } = useBreakpointProps(props.defaultServerBreakpoint);
+
const {
className,
element: Element = 'div',
isStretched,
spacing,
- topSpacing,
- bottomSpacing,
axis = 'horizontal',
- color = 'default',
+ color = 'primary',
variant,
thickness = 1,
height,
- dotSize,
+ dotSize = 'large',
+ dotStyle = 'filled',
+ dotPosition,
display = 'block',
...rest
} = getCurrentBreakpointProps(props);
+ const isNumericDotPosition = typeof dotPosition === 'number';
+ const resolvedDotPosition = variant !== 'dot-only' && !isNumericDotPosition ? dotPosition : undefined;
+
+ let top: number | undefined;
+ let bottom: number | undefined;
+ let left: number | undefined;
+ let right: number | undefined;
+
+ if (typeof spacing === 'number') {
+ if (axis === 'horizontal') {
+ top = bottom = spacing;
+ left = right = 0;
+ } else {
+ left = right = spacing;
+ top = bottom = 0;
+ }
+ } else if (typeof spacing === 'object' && spacing !== null) {
+ top = spacing.top ?? (axis === 'horizontal' ? spacing.top ?? spacing.bottom ?? 0 : 0);
+ bottom = spacing.bottom ?? (axis === 'horizontal' ? spacing.top ?? spacing.bottom ?? 0 : 0);
+ left = spacing.left ?? (axis === 'vertical' ? spacing.left ?? spacing.right ?? 0 : 0);
+ right = spacing.right ?? (axis === 'vertical' ? spacing.left ?? spacing.right ?? 0 : 0);
+ }
+
const SeparatorBEM = cn(
styles['tedi-separator'],
className,
@@ -135,19 +167,33 @@ export const Separator = (props: SeparatorProps): JSX.Element => {
{ [styles[`tedi-separator--${axis}`]]: axis },
{ [styles[`tedi-separator--${variant}`]]: variant },
{ [styles[`tedi-separator--${display}`]]: display },
- { [styles[`tedi-separator--${variant}-${dotSize}`]]: variant && dotSize },
- { [styles[`tedi-separator--thickness-${thickness}`]]: thickness && !variant },
+ { [styles[`tedi-separator--${variant}-${dotSize}`]]: variant === 'dot-only' && dotSize },
+ { [styles[`tedi-separator--dot-style-${dotStyle}`]]: variant && dotStyle },
+ { [styles[`tedi-separator--dotted-${dotSize}`]]: variant === 'dotted' && dotSize },
+ { [styles[`tedi-separator--dot-position-${resolvedDotPosition}`]]: resolvedDotPosition && variant !== 'dot-only' },
+ { [styles['tedi-separator--dot-position-custom']]: isNumericDotPosition },
+ {
+ [styles[`tedi-separator--thickness-${thickness}`]]: thickness || dotStyle === 'outlined' ? thickness : undefined,
+ },
{ [styles['tedi-separator--is-stretched']]: isStretched },
- { [styles[`tedi-separator--spacing-${spacing}`.replace('.', '-')]]: spacing },
- { [styles[`tedi-separator--top-${topSpacing}`.replace('.', '-')]]: !spacing && topSpacing },
- { [styles[`tedi-separator--bottom-${bottomSpacing}`.replace('.', '-')]]: !spacing && bottomSpacing }
+ { [styles[`tedi-separator--top-${top}`.replace('.', '-')]]: top },
+ { [styles[`tedi-separator--bottom-${bottom}`.replace('.', '-')]]: bottom },
+ { [styles[`tedi-separator--left-${left}`.replace('.', '-')]]: left },
+ { [styles[`tedi-separator--right-${right}`.replace('.', '-')]]: right }
);
const getCssVars = () => {
const cssvars: CSSProperties = {};
-
if (height) cssvars['--vertical-separator-height'] = `${height}rem`;
+ if (thickness) {
+ cssvars['--separator-thickness'] = `${thickness}px`;
+ }
+
+ if (variant === 'dotted' && isNumericDotPosition) {
+ cssvars['--separator-dot-position'] = `${dotPosition}rem`;
+ }
+
return cssvars;
};