@wallarm-org/design-system 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,18 +2,22 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  import { TestIdProvider } from "../../utils/testId.js";
4
4
  import { emptyStateVariants } from "./classes.js";
5
+ import { EmptyStateProvider } from "./EmptyStateContext.js";
5
6
  const EmptyState = ({ ref, type = 'collection-empty', className, children, 'data-testid': testId, ...props })=>/*#__PURE__*/ jsx(TestIdProvider, {
6
7
  value: testId,
7
- children: /*#__PURE__*/ jsx("div", {
8
- ...props,
9
- ref: ref,
10
- role: "status",
11
- "data-slot": "empty-state",
12
- "data-testid": testId,
13
- className: cn(emptyStateVariants({
14
- type
15
- }), className),
16
- children: children
8
+ children: /*#__PURE__*/ jsx(EmptyStateProvider, {
9
+ value: type,
10
+ children: /*#__PURE__*/ jsx("div", {
11
+ ...props,
12
+ ref: ref,
13
+ role: "status",
14
+ "data-slot": "empty-state",
15
+ "data-testid": testId,
16
+ className: cn(emptyStateVariants({
17
+ type
18
+ }), className),
19
+ children: children
20
+ })
17
21
  })
18
22
  });
19
23
  EmptyState.displayName = 'EmptyState';
@@ -0,0 +1,3 @@
1
+ import type { EmptyStateType } from './classes';
2
+ export declare const EmptyStateProvider: import("react").Provider<EmptyStateType>;
3
+ export declare const useEmptyStateType: () => EmptyStateType;
@@ -0,0 +1,5 @@
1
+ import { createContext, useContext } from "react";
2
+ const EmptyStateContext = /*#__PURE__*/ createContext('collection-empty');
3
+ const EmptyStateProvider = EmptyStateContext.Provider;
4
+ const useEmptyStateType = ()=>useContext(EmptyStateContext);
5
+ export { EmptyStateProvider, useEmptyStateType };
@@ -1,6 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  import { useTestId } from "../../utils/testId.js";
4
+ import { emptyStateIllustrationVariants } from "./classes.js";
4
5
  const EmptyStateIllustration = ({ ref, className, children, ...props })=>{
5
6
  const testId = useTestId('illustration');
6
7
  return /*#__PURE__*/ jsx("div", {
@@ -8,7 +9,7 @@ const EmptyStateIllustration = ({ ref, className, children, ...props })=>{
8
9
  ref: ref,
9
10
  "data-slot": "empty-state-illustration",
10
11
  "data-testid": testId,
11
- className: cn('text-text-secondary p-8 border border-border-primary bg-bg-light-primary rounded-full w-36 h-36 flex items-center justify-center', className),
12
+ className: cn(emptyStateIllustrationVariants(), className),
12
13
  children: children
13
14
  });
14
15
  };
@@ -1,14 +1,19 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  import { useTestId } from "../../utils/testId.js";
4
+ import { emptyStateMessageVariants } from "./classes.js";
5
+ import { useEmptyStateType } from "./EmptyStateContext.js";
4
6
  const EmptyStateMessage = ({ ref, className, children, ...props })=>{
5
7
  const testId = useTestId('message');
8
+ const type = useEmptyStateType();
6
9
  return /*#__PURE__*/ jsx("div", {
7
10
  ...props,
8
11
  ref: ref,
9
12
  "data-slot": "empty-state-message",
10
13
  "data-testid": testId,
11
- className: cn('flex flex-col items-center gap-8', className),
14
+ className: cn(emptyStateMessageVariants({
15
+ type
16
+ }), className),
12
17
  children: children
13
18
  });
14
19
  };
@@ -1,14 +1,19 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  import { useTestId } from "../../utils/testId.js";
4
+ import { emptyStateTitleVariants } from "./classes.js";
5
+ import { useEmptyStateType } from "./EmptyStateContext.js";
4
6
  const EmptyStateTitle = ({ ref, className, children, ...props })=>{
5
7
  const testId = useTestId('title');
8
+ const type = useEmptyStateType();
6
9
  return /*#__PURE__*/ jsx("p", {
7
10
  ...props,
8
11
  ref: ref,
9
12
  "data-testid": testId,
10
13
  "data-slot": "empty-state-title",
11
- className: cn('font-pixel text-base leading-base text-text-primary text-center break-words', className),
14
+ className: cn(emptyStateTitleVariants({
15
+ type
16
+ }), className),
12
17
  children: children
13
18
  });
14
19
  };
@@ -2,3 +2,30 @@ export type EmptyStateType = 'collection-empty' | 'no-results';
2
2
  export declare const emptyStateVariants: (props?: ({
3
3
  type?: "collection-empty" | "no-results" | null | undefined;
4
4
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
5
+ /**
6
+ * The medallion: a 36px raised tile holding a 20px glyph, which with `p-8` fills
7
+ * the content box exactly — the hairline is an inset ring in the shadow list,
8
+ * not a real border, so it doesn't eat into the padding. `text-xl` sets the
9
+ * 20px em box, so an icon left at its default `size='inherit'` lands on spec
10
+ * without every call site restating a size.
11
+ *
12
+ * The Figma master's placeholder is 16px at 10px padding, but every applied
13
+ * instance and the medallion spec sheet itself use 20px at 8px — the file is
14
+ * inconsistent here, and the 20/8 pairing is the one that fills the tile.
15
+ *
16
+ * There is deliberately no unframed variant — the spec never shows a bare glyph,
17
+ * at either scale, so an illustration always carries the tile.
18
+ *
19
+ * `rounded-[15px]` is off the radius scale on purpose: it's the value Figma
20
+ * reports for this node, and fitting a superellipse to the rendered outline
21
+ * agrees — a plain circular corner at r=14.5 matches to 0.29px, and r=15 to
22
+ * 0.35px, while the nearest token (16) is off by 0.73px. There is no corner
23
+ * smoothing on this shape, so no `corner-shape` is needed.
24
+ */
25
+ export declare const emptyStateIllustrationVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
26
+ export declare const emptyStateMessageVariants: (props?: ({
27
+ type?: "collection-empty" | "no-results" | null | undefined;
28
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
29
+ export declare const emptyStateTitleVariants: (props?: ({
30
+ type?: "collection-empty" | "no-results" | null | undefined;
31
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
@@ -1,13 +1,36 @@
1
1
  import { cva } from "class-variance-authority";
2
- const emptyStateVariants = cva('flex flex-col items-center text-center gap-16 m-[0_auto]', {
2
+ const emptyStateVariants = cva('flex flex-col items-center text-center m-[0_auto]', {
3
3
  variants: {
4
4
  type: {
5
- 'collection-empty': 'max-w-[560px] min-w-[256px]',
6
- 'no-results': 'w-[240px] py-16'
5
+ 'collection-empty': 'gap-16 py-24 max-w-[560px] min-w-[256px]',
6
+ 'no-results': 'gap-8 py-8 w-[240px]'
7
7
  }
8
8
  },
9
9
  defaultVariants: {
10
10
  type: 'collection-empty'
11
11
  }
12
12
  });
13
- export { emptyStateVariants };
13
+ const emptyStateIllustrationVariants = cva('empty-state-medallion flex items-center justify-center shrink-0 size-36 rounded-[15px] p-8 text-xl text-icon-secondary');
14
+ const emptyStateMessageVariants = cva('flex flex-col items-center', {
15
+ variants: {
16
+ type: {
17
+ 'collection-empty': 'gap-8',
18
+ 'no-results': 'gap-4'
19
+ }
20
+ },
21
+ defaultVariants: {
22
+ type: 'collection-empty'
23
+ }
24
+ });
25
+ const emptyStateTitleVariants = cva('text-center break-words', {
26
+ variants: {
27
+ type: {
28
+ 'collection-empty': 'font-pixel text-base leading-base text-text-primary',
29
+ 'no-results': 'font-sans-display text-sm font-medium text-text-secondary'
30
+ }
31
+ },
32
+ defaultVariants: {
33
+ type: 'collection-empty'
34
+ }
35
+ });
36
+ export { emptyStateIllustrationVariants, emptyStateMessageVariants, emptyStateTitleVariants, emptyStateVariants };
@@ -0,0 +1,3 @@
1
+ import type { FC } from 'react';
2
+ import { type SvgIconProps } from './SvgIcon';
3
+ export declare const Shapes: FC<SvgIconProps>;
@@ -0,0 +1,12 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { SvgIcon } from "./SvgIcon.js";
3
+ const Shapes = (props)=>/*#__PURE__*/ jsx(SvgIcon, {
4
+ ...props,
5
+ viewBox: "0 0 24 24",
6
+ children: /*#__PURE__*/ jsx("path", {
7
+ d: "M9 13C10.105 13 11 13.895 11 15V20C11 21.105 10.105 22 9 22H4C2.895 22 2 21.105 2 20V15C2 13.895 2.895 13 4 13H9ZM17.5 13C19.985 13 22 15.015 22 17.5C22 19.985 19.985 22 17.5 22C15.015 22 13 19.985 13 17.5C13 15.015 15.015 13 17.5 13ZM4 20H9V15H4V20ZM17.5 15C16.119 15 15 16.119 15 17.5C15 18.881 16.119 20 17.5 20C18.881 20 20 18.881 20 17.5C20 16.119 18.881 15 17.5 15ZM12.169 1.647C12.314 1.661 12.457 1.692 12.594 1.742L12.794 1.83L12.981 1.943C13.161 2.068 13.315 2.226 13.435 2.41C13.438 2.416 13.443 2.422 13.446 2.428L17.148 8.371C17.287 8.584 17.377 8.824 17.41 9.075L17.422 9.19L17.425 9.307C17.424 9.577 17.358 9.843 17.233 10.084C17.09 10.359 16.875 10.59 16.61 10.751C16.346 10.912 16.041 10.999 15.731 11H8.3V10.999C8.005 11.006 7.713 10.937 7.453 10.797C7.178 10.648 6.948 10.425 6.791 10.154C6.634 9.883 6.555 9.573 6.562 9.26C6.57 8.946 6.664 8.641 6.834 8.378L10.554 2.467L10.555 2.469C10.695 2.233 10.889 2.034 11.123 1.891C11.373 1.738 11.658 1.651 11.95 1.641L12.169 1.647ZM8.806 9H15.184L12.011 3.905L8.806 9Z",
8
+ fill: "currentColor"
9
+ })
10
+ });
11
+ Shapes.displayName = 'ShapesIcon';
12
+ export { Shapes };
@@ -438,6 +438,7 @@ export { Ses } from './Ses';
438
438
  export { SesNeutral } from './SesNeutral';
439
439
  export { Settings } from './Settings';
440
440
  export { Settings2 } from './Settings2';
441
+ export { Shapes } from './Shapes';
441
442
  export { Share } from './Share';
442
443
  export { Sheet } from './Sheet';
443
444
  export { Shield } from './Shield';
@@ -438,6 +438,7 @@ export { Ses } from "./Ses.js";
438
438
  export { SesNeutral } from "./SesNeutral.js";
439
439
  export { Settings } from "./Settings.js";
440
440
  export { Settings2 } from "./Settings2.js";
441
+ export { Shapes } from "./Shapes.js";
441
442
  export { Share } from "./Share.js";
442
443
  export { Sheet } from "./Sheet.js";
443
444
  export { Shield } from "./Shield.js";
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.6.1",
3
- "generatedAt": "2026-08-25T09:31:57.422Z",
2
+ "version": "1.6.2",
3
+ "generatedAt": "2026-08-25T16:40:44.467Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Accordion",
@@ -29733,19 +29733,26 @@
29733
29733
  "examples": [
29734
29734
  {
29735
29735
  "name": "CollectionEmpty",
29736
- "code": "args => (\n <EmptyState {...args}>\n <EmptyStateIllustration>\n <Circle size='lg' />\n </EmptyStateIllustration>\n <EmptyStateMessage>\n <EmptyStateTitle>Title text goes here</EmptyStateTitle>\n <EmptyStateDescription>\n Description text explains what happened and what the user can do next.\n <br />\n One to two sentences maximum.\n </EmptyStateDescription>\n </EmptyStateMessage>\n <EmptyStateActions>\n <Button size='medium'>Reset filters</Button>\n <Button size='medium' variant='outline' color='neutral'>\n Refresh\n </Button>\n </EmptyStateActions>\n <EmptyStateLink href='#'>Link</EmptyStateLink>\n </EmptyState>\n)"
29736
+ "code": "args => (\n <EmptyState {...args}>\n <EmptyStateIllustration>\n <Shapes />\n </EmptyStateIllustration>\n <EmptyStateMessage>\n <EmptyStateTitle>Title text goes here</EmptyStateTitle>\n <EmptyStateDescription>\n Description text explains what happened and what the user can do next.\n <br />\n One to two sentences maximum.\n </EmptyStateDescription>\n </EmptyStateMessage>\n <EmptyStateActions>\n <Button size='medium'>Reset filters</Button>\n <Button size='medium' variant='outline' color='neutral'>\n Refresh\n </Button>\n </EmptyStateActions>\n <EmptyStateLink href='#'>Link</EmptyStateLink>\n </EmptyState>\n)"
29737
29737
  },
29738
29738
  {
29739
- "name": "NoResults",
29740
- "code": "args => (\n <EmptyState {...args}>\n <EmptyStateIllustration>\n <Zap size='lg' />\n </EmptyStateIllustration>\n <EmptyStateMessage>\n <EmptyStateTitle>No attacks found</EmptyStateTitle>\n <EmptyStateDescription>Try to apply different filter or reset it.</EmptyStateDescription>\n </EmptyStateMessage>\n <EmptyStateActions>\n <Button size='medium' variant='outline' color='neutral'>\n Reset filters\n </Button>\n </EmptyStateActions>\n </EmptyState>\n)"
29739
+ "name": "NoFilterResults",
29740
+ "code": "args => (\n <EmptyState {...args}>\n <EmptyStateIllustration>\n <Zap />\n </EmptyStateIllustration>\n <EmptyStateMessage>\n <EmptyStateTitle>No attacks found</EmptyStateTitle>\n <EmptyStateDescription>Try to apply different filter or reset it.</EmptyStateDescription>\n </EmptyStateMessage>\n <EmptyStateActions>\n <Button size='medium' variant='outline' color='neutral'>\n Refresh\n </Button>\n </EmptyStateActions>\n </EmptyState>\n)",
29741
+ "description": "The page-level state for a search or filter that matched nothing. Same\n`collection-empty` scale as an untouched collection — the difference is the\nCTA: a neutral \"get back to something\" action, never a create action, because\nthe data exists and the query is what needs changing."
29741
29742
  },
29742
29743
  {
29743
29744
  "name": "Minimal",
29744
- "code": "args => (\n <EmptyState {...args}>\n <EmptyStateMessage>\n <EmptyStateTitle>Nothing here</EmptyStateTitle>\n <EmptyStateDescription>This collection is empty.</EmptyStateDescription>\n </EmptyStateMessage>\n </EmptyState>\n)"
29745
+ "code": "args => (\n <EmptyState {...args}>\n <EmptyStateMessage>\n <EmptyStateTitle>No results</EmptyStateTitle>\n <EmptyStateDescription>Short description</EmptyStateDescription>\n </EmptyStateMessage>\n </EmptyState>\n)",
29746
+ "description": "The compact state at its spec default: no icon, no actions — just the title\nand subtitle. This is the shape most in-app empty states take."
29745
29747
  },
29746
29748
  {
29747
29749
  "name": "NoResultsExamples",
29748
29750
  "code": "() => (\n <HStack gap={32} align='start'>\n {/* In a chart card */}\n <Card className='w-[340px]'>\n <CardHeader>\n <CardTitle>Bar chart</CardTitle>\n </CardHeader>\n <CardContent className='flex items-center justify-center py-48'>\n <EmptyState type='no-results'>\n <EmptyStateMessage>\n <EmptyStateDescription>No data found for selected period</EmptyStateDescription>\n </EmptyStateMessage>\n </EmptyState>\n </CardContent>\n </Card>\n\n {/* In a select dropdown */}\n <VStack gap={4} className='w-[260px]'>\n <Field>\n <FieldLabel>Label</FieldLabel>\n <Select collection={emptyCollection}>\n <SelectButton placeholder='Select' />\n <SelectPositioner className='w-[260px]'>\n <SelectContent />\n </SelectPositioner>\n </Select>\n </Field>\n </VStack>\n </HStack>\n)"
29751
+ },
29752
+ {
29753
+ "name": "Medallion",
29754
+ "code": "() => (\n <HStack gap={0} className='w-[330px] overflow-hidden rounded-16 border border-border-primary'>\n {(['bg-bg-surface-1', 'bg-bg-light-primary'] as const).map(surface => (\n <div key={surface} className={cn('flex flex-1 justify-center py-28', surface)}>\n <EmptyStateIllustration>\n <ScanLine />\n </EmptyStateIllustration>\n </div>\n ))}\n </HStack>\n)",
29755
+ "description": "The medallion reads as a lit surface: a top-down gradient, a hairline border,\na soft drop shadow and an inner top highlight. Every layer resolves through\n`--color-component-empty-state-medallion-*` and\n`--shadow-empty-state-medallion`, so it holds up on both the card surface and\nthe slightly darker page surface it sits on — shown here side by side. Use the\ntoolbar theme switcher to check it in dark."
29749
29756
  }
29750
29757
  ]
29751
29758
  },
@@ -82645,13 +82652,13 @@
82645
82652
  },
82646
82653
  {
82647
82654
  "name": "EmptyCollection",
82648
- "code": "() => (\n <Table data={[]} columns={securityColumns} getRowId={row => row.id}>\n <TableEmptyState>\n <EmptyState type='collection-empty'>\n <EmptyStateIllustration>\n <Database size='lg' />\n </EmptyStateIllustration>\n <EmptyStateMessage>\n <EmptyStateTitle>No events yet</EmptyStateTitle>\n <EmptyStateDescription>\n Security events will appear here as soon as Wallarm detects traffic.\n </EmptyStateDescription>\n </EmptyStateMessage>\n <EmptyStateActions>\n <Button size='medium'>Configure source</Button>\n </EmptyStateActions>\n </EmptyState>\n </TableEmptyState>\n </Table>\n)",
82655
+ "code": "() => (\n <Table data={[]} columns={securityColumns} getRowId={row => row.id}>\n <TableEmptyState>\n <EmptyState type='collection-empty'>\n <EmptyStateIllustration>\n <Database />\n </EmptyStateIllustration>\n <EmptyStateMessage>\n <EmptyStateTitle>No events yet</EmptyStateTitle>\n <EmptyStateDescription>\n Security events will appear here as soon as Wallarm detects traffic.\n </EmptyStateDescription>\n </EmptyStateMessage>\n <EmptyStateActions>\n <Button size='medium'>Configure source</Button>\n </EmptyStateActions>\n </EmptyState>\n </TableEmptyState>\n </Table>\n)",
82649
82656
  "description": "When `data` is empty and the table is not loading, the `TableEmptyState` slot\nrenders its children. Compose the `EmptyState` component inside it to get the\nstandard illustration / message / actions layout.\n\nUse `type='collection-empty'` when there is genuinely no data yet (nothing has\nbeen created), and offer a primary action that creates the first item."
82650
82657
  },
82651
82658
  {
82652
82659
  "name": "NoResults",
82653
- "code": "() => (\n <Table data={[]} columns={securityColumns} getRowId={row => row.id}>\n <TableEmptyState>\n <EmptyState type='no-results'>\n <EmptyStateIllustration>\n <SearchX size='lg' />\n </EmptyStateIllustration>\n <EmptyStateMessage>\n <EmptyStateTitle>No results found</EmptyStateTitle>\n <EmptyStateDescription>\n No events match your current filters. Try adjusting or resetting them.\n </EmptyStateDescription>\n </EmptyStateMessage>\n <EmptyStateActions>\n <Button size='medium' variant='outline' color='neutral'>\n <FilterX />\n Reset filters\n </Button>\n </EmptyStateActions>\n </EmptyState>\n </TableEmptyState>\n </Table>\n)",
82654
- "description": "Use `type='no-results'` when data exists but the current filters or search\nmatched nothing. Offer a neutral action that clears the filters rather than\na primary \"create\" action."
82660
+ "code": "() => (\n <Table data={[]} columns={securityColumns} getRowId={row => row.id}>\n <TableEmptyState>\n <EmptyState type='collection-empty'>\n <EmptyStateIllustration>\n <SearchX />\n </EmptyStateIllustration>\n <EmptyStateMessage>\n <EmptyStateTitle>No results found</EmptyStateTitle>\n <EmptyStateDescription>\n No events match your current filters. Try adjusting or resetting them.\n </EmptyStateDescription>\n </EmptyStateMessage>\n <EmptyStateActions>\n <Button size='medium' variant='outline' color='neutral'>\n <FilterX />\n Reset filters\n </Button>\n </EmptyStateActions>\n </EmptyState>\n </TableEmptyState>\n </Table>\n)",
82661
+ "description": "When data exists but the current filters or search matched nothing, stay at\nthe `collection-empty` scale — a table body is a page-level surface, and\n`no-results` is the compact treatment for a card or menu. What changes is the\naction: a neutral one that clears the filters, never a primary \"create\"."
82655
82662
  },
82656
82663
  {
82657
82664
  "name": "ColumnResizing",
@@ -95889,6 +95896,18 @@
95889
95896
  "name": "--color-component-app-shell-bg",
95890
95897
  "value": "var(--color-slate-100)"
95891
95898
  },
95899
+ {
95900
+ "name": "--color-component-empty-state-medallion-bg-from",
95901
+ "value": "var(--color-white)"
95902
+ },
95903
+ {
95904
+ "name": "--color-component-empty-state-medallion-bg-to",
95905
+ "value": "--alpha(var(--color-slate-100) / 80%)"
95906
+ },
95907
+ {
95908
+ "name": "--color-component-empty-state-medallion-border",
95909
+ "value": "--alpha(var(--color-slate-300) / 70%)"
95910
+ },
95892
95911
  {
95893
95912
  "name": "--color-syntax-no-syntax",
95894
95913
  "value": "var(--color-slate-900)"
@@ -96951,6 +96970,18 @@
96951
96970
  "name": "--color-component-app-shell-bg",
96952
96971
  "value": "var(--color-slate-975)"
96953
96972
  },
96973
+ {
96974
+ "name": "--color-component-empty-state-medallion-bg-from",
96975
+ "value": "--alpha(var(--color-white) / 7%)"
96976
+ },
96977
+ {
96978
+ "name": "--color-component-empty-state-medallion-bg-to",
96979
+ "value": "--alpha(var(--color-white) / 2%)"
96980
+ },
96981
+ {
96982
+ "name": "--color-component-empty-state-medallion-border",
96983
+ "value": "--alpha(var(--color-white) / 13%)"
96984
+ },
96954
96985
  {
96955
96986
  "name": "--color-syntax-no-syntax",
96956
96987
  "value": "var(--color-slate-200)"
@@ -4,7 +4,7 @@ import { Text } from '../components/Text';
4
4
 
5
5
  const DESCRIPTION = [
6
6
  'Geist Pixel is the decorative typeface. Use it for empty states, and for moments that want personality rather than information.',
7
- 'There is no `Pixel` component. Pixel type is composed from the `font-pixel` utility plus the shared `text-*` size utilities, so the steps below are conventions rather than a component API. The only place it ships today is `EmptyStateTitle`, at 16/24.',
7
+ 'There is no `Pixel` component. Pixel type is composed from the `font-pixel` utility plus the shared `text-*` size utilities, so the steps below are conventions rather than a component API. The only place it ships today is `EmptyStateTitle` at the `collection-empty` scale, at 16/24 — the compact `no-results` title is sans, not pixel.',
8
8
  'One cut only: Square at regular weight. There is no light or bold, so `font-bold` on pixel type renders as a browser-synthesized fake — emphasis has to come from size or colour, never from weight.',
9
9
  'Not for UI copy, labels, or anything read at length; that is the text ramp. Keep it to short strings, where the blocky letterforms stay legible.',
10
10
  ].join('\n\n');
@@ -17,6 +17,7 @@
17
17
  @import './icon.css';
18
18
  @import './utilities/hover-overlay.css';
19
19
  @import './utilities/code-snippet-bg.css';
20
+ @import './utilities/empty-state-medallion.css';
20
21
  @import './components/drawer.css';
21
22
  @import './components/dialog.css';
22
23
  @import './components/tooltip.css';
@@ -216,6 +216,24 @@
216
216
  --color-component-toast-ic-info: var(--color-blue-400);
217
217
  --color-component-app-shell-bg: var(--color-slate-100);
218
218
 
219
+ /* EmptyState medallion — the small raised tile behind the empty-state icon.
220
+ * Reads as a lit surface: a top-down gradient, a hairline border, a soft
221
+ * drop shadow and an inner top highlight. Kept as tokens (not utilities)
222
+ * because every layer flips between light and dark. */
223
+ --color-component-empty-state-medallion-bg-from: var(--color-white);
224
+ --color-component-empty-state-medallion-bg-to: --alpha(var(--color-slate-100) / 80%);
225
+ --color-component-empty-state-medallion-border: --alpha(var(--color-slate-300) / 70%);
226
+ /* The hairline rides in the shadow list as an inset ring rather than a real
227
+ * border: Figma composites the top highlight *over* the stroke, and a CSS
228
+ * inset shadow is clipped to the padding box, so it can never reach a real
229
+ * border. Ordering matters — earlier shadows paint on top, so the highlight
230
+ * and the inner shade both land over the ring, as in Figma. It also keeps
231
+ * the glyph at 8px from the edge instead of 9px. */
232
+ --shadow-empty-state-medallion:
233
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.45), inset 0 -1px 1px 0 rgba(15, 23, 43, 0.04),
234
+ inset 0 0 0 1px var(--color-component-empty-state-medallion-border),
235
+ 0 2px 2.5px 0 rgba(15, 23, 43, 0.03), 0 1px 1px 0 rgba(15, 23, 43, 0.05);
236
+
219
237
  /* ========================================
220
238
  * SYNTAX COLORS
221
239
  * ⚠️ CODE-MANAGED — DO NOT SYNC FROM FIGMA
@@ -612,6 +630,16 @@
612
630
  --color-component-toast-ic-info: var(--color-blue-600);
613
631
  --color-component-app-shell-bg: var(--color-slate-975);
614
632
 
633
+ /* EmptyState medallion (dark) — same recipe inverted: the gradient and the
634
+ * top highlight become low-alpha white, the shadows deepen to near-black. */
635
+ --color-component-empty-state-medallion-bg-from: --alpha(var(--color-white) / 7%);
636
+ --color-component-empty-state-medallion-bg-to: --alpha(var(--color-white) / 2%);
637
+ --color-component-empty-state-medallion-border: --alpha(var(--color-white) / 13%);
638
+ --shadow-empty-state-medallion:
639
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.05), inset 0 -1px 2px 0 rgba(0, 0, 0, 0.16),
640
+ inset 0 0 0 1px var(--color-component-empty-state-medallion-border),
641
+ 0 2px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 2px 0 rgba(0, 0, 0, 0.24);
642
+
615
643
  /* ========================================
616
644
  * SYNTAX COLORS
617
645
  * ⚠️ CODE-MANAGED — DO NOT SYNC FROM FIGMA
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The raised tile behind an EmptyState icon.
3
+ *
4
+ * Composed here rather than in the component so the three layers Figma draws
5
+ * as stacked overlays (gradient fill, outer drop shadow, inner top highlight)
6
+ * collapse into one element: the gradient is a background-image, and the drop
7
+ * shadow, the inset highlight and the hairline ring all ride in a single
8
+ * box-shadow list. All of it resolves through tokens that flip in
9
+ * `[data-theme='dark']`.
10
+ */
11
+ @utility empty-state-medallion {
12
+ background-image: linear-gradient(
13
+ to bottom,
14
+ var(--color-component-empty-state-medallion-bg-from),
15
+ var(--color-component-empty-state-medallion-bg-to)
16
+ );
17
+ box-shadow: var(--shadow-empty-state-medallion);
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wallarm-org/design-system",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "Core design system library with React components and Storybook documentation",
5
5
  "publishConfig": {
6
6
  "access": "public",