mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
Adding license generation functionality
This commit is contained in:
@@ -2,6 +2,7 @@ export * from './lib/input/BilingualInput';
|
||||
export * from './lib/feedback/ConfirmModal';
|
||||
export * from './lib/feedback/ApiErrorAlert';
|
||||
export * from './lib/feedback/notify';
|
||||
export * from './lib/feedback/FeatureUnavailable';
|
||||
export * from './lib/layout/AppHeader';
|
||||
export * from './lib/layout/AppSidebar';
|
||||
export * from './lib/layout/BrandAvatar';
|
||||
|
||||
45
libs/ui/src/lib/feedback/FeatureUnavailable.tsx
Normal file
45
libs/ui/src/lib/feedback/FeatureUnavailable.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Card, Center, Stack, Text, ThemeIcon, Title } from '@mantine/core';
|
||||
import { IconTool } from '@tabler/icons-react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface FeatureUnavailableProps {
|
||||
/** What the screen will eventually do, e.g. "Seaman Book applications". */
|
||||
title: string;
|
||||
/** Optional extra context — what is missing, or what to use instead. */
|
||||
description?: string;
|
||||
action?: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder for a screen whose backend does not exist yet.
|
||||
*
|
||||
* These pages previously rendered hardcoded sample rows, which read as real
|
||||
* records: reviewers saw queues of applications that had never been filed and
|
||||
* dashboards counting things nobody had done. Showing nothing is the honest
|
||||
* option — an empty screen cannot be mistaken for data.
|
||||
*/
|
||||
export function FeatureUnavailable({
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
}: FeatureUnavailableProps) {
|
||||
return (
|
||||
<Center mih={320}>
|
||||
<Card withBorder radius="md" padding="xl" maw={520} w="100%">
|
||||
<Stack align="center" gap="sm">
|
||||
<ThemeIcon size={48} radius="xl" variant="light" color="gray">
|
||||
<IconTool size={24} stroke={1.5} />
|
||||
</ThemeIcon>
|
||||
<Title order={4} ta="center">
|
||||
{title}
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed" ta="center">
|
||||
{description ??
|
||||
'This feature is not connected to the backend yet, so there is nothing to show.'}
|
||||
</Text>
|
||||
{action}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
AppShell,
|
||||
Badge,
|
||||
NavLink,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
@@ -20,11 +21,30 @@ export interface NavItem {
|
||||
label: string;
|
||||
icon: Icon;
|
||||
to?: string;
|
||||
/** Not yet connected to real data — surfaced as a "Soon" badge. */
|
||||
soon?: boolean;
|
||||
}
|
||||
|
||||
/** A labelled group of nav items. */
|
||||
export interface NavSection {
|
||||
/** i18n key or literal heading. Omit for an ungrouped leading block. */
|
||||
label?: string;
|
||||
items: NavItem[];
|
||||
}
|
||||
|
||||
/** Both shapes are accepted so callers can migrate to sections gradually. */
|
||||
export type NavEntries = NavItem[] | NavSection[];
|
||||
|
||||
function toSections(entries: NavEntries): NavSection[] {
|
||||
if (entries.length === 0) return [];
|
||||
const isSectioned = (entries as NavSection[])[0]?.items !== undefined;
|
||||
return isSectioned
|
||||
? (entries as NavSection[])
|
||||
: [{ items: entries as NavItem[] }];
|
||||
}
|
||||
|
||||
interface AppSidebarProps {
|
||||
navItems: NavItem[];
|
||||
navItems: NavEntries;
|
||||
collapsed: boolean;
|
||||
activePath: string;
|
||||
onToggleCollapse: () => void;
|
||||
@@ -96,14 +116,43 @@ export function AppSidebar({
|
||||
|
||||
{/* Navigation items */}
|
||||
<AppShell.Section grow component={ScrollArea} p="md">
|
||||
<Stack gap={4}>
|
||||
{navItems.map((item) => {
|
||||
<Stack gap={2}>
|
||||
{toSections(navItems).map((section, sectionIndex) => (
|
||||
<Stack gap={2} key={section.label ?? `section-${sectionIndex}`}>
|
||||
{/* Headings are noise when only icons are visible. */}
|
||||
{section.label && !collapsed && (
|
||||
<Text
|
||||
size="xs"
|
||||
fw={700}
|
||||
c="dimmed"
|
||||
mt={sectionIndex === 0 ? 0 : rem(14)}
|
||||
pl={rem(12)}
|
||||
style={{ textTransform: 'uppercase', letterSpacing: '0.06em' }}
|
||||
>
|
||||
{t(section.label)}
|
||||
</Text>
|
||||
)}
|
||||
{section.label && collapsed && sectionIndex > 0 && (
|
||||
<div
|
||||
style={{
|
||||
height: 1,
|
||||
margin: `${rem(8)} ${rem(6)}`,
|
||||
backgroundColor: 'var(--mantine-color-gray-2)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{section.items.map((item) => {
|
||||
const ItemIcon = item.icon;
|
||||
const active = activeNavItem(item);
|
||||
|
||||
if (collapsed) {
|
||||
return (
|
||||
<Tooltip key={item.label} label={t(item.label)} position="right" withArrow>
|
||||
<Tooltip
|
||||
key={item.label}
|
||||
label={item.soon ? `${t(item.label)} — ${t('nav.soon', 'Soon')}` : t(item.label)}
|
||||
position="right"
|
||||
withArrow
|
||||
>
|
||||
<UnstyledButton
|
||||
onClick={() => onNavigate(item)}
|
||||
style={{
|
||||
@@ -113,6 +162,7 @@ export function AppSidebar({
|
||||
width: '100%',
|
||||
height: rem(40),
|
||||
borderRadius: rem(10),
|
||||
opacity: item.soon ? 0.55 : 1,
|
||||
color: active ? 'var(--mantine-color-blue-6)' : undefined,
|
||||
backgroundColor: active ? 'var(--mantine-color-blue-light)' : undefined,
|
||||
}}
|
||||
@@ -129,12 +179,25 @@ export function AppSidebar({
|
||||
active={active}
|
||||
label={t(item.label)}
|
||||
leftSection={<ItemIcon size={19} stroke={1.6} />}
|
||||
// Tells a reviewer at a glance which screens are wired up.
|
||||
rightSection={
|
||||
item.soon ? (
|
||||
<Badge size="xs" variant="light" color="gray" radius="sm">
|
||||
{t('nav.soon', 'Soon')}
|
||||
</Badge>
|
||||
) : undefined
|
||||
}
|
||||
onClick={() => onNavigate(item)}
|
||||
variant="light"
|
||||
styles={{ root: { borderRadius: rem(10) }, label: { fontWeight: 500 } }}
|
||||
styles={{
|
||||
root: { borderRadius: rem(10), opacity: item.soon ? 0.7 : 1 },
|
||||
label: { fontWeight: 500 },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
})}
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
</AppShell.Section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user