(
+ (
+ {
+ value,
+ label,
+ icon,
+ iconColor,
+ iconProps,
+ prefix,
+ suffix,
+ trend,
+ valueEffect,
+ labelEffect,
+ effect,
+ align = 'center',
+ size = 'normal',
+ customTheme,
+ intent,
+ fluid,
+ flat,
+ disabled,
+ tooltip,
+ rounded,
+ transparent,
+ opacity,
+ className,
+ ...rest
+ },
+ ref
+ ) => {
+ const theme = useReqoreTheme('main', customTheme, intent);
+
+ const secondarySize = useMemo(() => getOneLessSize(size), [size]);
+ const flexAlign = useMemo(() => alignToFlexAlign(align), [align]);
+
+ const interactive = useMemo(
+ () => !!(rest.onClick || rest.onDoubleClick || rest.onContextMenu),
+ [rest.onClick, rest.onDoubleClick, rest.onContextMenu]
+ );
+
+ const hasBackground = useMemo(
+ () => !!(effect || rounded || flat !== undefined || transparent || opacity !== undefined),
+ [effect, rounded, flat, transparent, opacity]
+ );
+
+ const trendIntent = useMemo(
+ () => (trend ? trend.intent || TREND_DEFAULT_INTENTS[trend.direction] : undefined),
+ [trend]
+ );
+
+ const trendIcon = useMemo(
+ () => (trend ? trend.icon || TREND_ICONS[trend.direction] : undefined),
+ [trend]
+ );
+
+ const transformedEffect: IReqoreEffect = useMemo(() => {
+ if (!effect) return undefined;
+
+ const newEffect: IReqoreEffect = { ...effect };
+
+ if (newEffect.gradient && intent) {
+ newEffect.gradient.borderColor = theme.intents[intent];
+ }
+
+ return newEffect;
+ }, [effect, intent, theme]);
+
+ return (
+
+
+ {icon && (
+
+ )}
+ {label && (
+
+ {label}
+
+ )}
+
+ {prefix && (
+
+ {prefix}
+
+ )}
+
+ {value}
+
+ {suffix && (
+
+ {suffix}
+
+ )}
+
+ {trend && (
+
+
+ {trend.value !== undefined && (
+
+ {trend.value}
+
+ )}
+
+ )}
+
+
+ );
+ }
+ )
+);
+
+export default ReqoreStatistic;
diff --git a/src/index.tsx b/src/index.tsx
index 851288e0..c9387310 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -57,6 +57,7 @@ export { ReqoreSlider } from './components/Slider';
export { ReqoreHorizontalSpacer, ReqoreSpacer, ReqoreVerticalSpacer } from './components/Spacer';
export { ReqoreSpan } from './components/Span';
export { ReqoreSpinner } from './components/Spinner';
+export { default as ReqoreStatistic } from './components/Statistic';
export { default as ReqoreTable } from './components/Table';
export { ReqoreTableBodyCell } from './components/Table/cell';
export { ReqoreTableHeaderCell } from './components/Table/headerCell';
diff --git a/src/stories/ControlGroup/ControlGroup.stories.tsx b/src/stories/ControlGroup/ControlGroup.stories.tsx
index 5b59f769..230c4020 100644
--- a/src/stories/ControlGroup/ControlGroup.stories.tsx
+++ b/src/stories/ControlGroup/ControlGroup.stories.tsx
@@ -357,6 +357,39 @@ export const Stacked: Story = {
},
};
+export const StackedButtonsOnly: Story = {
+ render: () => {
+ return (
+
+ First
+ Second
+ Third
+ Fourth
+ Fifth
+ Sixth
+ Seventh
+ Eighth
+ Ninth
+ Tenth
+ Eleventh
+ Twelfth
+ Thirteenth
+ Fourteenth
+ Fifteenth
+ Sixteenth
+ Seventeenth
+ Eighteenth
+ Nineteenth
+ Twentieth
+
+ );
+ },
+
+ args: {
+ stack: true,
+ },
+};
+
export const BigGapSize: Story = {
render: Template,
@@ -397,3 +430,61 @@ export const Responsive: Story = {
responsive: true,
},
};
+
+export const StackedBorders: Story = {
+ render: (args) => (
+
+ First
+ Second
+ Third
+ Fourth
+
+ ),
+};
+
+export const StackedBordersVertical: Story = {
+ render: (args) => (
+
+ First
+ Second
+ Third
+
+ ),
+};
+
+export const StackedBordersWithIntents: Story = {
+ render: (args) => (
+
+ Default
+
+ Info
+
+
+ Success
+
+
+ Warning
+
+
+ Danger
+
+
+ ),
+};
+
+export const StackedBordersWrapping: Story = {
+ render: (args) => (
+
+
+ One
+ Two
+ Three
+ Four
+ Five
+ Six
+ Seven
+ Eight
+
+
+ ),
+};
diff --git a/src/stories/Statistic/Statistic.stories.tsx b/src/stories/Statistic/Statistic.stories.tsx
new file mode 100644
index 00000000..ad0c4b16
--- /dev/null
+++ b/src/stories/Statistic/Statistic.stories.tsx
@@ -0,0 +1,440 @@
+import { StoryObj } from '@storybook/react';
+import { useState } from 'react';
+import ReqoreStatistic from '../../components/Statistic';
+import { ReqoreControlGroup } from '../../index';
+import { StoryMeta } from '../utils';
+
+const meta = {
+ title: 'Data Display/Statistic/Stories',
+ component: ReqoreStatistic,
+} as StoryMeta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Basic: Story = {
+ args: {
+ value: 12345,
+ },
+};
+
+export const WithLabel: Story = {
+ render: (args) => (
+
+
+
+
+
+ ),
+};
+
+export const WithIcon: Story = {
+ render: (args) => (
+
+
+
+
+
+ ),
+};
+
+export const WithPrefixAndSuffix: Story = {
+ render: (args) => (
+
+
+
+
+
+
+ ),
+};
+
+export const WithTrend: Story = {
+ render: (args) => (
+
+
+
+
+
+ ),
+};
+
+export const Sizes: Story = {
+ render: (args) => {
+ const sizes = ['tiny', 'small', 'normal', 'big', 'huge'] as const;
+
+ return (
+
+ {sizes.map((size) => (
+
+ ))}
+
+ );
+ },
+};
+
+export const Intents: Story = {
+ render: (args) => (
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const Alignment: Story = {
+ render: (args) => (
+
+
+
+
+
+
+
+
+
+
+
+ ),
+};
+
+export const WithValueEffects: Story = {
+ render: (args) => (
+
+
+
+
+ ),
+};
+
+export const WithBackground: Story = {
+ render: (args) => (
+
+
+
+
+
+
+ ),
+};
+
+export const WithBackgroundFlat: Story = {
+ render: (args) => (
+
+
+
+
+
+ ),
+};
+
+export const WithGradientBackground: Story = {
+ render: (args) => (
+
+
+
+
+
+ ),
+};
+
+export const Disabled: Story = {
+ render: (args) => (
+
+
+
+
+ ),
+};
+
+export const Interactive: Story = {
+ render: (args) => {
+ const [selected, setSelected] = useState(null);
+
+ return (
+
+ setSelected('users')}
+ />
+ setSelected('revenue')}
+ />
+ setSelected('sessions')}
+ />
+ setSelected('disabled')}
+ />
+
+ );
+ },
+};
+
+export const DashboardExample: Story = {
+ render: (args) => (
+
+
+
+
+
+
+ ),
+};
+
+export const StackedStatistics: Story = {
+ render: (args) => (
+
+
+
+
+
+
+
+ ),
+};