001package react4j;
002
003import java.util.Objects;
004import javax.annotation.Nonnull;
005import javax.annotation.Nullable;
006import jsinterop.annotations.JsFunction;
007import jsinterop.annotations.JsOverlay;
008import jsinterop.annotations.JsPackage;
009import jsinterop.annotations.JsType;
010import jsinterop.base.Js;
011import jsinterop.base.JsPropertyMap;
012
013/**
014 * Context is designed to share data that can be considered "global" for a tree of views.
015 * In a typical React application, data is passed top-down (parent to child) via inputs, but this can
016 * be cumbersome for certain types of inputs (e.g. locale preference, UI theme) that are required by
017 * many views within an application. Context provides a way to share values like this between
018 * views without having to explicitly pass a input through every level of the tree.
019 */
020@JsType( isNative = true, namespace = JsPackage.GLOBAL, name = "Object" )
021public final class Context<T>
022{
023  /**
024   * Create a builder for the Provider component.
025   *
026   * @return a builder for the Provider component.
027   */
028  @JsOverlay
029  @Nonnull
030  public ProviderBuilder<T> provider()
031  {
032    return new ProviderBuilder<>( this );
033  }
034
035  /**
036   * Create a provider component that provides specified value to specified children.
037   *
038   * @param value    the value to provider to children.
039   * @param children the child elements.
040   * @return the element.
041   */
042  @JsOverlay
043  @Nonnull
044  public ReactNode provide( @Nullable final T value, final ReactNode... children )
045  {
046    return provider().value( value ).children( children );
047  }
048
049  /**
050   * Create a builder for the Consumer component.
051   *
052   * @return a builder for the Consumer component.
053   */
054  @JsOverlay
055  @Nonnull
056  public ConsumerBuilder<T> consumer()
057  {
058    return new ConsumerBuilder<>( this );
059  }
060
061  /**
062   * A Builder for the Provider component.
063   * A provider component is a react component that allows Consumers to subscribe to context changes.
064   */
065  public static final class ProviderBuilder<ST>
066  {
067    @Nonnull
068    private final ReactElement _element;
069
070    private ProviderBuilder( @Nonnull final Context<ST> context )
071    {
072      _element = ReactElement.createContextElement( Js.<JsPropertyMap<Object>>cast( context ).get( "Provider" ) );
073    }
074
075    /**
076     * Specify the key for the component if required.
077     *
078     * @param key the key for the component.
079     * @return the builder.
080     */
081    @Nonnull
082    public ProviderBuilder<ST> key( @Nonnull final String key )
083    {
084      _element.setKey( Objects.requireNonNull( key ) );
085      return this;
086    }
087
088    @Nonnull
089    public ProviderBuilder<ST> value( @Nullable final ST value )
090    {
091      _element.input( "value", value );
092      return this;
093    }
094
095    @Nonnull
096    public ReactNode children( final ReactNode... children )
097    {
098      _element.input( "children", children );
099      return build();
100    }
101
102    @Nonnull
103    public ReactNode build()
104    {
105      return _element;
106    }
107  }
108
109  /**
110   * Interface used to type the render function input.
111   */
112  @JsFunction
113  @FunctionalInterface
114  public interface ConsumerRenderFunction<T>
115  {
116    /**
117     * Render the specified tree for context value.
118     *
119     * @param value the context value.
120     * @return the rendered react node tree.
121     */
122    ReactNode render( T value );
123  }
124
125  /**
126   * A Builder for the Consumer component.
127   * A Consumer component is a react component that subscribes to context changes.
128   */
129  public static final class ConsumerBuilder<ST>
130  {
131    @Nonnull
132    private final ReactElement _element;
133
134    private ConsumerBuilder( @Nonnull final Context<ST> context )
135    {
136      _element = ReactElement.createContextElement( Js.<JsPropertyMap<Object>>cast( context ).get( "Consumer" ) );
137    }
138
139    /**
140     * Specify the key for component if required.
141     *
142     * @param key the key for the component.
143     * @return the builder.
144     */
145    @Nonnull
146    public ConsumerBuilder<ST> key( @Nonnull final String key )
147    {
148      _element.setKey( Objects.requireNonNull( key ) );
149      return this;
150    }
151
152    /**
153     * Specify the child render function.
154     *
155     * @param render the child render function.
156     * @return the creates react node.
157     */
158    @Nonnull
159    public ReactNode render( @Nonnull final ConsumerRenderFunction<ST> render )
160    {
161      _element.input( "children", render );
162      return _element;
163    }
164  }
165}