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>"<max>+"</js> and {@link #tone} selects 029 * the badge colour from the shared {@link StatusTone} palette. A count is painted with {@code textContent} 030 * — 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} — blue 037 * <li>{@link StatusTone#SUCCESS} — green 038 * <li>{@link StatusTone#WARNING} — amber 039 * <li>{@link StatusTone#ERROR} — red 040 * <li>{@link StatusTone#NEUTRAL} — 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>"<max>+"</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}