ui(segmentation): Design Prototype for split panel component (#4378)

This commit is contained in:
Dan Rukas 2024-09-24 15:44:59 -04:00 committed by GitHub
parent cb1f3b877b
commit 7f17d5d733
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 989 additions and 2 deletions

View File

@ -38,6 +38,7 @@ module.exports = {
entry: {
home: './src/_pages/index.tsx',
playground: './src/_pages/playground.tsx',
patterns: './src/_pages/patterns.tsx',
viewer: './src/_pages/viewer.tsx',
colors: './src/_pages/colors.tsx',
// add other pages here
@ -82,6 +83,11 @@ module.exports = {
chunks: ['playground'],
filename: 'playground.html',
}),
new HtmlWebpackPlugin({
template: './.webpack/template.html',
chunks: ['patterns'],
filename: 'patterns.html',
}),
new HtmlWebpackPlugin({
template: './.webpack/template.html',
chunks: ['viewer'],

View File

@ -13,6 +13,12 @@ const App: React.FC = () => (
>
Playground
</a>
<a
href="patterns.html"
className="text-blue-500 hover:text-blue-700"
>
Patterns
</a>
<a
href="viewer.html"
className="text-blue-500 hover:text-blue-700"

View File

@ -0,0 +1,43 @@
// src/_pages/patterns.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import '../tailwind.css';
// component imports
import { Button } from '../components/Button';
import { Input } from '../components/Input';
import { Label } from '../components/Label';
import {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} from '../components/Select';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/Tabs';
import Separator from '../components/Separator';
import { Switch } from '../components/Switch';
import { Checkbox } from '../components/Checkbox';
import { Toggle, toggleVariants } from '../components/Toggle';
import { Slider } from '../components/Slider';
import { ScrollArea, ScrollBar } from '../components/ScrollArea';
import { PanelSplit } from '../_prototypes/PanelSplit'; // Updated import path
function Patterns() {
return (
<div className="my-4 mx-auto max-w-6xl py-6">
<div className="text-3xl"> Patterns 1 </div>
<PanelSplit />;
</div>
);
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(React.createElement(Patterns));

View File

@ -18,7 +18,7 @@ import {
SelectScrollDownButton,
} from '../components/Select';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/Tabs';
import { Separator } from '../components/Separator';
import Separator from '../components/Separator';
import { Switch } from '../components/Switch';
import { Checkbox } from '../components/Checkbox';
import { Toggle, toggleVariants } from '../components/Toggle';
@ -26,6 +26,7 @@ import { Slider } from '../components/Slider';
import { ScrollArea, ScrollBar } from '../components/ScrollArea';
import { BackgroundColorSelect } from '../components/BackgroundColorSelect';
// import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../components/Tooltip';
// import type { Metadata } from 'next';

View File

@ -0,0 +1,213 @@
import React from 'react';
import { Item, VisibilityState, AvailabilityState } from './types';
import { Button } from '../../components/Button'; // Updated import path
interface ItemListProps {
items: Item[];
onSelectItem: (item: Item) => void;
selectedItem: Item | null;
onToggleVisibility: (itemId: number) => void; // Prop for visibility toggle
onAddItem: (itemId: number) => void; // Prop for add button
}
/**
* ItemList Component
*
* Displays a list of items that can be selected, toggled for visibility,
* and added (changing availability).
*
* @param items - Array of items to display.
* @param onSelectItem - Callback when an item is selected.
* @param selectedItem - The currently selected item.
* @param onToggleVisibility - Callback when an item's visibility is toggled.
* @param onAddItem - Callback when an item's availability is changed to 'loaded'.
*/
const ItemList: React.FC<ItemListProps> = ({
items,
onSelectItem,
selectedItem,
onToggleVisibility,
onAddItem,
}) => {
return (
<ul
aria-label="Item List"
className="space-y-1"
>
{items.map(item => (
<li key={item.id}>
<div className="flex items-center">
{/* Conditionally render the Add button for 'available' items */}
{item.availability === 'available' && (
<Button
variant="ghost"
size="icon"
onClick={e => {
e.stopPropagation(); // Prevent parent onClick
onAddItem(item.id);
}}
aria-label={`Add ${item.name}`}
className="ml-1 mr-1"
>
{/* Plus Icon */}
<svg
width="16px"
height="16px"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 5V19"
stroke="#348CFD"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 12H19"
stroke="#348CFD"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Button>
)}
{/* All items are now rendered as clickable rows */}
<button
onClick={() => onSelectItem(item)}
className={`text-foreground flex h-7 w-full flex-grow cursor-pointer items-center justify-between rounded p-1 text-sm ${
item.id === selectedItem?.id ? 'bg-primary/20' : 'bg-muted hover:bg-primary/30'
} focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-1`}
aria-pressed={item.id === selectedItem?.id}
>
<span className="ml-1">{item.name}</span>
{/* Conditionally render the "Available" label for 'available' items */}
{item.availability === 'available' && (
<span className="text-muted-foreground text-xs">Available</span>
)}
{/* Conditionally render the visibility icon for 'loaded' items */}
{item.availability === 'loaded' && (
<Button
variant="ghost"
size="icon"
onClick={e => {
e.stopPropagation(); // Prevent parent onClick
onToggleVisibility(item.id);
}}
aria-label={
item.visibility === 'Visible' ? `Hide ${item.name}` : `Show ${item.name}`
}
>
{item.visibility === 'Visible' ? (
// SVG Icon for "Visible"
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
>
<g
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
x="0"
y="0"
width="24"
height="24"
></rect>
<circle
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
cx="12.4986195"
cy="11.8041442"
r="2.58684689"
></circle>
<path
d="M20.906611,11.5617197 C20.0470387,10.5861089 16.6094888,7 12.4986195,7
C8.38775024,7 4.95020027,10.5861089 4.090628,11.5617197
C3.96979067,11.7007491 3.96979067,11.9075393 4.090628,12.0465687
C4.95020027,13.0221796 8.38775024,16.6082885 12.4986195,16.6082885
C16.6094888,16.6082885 20.0470387,13.0221796 20.906611,12.0465687
C21.0274483,11.9075393 21.0274483,11.7007491 20.906611,11.5617197 Z"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
</g>
</svg>
) : (
// SVG Icon for "Hidden"
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
>
<g
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<path
d="M18.0567826,8.96286957
C19.1471229,9.75269568 20.1356859,10.674229 21,11.7065217
C21,11.7065217 17.1949565,16.5108696 12.5,16.5108696
C11.7479876,16.5066962 11.0007435,16.3911225 10.2826087,16.167913"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M6.93286957,14.4413043
C5.84666081,13.6535964 4.86162018,12.7350857 4,11.7065217
C4,11.7065217 7.80504348,6.90217391 12.5,6.90217391
C13.1235541,6.90480509 13.7443251,6.98550531 14.3478261,7.1423913"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M9.54347826,11.7065217
C9.54347826,10.0736799 10.8671581,8.75 12.5,8.75"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M15.4565217,11.7065217
C15.4565217,13.3393636 14.1328419,14.6630435 12.5,14.6630435"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<line
x1="19.7065217"
y1="4.5"
x2="5.29347826"
y2="18.9130435"
stroke="#348CFD"
strokeLinecap="round"
strokeLinejoin="round"
></line>
</g>
</svg>
)}
</Button>
)}
</button>
</div>
</li>
))}
</ul>
);
};
export default ItemList;

View File

@ -0,0 +1,331 @@
import React, { useState, useEffect } from 'react';
import ItemList from './ItemList';
import PropertiesPanel from './PropertiesPanel';
import { Item, DisplayMode, VisibilityState, AvailabilityState } from './types';
import { ScrollArea } from '../../components/ScrollArea';
const PanelSplit: React.FC = () => {
const [selectedItem, setSelectedItem] = useState<Item | null>(null);
const [items, setItems] = useState<Item[]>([
{
id: 1,
name: 'All Items',
controlsAll: true, // Master item
displayMode: 'Fill & Outline', // Default display mode
visibility: 'Visible', // Default visibility
availability: 'loaded', // Set to 'loaded'
properties: [
{
key: 'opacity',
label: 'Opacity',
type: 'slider',
value: 50,
min: 1,
max: 100,
step: 1,
},
{
key: 'outline',
label: 'Outline',
type: 'slider',
value: 5,
min: 1,
max: 10,
step: 1,
},
{
key: 'displayInactiveSegments',
label: 'Display inactive segments',
type: 'boolean',
value: false,
},
],
},
{
id: 2,
name: 'List item 1',
series: 'Series A',
displayMode: 'Fill & Outline', // Default display mode
visibility: 'Visible', // Default visibility
availability: 'loaded', // Set to 'loaded'
properties: [
{
key: 'opacity',
label: 'Opacity',
type: 'slider',
value: 70,
min: 1,
max: 100,
step: 1,
},
{
key: 'outline',
label: 'Outline',
type: 'slider',
value: 7,
min: 1,
max: 10,
step: 1,
},
],
},
{
id: 3,
name: 'List item 2',
series: 'Series B',
displayMode: 'Fill & Outline', // Default display mode
visibility: 'Visible', // Default visibility
availability: 'loaded', // Set to 'loaded'
properties: [
{
key: 'opacity',
label: 'Opacity',
type: 'slider',
value: 70,
min: 1,
max: 100,
step: 1,
},
{
key: 'outline',
label: 'Outline',
type: 'slider',
value: 7,
min: 1,
max: 10,
step: 1,
},
],
},
{
id: 4,
name: 'List item 3',
series: 'Series C',
displayMode: 'Fill & Outline', // Default display mode
visibility: 'Visible', // Visibility will not be shown initially
availability: 'available', // Set to 'available'
properties: [
{
key: 'opacity',
label: 'Opacity',
type: 'slider',
value: 80,
min: 1,
max: 100,
step: 1,
},
{
key: 'outline',
label: 'Outline',
type: 'slider',
value: 8,
min: 1,
max: 10,
step: 1,
},
],
},
// Add more items as needed
]);
// Set the master item as selected by default on mount
useEffect(() => {
if (items.length > 0 && !selectedItem) {
setSelectedItem(items.find(item => item.controlsAll) || items[0]);
}
}, [items, selectedItem]);
/**
* Handles updating a property's value.
*
* @param itemId - The ID of the item being updated.
* @param propertyKey - The key identifying the property.
* @param newValue - The new value for the property.
*/
const handleUpdateProperty = (itemId: number, propertyKey: string, newValue: any) => {
const masterItem = items.find(item => item.controlsAll);
if (masterItem && itemId === masterItem.id) {
if (propertyKey === 'displayMode') {
// Update displayMode for all items
setItems(prevItems =>
prevItems.map(item => ({
...item,
displayMode: newValue, // Set to the new displayMode
properties: item.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}))
);
// Also update the selectedItem if it's the master
setSelectedItem(prevSelected =>
prevSelected
? {
...prevSelected,
displayMode: newValue,
properties: prevSelected.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}
: prevSelected
);
} else if (propertyKey === 'visibility') {
// Update visibility for all items
setItems(prevItems =>
prevItems.map(item => ({
...item,
visibility: newValue, // Set to the new visibility
}))
);
// Also update the selectedItem if it's the master
setSelectedItem(prevSelected =>
prevSelected
? {
...prevSelected,
visibility: newValue,
}
: prevSelected
);
} else {
// Update other properties for all items
setItems(prevItems =>
prevItems.map(item => ({
...item,
properties: item.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}))
);
// Also update the selectedItem if it's the master
setSelectedItem(prevSelected =>
prevSelected
? {
...prevSelected,
properties: prevSelected.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}
: prevSelected
);
}
} else {
if (propertyKey === 'displayMode') {
// Update displayMode only for the selected item
setItems(prevItems =>
prevItems.map(item => (item.id === itemId ? { ...item, displayMode: newValue } : item))
);
// Update selectedItem
setSelectedItem(prevSelected =>
prevSelected ? { ...prevSelected, displayMode: newValue } : prevSelected
);
} else if (propertyKey === 'visibility') {
// Toggle visibility only for the selected item
setItems(prevItems =>
prevItems.map(item => (item.id === itemId ? { ...item, visibility: newValue } : item))
);
// Update selectedItem
setSelectedItem(prevSelected =>
prevSelected ? { ...prevSelected, visibility: newValue } : prevSelected
);
} else if (propertyKey === 'availability') {
// Update availability only for the selected item
setItems(prevItems =>
prevItems.map(item => (item.id === itemId ? { ...item, availability: newValue } : item))
);
// Update selectedItem
setSelectedItem(prevSelected =>
prevSelected ? { ...prevSelected, availability: newValue } : prevSelected
);
} else {
// Update only the selected item's properties
setItems(prevItems =>
prevItems.map(item =>
item.id === itemId
? {
...item,
properties: item.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}
: item
)
);
// Update selectedItem
setSelectedItem(prevSelected =>
prevSelected
? {
...prevSelected,
properties: prevSelected.properties.map(prop =>
prop.key === propertyKey ? { ...prop, value: newValue } : prop
),
}
: prevSelected
);
}
}
};
/**
* Handles selecting an item from the list.
*
* @param item - The item being selected.
*/
const handleSelectItem = (item: Item) => {
setSelectedItem(item);
};
/**
* Handles toggling the visibility of an item.
*
* @param itemId - The ID of the item being toggled.
*/
const handleToggleVisibility = (itemId: number) => {
const item = items.find(item => item.id === itemId);
if (item) {
const newVisibility: VisibilityState = item.visibility === 'Visible' ? 'Hidden' : 'Visible';
handleUpdateProperty(itemId, 'visibility', newVisibility);
}
};
/**
* Handles adding an item (changing availability from 'available' to 'loaded').
*
* @param itemId - The ID of the item being added.
*/
const handleAddItem = (itemId: number) => {
handleUpdateProperty(itemId, 'availability', 'loaded');
};
return (
<div className="flex h-full w-[262px] flex-col">
{/* Top: List of Selectable Items */}
<ScrollArea className="bg-muted h-[132px] rounded-t border-gray-300 p-1">
<ItemList
items={items}
onSelectItem={handleSelectItem}
selectedItem={selectedItem}
onToggleVisibility={handleToggleVisibility} // Pass the toggle handler
onAddItem={handleAddItem} // Pass the add handler
/>
</ScrollArea>
{/* Bottom: Properties of Selected Item */}
<ScrollArea className="bg-popover max-h-[400px] flex-grow overflow-auto rounded p-1">
<PropertiesPanel
selectedItem={selectedItem}
onUpdateProperty={handleUpdateProperty}
onAddItem={handleAddItem} // Pass the add handler
/>
</ScrollArea>
</div>
);
};
export default PanelSplit;

View File

@ -0,0 +1,337 @@
import React from 'react';
import { Item, Property, DisplayMode, AvailabilityState } from './types';
import { Label } from '../../components/Label';
import { Slider } from '../../components/Slider';
import { Input } from '../../components/Input';
import { Switch } from '../../components/Switch';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../components/Tabs';
import { Button } from '../../components/Button';
interface PropertiesPanelProps {
selectedItem: Item | null;
onUpdateProperty: (itemId: number, propertyKey: string, newValue: any) => void;
onAddItem: (itemId: number) => void; // Prop for adding item
}
/**
* PropertiesPanel Component
*
* Displays and manages the properties of the selected item.
* Renders different content based on the item's availability state.
*
* @param selectedItem - The currently selected item.
* @param onUpdateProperty - Callback to handle property updates.
* @param onAddItem - Callback to handle adding the item.
*/
const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
selectedItem,
onUpdateProperty,
onAddItem,
}) => {
if (!selectedItem) {
return (
<div className="text-gray-500">
<p>No item selected.</p>
</div>
);
}
if (selectedItem.availability === 'available') {
return (
<div className="flex flex-col items-center p-1.5 text-sm">
{/* "Add this item" Button */}
<Button
onClick={() => onAddItem(selectedItem.id)}
className="mb-4 mt-2"
variant="default"
>
Add this item
</Button>
{/* Divider */}
<div className="border-primary/30 mb-2 w-full border-b"></div>
{/* Series Name */}
<div className="text-foreground w-full text-left">
Series: <span className="text-muted-foreground">{selectedItem.series}</span>
</div>
</div>
);
}
if (selectedItem.availability === 'loaded') {
/**
* Handles changes to a property's value.
*
* @param property - The property being updated.
* @param newValue - The new value for the property.
*/
const handleChange = (property: Property, newValue: any) => {
console.log(`Updating property '${property.key}' to`, newValue); // Debug log
onUpdateProperty(selectedItem.id, property.key, newValue);
};
// Determine if the selected item is the master
const isMaster = selectedItem.controlsAll;
/**
* Handles changes to the display mode via Tabs.
*
* @param newDisplayMode - The new display mode selected.
*/
const handleDisplayModeChange = (newDisplayMode: DisplayMode) => {
console.log(`Display mode changed to`, newDisplayMode); // Debug log
onUpdateProperty(selectedItem.id, 'displayMode', newDisplayMode);
};
return (
<div className="p-1.5 text-sm">
<div className="items-top mb-2.5 flex justify-between">
<div className="text-foreground text-sm font-semibold">
Properties <br />
<span className="text-muted-foreground font-normal">{selectedItem.name}</span>
</div>
{/* Tabs component for Outline and Fill control */}
<Tabs
value={selectedItem.displayMode}
onValueChange={handleDisplayModeChange}
className="ml-auto"
>
<TabsList>
<TabsTrigger value="Fill & Outline">
{/* SVG Icon for Fill & Outline */}
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
>
<g
id="view-outline-fill"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Group-13">
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<rect
id="Rectangle"
stroke="#348CFD"
x="1.5"
y="1.5"
width="15"
height="15"
rx="1"
></rect>
<rect
id="Rectangle"
fill="#348CFD"
x="3.5"
y="3.5"
width="11"
height="11"
rx="1"
></rect>
</g>
</g>
</svg>
</TabsTrigger>
<TabsTrigger value="Outline Only">
{/* SVG Icon for Outline Only */}
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
>
<g
id="view-outline"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Group-13">
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<rect
id="Rectangle"
stroke="#348CFD"
x="1.5"
y="1.5"
width="15"
height="15"
rx="1"
></rect>
</g>
</g>
</svg>
</TabsTrigger>
<TabsTrigger value="Fill Only">
{/* SVG Icon for Fill Only */}
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
>
<g
id="view-fill"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Group-13">
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<rect
id="Rectangle"
fill="#348CFD"
x="2"
y="2"
width="14"
height="14"
rx="1"
></rect>
</g>
</g>
</svg>
</TabsTrigger>
</TabsList>
{/* Display dynamic text under the tabs */}
<div className="mt-0">
<TabsContent value="Fill & Outline">
<p className="text-muted-foreground text-xxs text-center">Fill & Outline</p>
</TabsContent>
<TabsContent value="Outline Only">
<p className="text-muted-foreground text-xxs text-center">Outline Only</p>
</TabsContent>
<TabsContent value="Fill Only">
<p className="text-muted-foreground text-xxs text-center">Fill Only</p>
</TabsContent>
</div>
</Tabs>
</div>
{/* Properties List */}
<div className="mb-3 space-y-3">
{selectedItem.properties.map(prop => (
<div
key={prop.key}
className="flex items-center justify-between space-x-4"
>
{/* Label takes up space and doesn't wrap */}
<Label
htmlFor={prop.key}
className="flex-grow whitespace-nowrap"
>
{prop.label}
</Label>
{/* Flex container for input elements, with spacing */}
<div className="flex items-center space-x-3">
{renderPropertyInput(prop, handleChange)}
</div>
</div>
))}
</div>
{/* Conditionally render the details section for non-master items */}
{!isMaster && (
<div className="text-foreground mb-1">
<div className="border-primary/30 mb-2 w-full border-b"></div>
Series: <span className="text-muted-foreground">{selectedItem.series}</span>
</div>
)}
</div>
);
}
// For other availability states, you can add additional conditions if needed
return (
<div className="text-gray-500">
<p>No properties available for the selected item.</p>
</div>
);
};
/**
* Renders the appropriate input component based on the property's type.
*
* @param prop - The property to render.
* @param handleChange - Function to handle value changes.
* @returns JSX Element corresponding to the property type.
*/
const renderPropertyInput = (
prop: Property,
handleChange: (prop: Property, value: any) => void
) => {
switch (prop.type) {
case 'slider':
return (
<>
<Slider
id={prop.key}
value={[prop.value as number]} // Pass as an array for Radix UI Slider
min={prop.min}
max={prop.max}
step={prop.step}
onValueChange={values => {
console.log(`Slider '${prop.key}' changed to`, values[0]); // Debug log
handleChange(prop, values[0]);
}}
className="w-28"
/>
<Input
type="number"
id={prop.key}
value={prop.value as number}
min={prop.min}
max={prop.max}
step={prop.step}
onChange={e => {
const newVal = Number(e.target.value);
console.log(`Input '${prop.key}' changed to`, newVal); // Debug log
handleChange(prop, newVal);
}}
className="w-14"
/>
</>
);
case 'boolean':
return (
<Switch
id={prop.key}
checked={prop.value as boolean}
onCheckedChange={checked => {
console.log(`Switch '${prop.key}' toggled to`, checked); // Debug log
handleChange(prop, checked);
}}
/>
);
// Add more cases if you have other property types
default:
return null;
}
};
export default PropertiesPanel;

View File

@ -0,0 +1,7 @@
import PanelSplit from './PanelSplit';
import ItemList from './ItemList';
import PropertiesPanel from './PropertiesPanel';
export { default as PanelSplit } from './PanelSplit';
export { default as PropertiesPanel } from './PropertiesPanel';
export { default as ItemList } from './ItemList';

View File

@ -0,0 +1,24 @@
export interface Property {
key: string;
label: string;
type: 'slider' | 'boolean' | string; // Extend types as needed
value: any;
min?: number;
max?: number;
step?: number;
}
export type DisplayMode = 'Fill & Outline' | 'Outline Only' | 'Fill Only';
export type VisibilityState = 'Visible' | 'Hidden';
export type AvailabilityState = 'loaded' | 'available' | 'not available';
export interface Item {
id: number;
name: string;
controlsAll?: boolean; // Indicates if the item is the master
series?: string; // Optional, only for non-master items
displayMode: DisplayMode; // Existing property for display mode
visibility: VisibilityState; // Existing property for visibility
availability: AvailabilityState; // New property for availability
properties: Property[];
}

View File

@ -29,6 +29,11 @@ import PanelSection from './PanelSection';
import DisplaySetMessageListTooltip from './DisplaySetMessageListTooltip';
import { Toolbox, ToolboxUI } from './Toolbox';
// Prototypes
import { PanelSplit } from '../_prototypes/PanelSplit';
import { ItemList } from '../_prototypes/PanelSplit/ItemList';
import { PropertiesPanel } from '../_prototypes/PanelSplit/PropertiesPanel';
export {
Button,
buttonVariants,
@ -71,4 +76,7 @@ export {
DisplaySetMessageListTooltip,
Toolbox,
ToolboxUI,
PanelSplit,
ItemList,
PropertiesPanel,
};

View File

@ -197,7 +197,7 @@
}
body {
@apply bg-black;
@apply !bg-black;
}
}
@ -228,3 +228,13 @@ h3.section-header {
.TooltipContent[data-side='bottom'] {
animation-name: slideDown;
}
/* Custom CSS to hide default number input arrows */
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
@apply appearance-none;
}
input[type='number'] {
-moz-appearance: textfield; /* For Firefox */
}

View File

@ -13,6 +13,7 @@ module.exports = {
inter: ['Inter', 'sans-serif'],
},
fontSize: {
xxs: '0.6875rem', // 11px
xs: '0.75rem', // 12px
sm: '0.8125rem', // 13px
base: '0.875rem', // 14px