Skip to content
Snippets Groups Projects
icons.tsx 7.59 KiB
/* ************************************************************************ */
/*                                                                          */
/*   This file is part of Frama-C.                                          */
/*                                                                          */
/*   Copyright (C) 2007-2025                                                */
/*     CEA (Commissariat à l'énergie atomique et aux énergies               */
/*          alternatives)                                                   */
/*                                                                          */
/*   you can redistribute it and/or modify it under the terms of the GNU    */
/*   Lesser General Public License as published by the Free Software        */
/*   Foundation, version 2.1.                                               */
/*                                                                          */
/*   It is distributed in the hope that it will be useful,                  */
/*   but WITHOUT ANY WARRANTY; without even the implied warranty of         */
/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
/*   GNU Lesser General Public License for more details.                    */
/*                                                                          */
/*   See the GNU Lesser General Public License version 2.1                  */
/*   for more details (enclosed in the file licenses/LGPLv2.1).             */
/*                                                                          */
/* ************************************************************************ */

// --------------------------------------------------------------------------
// --- SVG Icons
// --------------------------------------------------------------------------

/**
   You can [register](#.register) new icons or override existing ones
   and [iterate](#.forEach) over the icon base.

   [[include:icons.md]]

   @packageDocumentation
   @module dome/controls/icons
 */

import _ from 'lodash';
import React from 'react';
import { classes, styles } from 'dome/misc/utils';
import Gallery from './gallery.json';
import './style.css';

/* @ internal */
const Icons: { [id: string]: { viewBox?: string; path: string } } = Gallery;

// --------------------------------------------------------------------------
// --- Raw SVG element
// --------------------------------------------------------------------------

export interface SVGprops {
  /** Icon's identifier. */
  id: string;
  /** Icon's tool-tip. */
  title?: string;
  /** Icon's dimension in pixels (default: `12`). */
  size?: number;
  /**
     Vertical alignement offset, in pixels.
     Default is set to `-0.125` times the size).
   */
  offset?: number;
  className?: string;
  fill?: string;
}

/**
   Low-level SVG icon.
   Only returns the identified `<svg/>` element from Icon base.
 */
export function SVG(props: SVGprops): null | JSX.Element {
  const { id, className, fill } = props;
  if (!id) return null;
  const icon = Icons[id];
  if (!icon) return <>{id}</>;
  const {
    title,
    size = 12,
    offset = _.floor(-size * 0.125),
  } = props;
  const { path, viewBox = '0 0 24 24' } = icon;
  return (
    <svg
      height={size}
      style={{ bottom: offset }}
      viewBox={viewBox}
      className={className}
    >
      {title && <title>{title}</title>}
      <path d={path} fill={fill} />
    </svg>
  );
}

// --------------------------------------------------------------------------
// --- Icon Component
// --------------------------------------------------------------------------

const iconKindList = [
  'disabled', 'selected', 'positive', 'negative', 'warning', 'default'
] as const;

export type IconKind = typeof iconKindList[number]

export function jIconKind(js : string) : IconKind | undefined {
  return iconKindList.find(elt => elt === js);
}

/** Icon Component Properties */
export interface IconProps extends SVGprops {
  /** Additional class name(s). */
  className?: string;
  /** Display (default is true). */
  display?: boolean;
  /** Visibility (default is true). */
  visible?: boolean;
  /** Additional DIV style. */
  style?: React.CSSProperties;
  /** Fill style property. */
  fill?: string;
  /** Icon Kind. */
  kind?: IconKind;
  /** Icon spinning */
  spinning?: boolean;
  /** Click callback. */
  onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
  /** Right-Click callback. */
  onContextMenu?: (event: React.MouseEvent<HTMLDivElement>) => void;
}

/**
   Icon Component.
   Consult the [Icon Gallery](../../doc/guides/icons.md) for default icons.
 */
export function Icon(props: IconProps): JSX.Element {
  const {
    id, title, fill, kind='default',
    size, className, offset, style,
    visible = true, display = true,
    spinning, onClick, onContextMenu,
  } = props;
  const forceSpinning = Boolean(id === "SPINNER" && spinning === undefined);
  const divClass = classes(
    'dome-xIcon', `dome-xIcon-${kind}`,
    !visible && 'dome-control-hidden',
    !display && 'dome-control-erased',
    (forceSpinning || spinning) && 'dome-xIcon-spinning',
    className
  );
  const divStyle = styles(fill && { fill }, style);
  return (
    <div
      className={divClass}
      style={divStyle}
      onClick={onClick}
      onContextMenu={onContextMenu}
    >
      <SVG id={id} size={size} title={title} offset={offset} />
    </div>
  );
}

// --------------------------------------------------------------------------
// ---  Badge Component
// --------------------------------------------------------------------------

/** Badge Properties */
export interface BadgeProps {
  /**
     Displayed value.
     Can be a number, a single letter, or an icon identifier.
   */
  value: number | string;
  /** Badge tool-tip. */
  title?: string;
  /** Click callback. */
  onClick?: (event?: React.MouseEvent) => void;
}

/**
   Rounded icon, number or letter.
   Depending on the type of value, display either a number,
   a label, or the corresponding named icon.
   Consult the [Icon Gallery](../../doc/guides/icons.md) for default icons.
 */
export function Badge(props: BadgeProps): JSX.Element {
  const { value, title, onClick } = props;
  let content;
  if (typeof value === 'string' && Icons[value]) {
    content = <Icon id={value} size={10} />;
  } else {
    const style =
      (typeof (value) === 'number' && value < 10) ||
        (typeof (value) === 'string' && value.length === 1) ?
        { paddingLeft: 2, paddingRight: 2 } : {};
    content = <label style={style} className="dome-text-label">{value}</label>;
  }
  return (
    <div
      className="dome-xBadge"
      title={title}
      onClick={onClick}
    >
      {content}
    </div>
  );
}

// --------------------------------------------------------------------------
// --- Icon Database
// --------------------------------------------------------------------------

/** Custom Icon Properties */
export interface CustomIcon {
  /** Icon identifier. */
  id: string;
  /** Icon's viewBox (default is `"0 0 24 24"`). */
  viewBox?: string;
  /** Icon's SVG path. */
  path: string;
}

/**
   Register a new custom icon.
 */
export function register(icon: CustomIcon): void {
  const { id, ...jsicon } = icon;
  Icons[id] = jsicon;
}

export interface IconData extends CustomIcon {
  section?: string;
  title?: string;
}

/**
   Iterate over icons gallery.
   See [[register]] to add custom icons to the gallery.
 */
export function forEach(fn: (ico: IconData) => void): void {
  const ids = Object.keys(Icons);
  ids.forEach((id) => {
    const jsicon = Icons[id];
    fn({ id, ...jsicon });
  });
}

// --------------------------------------------------------------------------