001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.console.widgets;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import org.apache.juneau.commons.bean.*;
022
023/**
024 * An overlay badge on a header action (a bell count, for example) or a {@link BarBadge}.
025 *
026 * <p>
027 * A badge is exactly one of a <b>count</b> badge ({@link #count}) or a <b>dot</b> badge ({@link #dot}); it never
028 * carries both.  The optional {@link #max} clamps a large count to <js>"&lt;max&gt;+"</js> and {@link #tone} selects
029 * the badge colour from the shared {@link StatusTone} palette.  A count is painted with {@code textContent}
030 * &mdash; never {@code innerHTML}.
031 *
032 * <p>
033 * The tone is serialized as its enum name (e.g. <js>"WARNING"</js>); the client lowercases it into the
034 * {@code data-juneau-badge-tone} attribute.  Colours map as:
035 * <ul>
036 *    <li>{@link StatusTone#INFO} &mdash; blue
037 *    <li>{@link StatusTone#SUCCESS} &mdash; green
038 *    <li>{@link StatusTone#WARNING} &mdash; amber
039 *    <li>{@link StatusTone#ERROR} &mdash; red
040 *    <li>{@link StatusTone#NEUTRAL} &mdash; neutral
041 * </ul>
042 *
043 * <h5 class='section'>Example:</h5>
044 * <p class='bjava'>
045 *    <jc>// A red unread-count badge that caps at "99+".</jc>
046 *    Badge <jv>badge</jv> = Badge.<jsm>count</jsm>(120).tone(StatusTone.<jsf>ERROR</jsf>).max(99).label(<js>"unread notifications"</js>);
047 *    <jv>badge</jv>.validate();
048 * </p>
049 *
050 * @since 10.0.0
051 */
052@BeanType(properties="count,dot,max,tone,label")
053@SuppressWarnings({
054   "java:S1845" // Fluent-builder setters intentionally mirror field names (Juneau DSL convention).
055})
056public class Badge {
057
058   /** A count badge value (mutually exclusive with {@link #dot}); when set must be {@code >= 0}. */
059   public Integer count;
060
061   /** A dot (presence) badge with no number (mutually exclusive with {@link #count}). */
062   public Boolean dot;
063
064   /** Optional display clamp; a count above this renders as <js>"&lt;max&gt;+"</js>.  When set must be {@code >= 1}. */
065   public Integer max;
066
067   /** Optional colour tone; <jk>null</jk> leaves the badge in its default colour. */
068   public StatusTone tone;
069
070   /** Optional visually-hidden context for screen readers, e.g. <js>"unread notifications"</js>. */
071   public String label;
072
073   /**
074    * Creates a count badge.
075    *
076    * @param value The count.  Must be {@code >= 0}.
077    * @return A new {@link Badge}.
078    */
079   public static Badge count(int value) {
080      var b = new Badge();
081      b.count = value;
082      return b;
083   }
084
085   /**
086    * Creates a dot (presence) badge with no number.
087    *
088    * @return A new {@link Badge}.
089    */
090   public static Badge dot() {
091      var b = new Badge();
092      b.dot = true;
093      return b;
094   }
095
096   /**
097    * Sets the color tone.
098    *
099    * @param value The tone.  Can be <jk>null</jk> to use the default colour.
100    * @return This object.
101    */
102   public Badge tone(StatusTone value) {
103      tone = value;
104      return this;
105   }
106
107   /**
108    * Sets the display clamp.
109    *
110    * @param value The clamp.  Must be {@code >= 1}.
111    * @return This object.
112    */
113   public Badge max(int value) {
114      max = value;
115      return this;
116   }
117
118   /**
119    * Sets the visually-hidden screen-reader context.
120    *
121    * @param value The label.
122    * @return This object.
123    */
124   public Badge label(String value) {
125      label = value;
126      return this;
127   }
128
129   /**
130    * Fail-closed bean validation; called by the enclosing bean's {@code validate()}.
131    *
132    * @throws IllegalArgumentException If this badge is not well-formed.
133    */
134   public void validate() {
135      var hasCount = count != null;
136      var hasDot = dot != null && dot;
137      if (hasCount == hasDot)
138         throw iaex("Badge must declare exactly one of count/dot.");
139      if (hasCount && count < 0)
140         throw iaex("Badge count must be >= 0.");
141      if (max != null && max < 1)
142         throw iaex("Badge max must be >= 1 when set.");
143   }
144}