@flux-ui/components 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/component/FluxBreadcrumb.vue.d.ts +15 -0
  2. package/dist/component/FluxBreadcrumbItem.vue.d.ts +23 -0
  3. package/dist/component/{FluxCheckbox.vue.d.ts → FluxFormCheckbox.vue.d.ts} +3 -0
  4. package/dist/component/FluxFormCheckboxGroup.vue.d.ts +26 -0
  5. package/dist/component/FluxFormField.vue.d.ts +1 -0
  6. package/dist/component/FluxFormNumberInput.vue.d.ts +26 -0
  7. package/dist/component/FluxFormRadio.vue.d.ts +18 -0
  8. package/dist/component/FluxFormRadioGroup.vue.d.ts +27 -0
  9. package/dist/component/FluxSkeleton.vue.d.ts +9 -0
  10. package/dist/component/index.d.ts +8 -1
  11. package/dist/composable/index.d.ts +2 -0
  12. package/dist/composable/useFormCheckboxGroupInjection.d.ts +2 -0
  13. package/dist/composable/useFormFieldInjection.d.ts +3 -2
  14. package/dist/composable/useFormRadioGroupInjection.d.ts +2 -0
  15. package/dist/data/di.d.ts +23 -0
  16. package/dist/index.css +1373 -1012
  17. package/dist/index.js +849 -355
  18. package/dist/index.js.map +1 -1
  19. package/package.json +3 -3
  20. package/src/component/FluxBreadcrumb.vue +24 -0
  21. package/src/component/FluxBreadcrumbItem.vue +72 -0
  22. package/src/component/FluxDataTable.vue +3 -3
  23. package/src/component/FluxFormCheckbox.vue +123 -0
  24. package/src/component/FluxFormCheckboxGroup.vue +57 -0
  25. package/src/component/FluxFormField.vue +23 -7
  26. package/src/component/FluxFormNumberInput.vue +180 -0
  27. package/src/component/FluxFormPinInput.vue +2 -2
  28. package/src/component/FluxFormRadio.vue +76 -0
  29. package/src/component/FluxFormRadioGroup.vue +53 -0
  30. package/src/component/FluxQuantitySelector.vue +4 -4
  31. package/src/component/FluxSkeleton.vue +46 -0
  32. package/src/component/FluxToggle.vue +4 -4
  33. package/src/component/index.ts +8 -1
  34. package/src/composable/index.ts +2 -0
  35. package/src/composable/useFormCheckboxGroupInjection.ts +6 -0
  36. package/src/composable/useFormFieldInjection.ts +7 -3
  37. package/src/composable/useFormRadioGroupInjection.ts +13 -0
  38. package/src/css/component/BorderBeam.module.scss +51 -22
  39. package/src/css/component/Breadcrumb.module.scss +86 -0
  40. package/src/css/component/Form.module.scss +251 -45
  41. package/src/css/component/SegmentedControl.module.scss +18 -18
  42. package/src/css/component/Skeleton.module.scss +67 -0
  43. package/src/data/di.ts +30 -0
  44. package/src/component/FluxCheckbox.vue +0 -87
@@ -288,6 +288,63 @@
288
288
  padding-right: 42px;
289
289
  }
290
290
 
291
+ .formNumberInputNative {
292
+ composes: formInputNative;
293
+
294
+ padding-right: 38px;
295
+ appearance: textfield;
296
+ -moz-appearance: textfield;
297
+ font-variant-numeric: tabular-nums;
298
+
299
+ &::-webkit-outer-spin-button,
300
+ &::-webkit-inner-spin-button {
301
+ margin: 0;
302
+ -webkit-appearance: none;
303
+ }
304
+ }
305
+
306
+ .formNumberInputButtons {
307
+ position: absolute;
308
+ top: 0;
309
+ right: 0;
310
+ bottom: 0;
311
+ display: flex;
312
+ flex-direction: column;
313
+ width: 33px;
314
+ border-left: 1px solid var(--surface-stroke);
315
+ border-radius: 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0;
316
+ overflow: hidden;
317
+ }
318
+
319
+ .formNumberInputButton {
320
+ display: flex;
321
+ flex: 1 1 0;
322
+ margin: 0;
323
+ padding: 0;
324
+ align-items: center;
325
+ justify-content: center;
326
+ background: var(--surface);
327
+ border: 0;
328
+ color: var(--foreground-secondary);
329
+ cursor: pointer;
330
+ transition: var(--transition-default);
331
+ transition-property: background, color;
332
+
333
+ & + & {
334
+ border-top: 1px solid var(--surface-stroke);
335
+ }
336
+
337
+ @include mixin.hover {
338
+ background: var(--surface-hover);
339
+ color: var(--foreground);
340
+ }
341
+
342
+ &:disabled {
343
+ color: var(--surface-stroke);
344
+ cursor: not-allowed;
345
+ }
346
+ }
347
+
291
348
  .formInputAddition {
292
349
  composes: formInput;
293
350
 
@@ -584,23 +641,37 @@
584
641
  composes: formTextArea;
585
642
  }
586
643
 
587
- .checkbox {
644
+ .formCheckboxGroup {
645
+ display: flex;
646
+ flex-direction: column;
647
+ align-items: flex-start;
648
+ gap: 12px;
649
+
650
+ &.isInline {
651
+ flex-direction: row;
652
+ flex-wrap: wrap;
653
+ gap: 8px 20px;
654
+ }
655
+ }
656
+
657
+ .formCheckbox {
588
658
  display: inline-flex;
589
659
  flex-shrink: 0;
660
+ align-items: flex-start;
590
661
  gap: 12px;
591
- line-height: 20px;
662
+ line-height: 21px;
592
663
  outline: 0;
593
664
 
594
665
  &.isDisabled {
595
666
  cursor: not-allowed;
596
667
 
597
- .checkboxElement {
668
+ .formCheckboxElement {
598
669
  background: var(--surface-disabled);
599
670
  border-color: var(--surface-stroke);
600
671
  color: var(--foreground-secondary);
601
672
  }
602
673
 
603
- .checkboxLabel {
674
+ .formCheckboxLabel {
604
675
  color: var(--foreground-secondary);
605
676
  }
606
677
  }
@@ -608,30 +679,31 @@
608
679
  &.isReadonly {
609
680
  cursor: default;
610
681
 
611
- .checkboxNative {
682
+ .formCheckboxNative {
612
683
  pointer-events: none;
613
684
  }
614
685
  }
615
686
 
616
- &.isInvalid .checkboxElement {
687
+ &.isInvalid .formCheckboxElement {
617
688
  border-color: var(--danger-500);
618
689
  }
619
690
  }
620
691
 
621
- .checkboxElement,
622
- .checkboxNative {
692
+ .formCheckboxElement,
693
+ .formCheckboxNative {
623
694
  margin: 1px 0 0;
624
695
  height: 20px;
625
696
  width: 20px;
626
697
  }
627
698
 
628
- .checkboxElement {
699
+ .formCheckboxElement {
629
700
  position: relative;
630
701
  display: inline-flex;
631
702
  height: 20px;
632
703
  width: 20px;
633
704
  padding: 0;
634
705
  align-items: center;
706
+ flex-shrink: 0;
635
707
  justify-content: center;
636
708
  background: var(--gray-50);
637
709
  border: 1px solid var(--gray-200);
@@ -657,22 +729,34 @@
657
729
  }
658
730
  }
659
731
 
660
- .checkboxLabel {
661
- align-self: center;
732
+ .formCheckboxText {
733
+ display: flex;
734
+ min-width: 0;
735
+ flex-direction: column;
736
+ gap: 3px;
662
737
  }
663
738
 
664
- .checkboxNative {
739
+ .formCheckboxLabel {
740
+ line-height: 21px;
741
+ }
742
+
743
+ .formCheckboxSubLabel {
744
+ color: var(--foreground-secondary);
745
+ font-size: 14px;
746
+ }
747
+
748
+ .formCheckboxNative {
665
749
  position: absolute;
666
750
  cursor: pointer;
667
751
  opacity: 0;
668
752
 
669
753
  @include mixin.hover {
670
- + .checkboxElement {
754
+ + .formCheckboxElement {
671
755
  background: var(--gray-100);
672
756
  }
673
757
  }
674
758
 
675
- &:is(:checked, :indeterminate) + .checkboxElement {
759
+ &:is(:checked, :indeterminate) + .formCheckboxElement {
676
760
  background: var(--primary-600);
677
761
  border-color: var(--primary-600);
678
762
 
@@ -681,7 +765,7 @@
681
765
  }
682
766
  }
683
767
 
684
- &:focus-visible + .checkboxElement {
768
+ &:focus-visible + .formCheckboxElement {
685
769
  @include mixin.focus-outline-visible(2px);
686
770
  }
687
771
 
@@ -689,14 +773,136 @@
689
773
  cursor: not-allowed;
690
774
  }
691
775
 
692
- &:disabled + .checkboxElement {
776
+ &:disabled + .formCheckboxElement {
693
777
  background: var(--gray-100);
694
778
  border-color: var(--gray-200);
695
779
  color: var(--foreground-secondary);
696
780
  }
697
781
  }
698
782
 
699
- .quantitySelector {
783
+ .formRadioGroup {
784
+ display: flex;
785
+ flex-direction: column;
786
+ align-items: flex-start;
787
+ gap: 12px;
788
+
789
+ &.isInline {
790
+ flex-direction: row;
791
+ flex-wrap: wrap;
792
+ gap: 9px 21px;
793
+ }
794
+ }
795
+
796
+ .formRadio {
797
+ display: inline-flex;
798
+ flex-shrink: 0;
799
+ gap: 12px;
800
+ line-height: 21px;
801
+ outline: 0;
802
+ cursor: pointer;
803
+
804
+ &.isDisabled {
805
+ cursor: not-allowed;
806
+
807
+ .formRadioElement {
808
+ background: var(--surface-disabled);
809
+ border-color: var(--surface-stroke);
810
+ }
811
+
812
+ .formRadioLabel {
813
+ color: var(--foreground-secondary);
814
+ }
815
+ }
816
+
817
+ &.isReadonly {
818
+ cursor: default;
819
+
820
+ .formRadioNative {
821
+ pointer-events: none;
822
+ }
823
+ }
824
+
825
+ &.isInvalid .formRadioElement {
826
+ border-color: var(--danger-500);
827
+ }
828
+ }
829
+
830
+ .formRadioElement,
831
+ .formRadioNative {
832
+ margin: 1px 0 0;
833
+ height: 20px;
834
+ width: 20px;
835
+ }
836
+
837
+ .formRadioElement {
838
+ position: relative;
839
+ display: inline-flex;
840
+ flex-shrink: 0;
841
+ height: 20px;
842
+ width: 20px;
843
+ align-items: center;
844
+ justify-content: center;
845
+ background: var(--gray-50);
846
+ border: 1px solid var(--gray-200);
847
+ border-radius: 50%;
848
+ cursor: pointer;
849
+ pointer-events: none;
850
+ transition: 210ms var(--swift-out);
851
+ transition-property: background, border-color, mixin.focus-ring-transition-properties();
852
+
853
+ @include mixin.focus-outline;
854
+
855
+ &::after {
856
+ content: '';
857
+ height: 8px;
858
+ width: 8px;
859
+ background: var(--primary-25);
860
+ border-radius: 50%;
861
+ scale: 0;
862
+ transition: inherit;
863
+ transition-property: scale;
864
+ }
865
+ }
866
+
867
+ .formRadioLabel {
868
+ align-self: center;
869
+ }
870
+
871
+ .formRadioNative {
872
+ position: absolute;
873
+ cursor: pointer;
874
+ opacity: 0;
875
+
876
+ @include mixin.hover {
877
+ + .formRadioElement {
878
+ background: var(--gray-100);
879
+ }
880
+ }
881
+
882
+ &:checked + .formRadioElement {
883
+ background: var(--primary-600);
884
+ border-color: var(--primary-600);
885
+
886
+ &::after {
887
+ scale: 1;
888
+ }
889
+ }
890
+
891
+ &:focus-visible + .formRadioElement {
892
+ @include mixin.focus-outline-visible(2px);
893
+ }
894
+
895
+ &:disabled {
896
+ cursor: not-allowed;
897
+ }
898
+
899
+ &:disabled + .formRadioElement {
900
+ background: var(--gray-100);
901
+ border-color: var(--gray-200);
902
+ }
903
+ }
904
+
905
+ .formQuantitySelector {
700
906
  min-width: max-content;
701
907
  align-self: center;
702
908
  justify-self: center;
@@ -714,7 +920,7 @@
714
920
  }
715
921
  }
716
922
 
717
- .quantitySelectorButton {
923
+ .formQuantitySelectorButton {
718
924
  margin: -1px;
719
925
  border: 0;
720
926
 
@@ -733,7 +939,7 @@
733
939
  }
734
940
  }
735
941
 
736
- .quantitySelectorInput {
942
+ .formQuantitySelectorInput {
737
943
  composes: formInput;
738
944
 
739
945
  margin: -1px 0;
@@ -765,7 +971,7 @@
765
971
  }
766
972
  }
767
973
 
768
- .pinInput {
974
+ .formPinInput {
769
975
  display: grid;
770
976
  width: min-content;
771
977
  gap: .4ch;
@@ -777,7 +983,7 @@
777
983
  font-weight: 700;
778
984
  }
779
985
 
780
- .pinInputField {
986
+ .formPinInputField {
781
987
  padding: 0;
782
988
  width: 2.7ch;
783
989
  border: 1px solid var(--surface-stroke);
@@ -791,10 +997,10 @@
791
997
  transition-property: border-color, mixin.focus-ring-transition-properties();
792
998
  }
793
999
 
794
- .pinInputEnabled {
795
- composes: pinInput;
1000
+ .formPinInputEnabled {
1001
+ composes: formPinInput;
796
1002
 
797
- .pinInputField {
1003
+ .formPinInputField {
798
1004
  background-color: var(--surface);
799
1005
  color: var(--foreground-prominent);
800
1006
 
@@ -802,17 +1008,17 @@
802
1008
  }
803
1009
  }
804
1010
 
805
- .pinInputDisabled {
806
- composes: pinInput;
1011
+ .formPinInputDisabled {
1012
+ composes: formPinInput;
807
1013
 
808
- .pinInputField {
1014
+ .formPinInputField {
809
1015
  background-color: var(--surface-disabled);
810
1016
  color: var(--foreground-secondary);
811
1017
  cursor: not-allowed;
812
1018
  }
813
1019
  }
814
1020
 
815
- .toggle {
1021
+ .formToggle {
816
1022
  position: relative;
817
1023
  display: block;
818
1024
  width: 54px;
@@ -825,35 +1031,35 @@
825
1031
 
826
1032
  @include mixin.focus-outline;
827
1033
 
828
- &:has(.toggleInput:focus-visible) {
1034
+ &:has(.formToggleInput:focus-visible) {
829
1035
  @include mixin.focus-outline-visible(2px);
830
1036
  }
831
1037
 
832
1038
  &.isChecked {
833
1039
  border-color: transparent;
834
1040
 
835
- .toggleInput::after {
1041
+ .formToggleInput::after {
836
1042
  translate: 24px 0;
837
1043
  }
838
1044
 
839
1045
  &:not(.isSwitch) {
840
1046
  background: var(--primary-600);
841
1047
 
842
- .toggleIcon {
1048
+ .formToggleIcon {
843
1049
  color: var(--primary-25);
844
1050
  }
845
1051
 
846
- .toggleInput::after {
1052
+ .formToggleInput::after {
847
1053
  border-color: transparent;
848
1054
  }
849
1055
  }
850
1056
 
851
- .toggleIconOn {
1057
+ .formToggleIconOn {
852
1058
  translate: calc(-50% + 24px) -50%;
853
1059
  }
854
1060
  }
855
1061
 
856
- &:not(.isChecked) .toggleIconOff {
1062
+ &:not(.isChecked) .formToggleIconOff {
857
1063
  translate: calc(-50% - 24px) -50%;
858
1064
  }
859
1065
 
@@ -862,7 +1068,7 @@
862
1068
  opacity: .6;
863
1069
  }
864
1070
 
865
- &.isReadonly .toggleInput {
1071
+ &.isReadonly .formToggleInput {
866
1072
  pointer-events: none;
867
1073
  }
868
1074
 
@@ -871,7 +1077,7 @@
871
1077
  }
872
1078
  }
873
1079
 
874
- .toggleIcon {
1080
+ .formToggleIcon {
875
1081
  position: absolute;
876
1082
  top: 50%;
877
1083
  color: var(--foreground-secondary);
@@ -879,19 +1085,19 @@
879
1085
  translate: -50% -50%;
880
1086
  }
881
1087
 
882
- .toggleIconOff {
883
- composes: toggleIcon;
1088
+ .formToggleIconOff {
1089
+ composes: formToggleIcon;
884
1090
 
885
1091
  left: 15px;
886
1092
  }
887
1093
 
888
- .toggleIconOn {
889
- composes: toggleIcon;
1094
+ .formToggleIconOn {
1095
+ composes: formToggleIcon;
890
1096
 
891
1097
  left: 39px;
892
1098
  }
893
1099
 
894
- .toggleInput {
1100
+ .formToggleInput {
895
1101
  -webkit-appearance: none;
896
1102
  appearance: none;
897
1103
 
@@ -921,10 +1127,10 @@
921
1127
  }
922
1128
  }
923
1129
 
924
- .toggle,
925
- .toggleIcon,
926
- .toggleInput,
927
- .toggleInput::after {
1130
+ .formToggle,
1131
+ .formToggleIcon,
1132
+ .formToggleInput,
1133
+ .formToggleInput::after {
928
1134
  transition: 210ms var(--swift-out);
929
1135
  transition-property: background, border-color, color, opacity, scale, translate, mixin.focus-ring-transition-properties();
930
1136
  }
@@ -8,24 +8,6 @@
8
8
  background: var(--gray-50);
9
9
  border: 1px solid var(--surface-stroke);
10
10
  border-radius: var(--radius);
11
-
12
- &.isSmall {
13
- height: 30px;
14
- padding: 0 12px;
15
- gap: 9px;
16
- }
17
-
18
- &.isMedium {
19
- height: 36px;
20
- padding: 0 15px;
21
- gap: 9px;
22
- }
23
-
24
- &.isLarge {
25
- height: 48px;
26
- padding: 0 21px;
27
- gap: 12px;
28
- }
29
11
  }
30
12
 
31
13
  .segmentedControlFill {
@@ -81,6 +63,24 @@
81
63
  cursor: default;
82
64
  }
83
65
 
66
+ &.isSmall {
67
+ height: 30px;
68
+ padding: 0 12px;
69
+ gap: 9px;
70
+ }
71
+
72
+ &.isMedium {
73
+ height: 36px;
74
+ padding: 0 15px;
75
+ gap: 9px;
76
+ }
77
+
78
+ &.isLarge {
79
+ height: 48px;
80
+ padding: 0 21px;
81
+ gap: 12px;
82
+ }
83
+
84
84
  &:disabled {
85
85
  opacity: .5;
86
86
  pointer-events: none;
@@ -0,0 +1,67 @@
1
+ @use '~flux/components/css/mixin';
2
+
3
+ .skeleton {
4
+ position: relative;
5
+ display: block;
6
+ height: 1em;
7
+ width: 100%;
8
+ background: var(--gray-100);
9
+ border-radius: var(--radius);
10
+ overflow: hidden;
11
+
12
+ &::after {
13
+ content: '';
14
+ position: absolute;
15
+ inset: 0;
16
+ background: linear-gradient(90deg, transparent 0%, var(--surface) 50%, transparent 100%);
17
+ translate: -100% 0;
18
+ animation: skeleton-shimmer 1.6s ease-in-out infinite;
19
+ }
20
+
21
+ @media (prefers-reduced-motion: reduce) {
22
+ animation: skeleton-pulse 1.6s ease-in-out infinite;
23
+
24
+ &::after {
25
+ display: none;
26
+ }
27
+ }
28
+ }
29
+
30
+ .isText {
31
+ height: 1em;
32
+ border-radius: var(--radius-half);
33
+ vertical-align: middle;
34
+ }
35
+
36
+ .isCircle {
37
+ height: 40px;
38
+ width: 40px;
39
+ flex-shrink: 0;
40
+ border-radius: 50%;
41
+ }
42
+
43
+ .isRectangle {
44
+ height: 120px;
45
+ border-radius: 0;
46
+ }
47
+
48
+ .isRounded {
49
+ height: 120px;
50
+ border-radius: calc(var(--radius) * 2);
51
+ }
52
+
53
+ @keyframes skeleton-shimmer {
54
+ from {
55
+ translate: -100% 0;
56
+ }
57
+
58
+ to {
59
+ translate: 100% 0;
60
+ }
61
+ }
62
+
63
+ @keyframes skeleton-pulse {
64
+ 50% {
65
+ opacity: .5;
66
+ }
67
+ }
package/src/data/di.ts CHANGED
@@ -9,7 +9,9 @@ export const FluxKanbanInjectionKey: InjectionKey<FluxKanbanInjection> = Symbol(
9
9
  export const FluxExpandableGroupInjectionKey: InjectionKey<FluxExpandableGroupInjection> = Symbol();
10
10
  export const FluxFlyoutInjectionKey: InjectionKey<FluxFlyoutInjection> = Symbol();
11
11
  export const FluxFilterInjectionKey: InjectionKey<FluxFilterInjection> = Symbol();
12
+ export const FluxFormCheckboxGroupInjectionKey: InjectionKey<FluxFormCheckboxGroupInjection> = Symbol();
12
13
  export const FluxFormFieldInjectionKey: InjectionKey<FluxFormFieldInjection> = Symbol();
14
+ export const FluxFormRadioGroupInjectionKey: InjectionKey<FluxFormRadioGroupInjection> = Symbol();
13
15
  export const FluxSegmentedControlInjectionKey: InjectionKey<FluxSegmentedControlInjection> = Symbol();
14
16
  export const FluxSplitViewInjectionKey: InjectionKey<FluxSplitViewInjection> = Symbol();
15
17
  export const FluxTabBarInjectionKey: InjectionKey<FluxTabBarInjection> = Symbol();
@@ -150,6 +152,22 @@ export type FluxFlyoutInjection = {
150
152
 
151
153
  export type FluxFormFieldInjection = {
152
154
  readonly id?: string;
155
+ readonly labelId?: string;
156
+ readonly isGroup?: boolean;
157
+
158
+ registerControl?(): string;
159
+ };
160
+
161
+ export type FluxFormCheckboxGroupValue = string | number | boolean;
162
+
163
+ export type FluxFormCheckboxGroupInjection = {
164
+ readonly modelValue: Ref<FluxFormCheckboxGroupValue[]>;
165
+ readonly disabled: Ref<boolean>;
166
+ readonly isReadonly: Ref<boolean>;
167
+ readonly error: Ref<string | null | undefined>;
168
+
169
+ has(value: FluxFormCheckboxGroupValue): boolean;
170
+ toggle(value: FluxFormCheckboxGroupValue): void;
153
171
  };
154
172
 
155
173
  export type FluxSplitViewPaneSpec = {
@@ -169,6 +187,18 @@ export type FluxSplitViewInjection = {
169
187
  getPaneIndex(id: number): number;
170
188
  };
171
189
 
190
+ export type FluxFormRadioGroupValue = string | number | boolean;
191
+
192
+ export type FluxFormRadioGroupInjection = {
193
+ readonly name: string;
194
+ readonly modelValue: Ref<FluxFormRadioGroupValue | undefined>;
195
+ readonly disabled: Ref<boolean>;
196
+ readonly isReadonly: Ref<boolean>;
197
+ readonly error: Ref<string | null | undefined>;
198
+
199
+ select(value: FluxFormRadioGroupValue): void;
200
+ };
201
+
172
202
  export type FluxSegmentedControlValue = string | number;
173
203
 
174
204
  export type FluxSegmentedControlInjection = {